/*
* Kontalk Java client
* Copyright (C) 2016 Kontalk Devteam <devteam@kontalk.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kontalk.client;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.pubsub.Item;
import org.jivesoftware.smackx.pubsub.ItemsExtension;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubElementType;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.pubsub.packet.PubSub;
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
import org.kontalk.misc.JID;
import org.kontalk.system.AvatarHandler;
/**
* Manage publishing and requesting user avatars (XEP-0084).
*
* Metadata notification events are incoming as PubSub messages from message
* listener.
*
* @author Alexander Bikadorov {@literal <bikaejkb@mail.tu-berlin.de>}
*/
final class AvatarSendReceiver {
private static final Logger LOGGER = Logger.getLogger(AvatarSendReceiver.class.getName());
static final String NOTIFY_FEATURE = "urn:xmpp:avatar:metadata+notify";
static final String METADATA_NODE = "urn:xmpp:avatar:metadata";
private static final String DATA_NODE = "urn:xmpp:avatar:data";
static {
ProviderManager.addExtensionProvider(
AvatarMetadataExtension.ELEMENT_NAME,
AvatarMetadataExtension.NAMESPACE,
new AvatarMetadataExtension.Provider());
ProviderManager.addExtensionProvider(
AvatarDataExtension.ELEMENT_NAME,
AvatarDataExtension.NAMESPACE,
new AvatarDataExtension.Provider());
}
private final KonConnection mConn;
private final AvatarHandler mHandler;
AvatarSendReceiver(KonConnection conn, AvatarHandler handler) {
mConn = conn;
mHandler = handler;
}
// TODO beta.kontalk.net does not support this, untested
void publish(String id, byte[] data) {
if (!mConn.isAuthenticated()) {
LOGGER.info("not logged in");
return;
}
PubSubManager mPubSubManager = PubSubManager.getInstance(mConn, mConn.getServiceName());
LeafNode node;
try {
node = mPubSubManager.createNode(DATA_NODE);
} catch (SmackException.NoResponseException |
XMPPException.XMPPErrorException |
SmackException.NotConnectedException |
InterruptedException ex) {
LOGGER.log(Level.WARNING, "can't create node", ex);
return;
}
PayloadItem<AvatarDataExtension> item = new PayloadItem<>(id,
new AvatarDataExtension(data));
try {
// blocking
node.send(item);
} catch (SmackException.NoResponseException |
XMPPException.XMPPErrorException |
SmackException.NotConnectedException |
InterruptedException ex) {
LOGGER.log(Level.WARNING, "can't send item", ex);
return;
}
// TODO
LOGGER.warning("not implemented");
// publish meta data...
}
boolean delete() {
if (!mConn.isAuthenticated()) {
LOGGER.info("not logged in");
return false;
}
// TODO
LOGGER.warning("not implemented");
return false;
}
void processMetadataEvent(JID jid, ItemsExtension itemsExt) {
List<? extends ExtensionElement> items = itemsExt.getItems();
if (items.isEmpty()) {
LOGGER.warning("no items in items event");
return;
}
// there should be only one item
ExtensionElement e = items.get(0);
if (!(e instanceof PayloadItem)) {
LOGGER.warning("element not a payloaditem");
return;
}
PayloadItem item = (PayloadItem) e;
ExtensionElement metadataExt = item.getPayload();
if (!(metadataExt instanceof AvatarMetadataExtension)) {
LOGGER.warning("payload not avatar metadata");
return;
}
AvatarMetadataExtension metadata = (AvatarMetadataExtension) metadataExt;
List<AvatarMetadataExtension.Info> infos = metadata.getInfos();
if (infos.isEmpty()) {
// this means the contact disabled avatar publishing
mHandler.onNotify(jid, "");
return;
}
// assuming infos are always in the same order
for (AvatarMetadataExtension.Info info : infos) {
if (AvatarHandler.SUPPORTED_TYPES.contains(info.getType())) {
mHandler.onNotify(jid, info.getId());
break;
} else {
LOGGER.info("image type not supported: "+info.getType());
}
}
}
void requestAndListen(final JID jid, final String id) {
// I dont get how to use this here
//PubSubManager manager = new PubSubManager(conn);
PubSub request = new PubSub(jid.toBareSmack(), IQ.Type.get, PubSubNamespace.BASIC);
request.addExtension(
new ItemsExtension(
ItemsExtension.ItemsElementType.items,
DATA_NODE,
Collections.singletonList(new Item(id))));
// handle response
StanzaListener callback = new StanzaListener() {
@Override
public void processStanza(Stanza packet)
throws SmackException.NotConnectedException {
if (!(packet instanceof PubSub)) {
LOGGER.warning("response not a pubsub packet");
return;
}
PubSub pubSub = (PubSub) packet;
ExtensionElement itemsExt = pubSub.getExtension(PubSubElementType.ITEMS);
if (!(itemsExt instanceof ItemsExtension)) {
LOGGER.warning("no items extension in response");
return;
}
ItemsExtension items = (ItemsExtension) itemsExt;
List<? extends ExtensionElement> itemsList = items.getItems();
if (itemsList.isEmpty()) {
// TODO why this happens?
LOGGER.warning("no items in itemlist");
return;
}
// there should be only one item
ExtensionElement e = itemsList.get(0);
if (!(e instanceof PayloadItem)) {
LOGGER.warning("element not a payloaditem");
return;
}
PayloadItem item = (PayloadItem) e;
ExtensionElement dataExt = item.getPayload();
if (!(dataExt instanceof AvatarDataExtension)) {
LOGGER.warning("payload not avatar data");
return;
}
AvatarDataExtension avatarExt = (AvatarDataExtension) dataExt;
byte[] avatarData = avatarExt.getData();
if (avatarData.length == 0) {
LOGGER.warning("no avatar data in packet");
return;
}
mHandler.onData(jid, id, avatarData);
}
};
mConn.sendWithCallback(request, callback);
}
}