include publish-options. prepare code for reconfiguration
This commit is contained in:
parent
c077e4e8da
commit
58b1e26367
|
@ -78,7 +78,7 @@ public final class Namespace {
|
||||||
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options";
|
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options";
|
||||||
public static final String PUBSUB_ERROR = PUBSUB + "#errors";
|
public static final String PUBSUB_ERROR = PUBSUB + "#errors";
|
||||||
public static final String PUB_SUB = "http://jabber.org/protocol/pubsub";
|
public static final String PUB_SUB = "http://jabber.org/protocol/pubsub";
|
||||||
public static final String PUB_SUB_ERROR = PUB_SUB + "#errors";
|
public static final String PUB_SUB_ERRORS = PUB_SUB + "#errors";
|
||||||
public static final String PUB_SUB_EVENT = PUB_SUB + "#event";
|
public static final String PUB_SUB_EVENT = PUB_SUB + "#event";
|
||||||
public static final String PUB_SUB_OWNER = PUB_SUB + "#owner";
|
public static final String PUB_SUB_OWNER = PUB_SUB + "#owner";
|
||||||
public static final String PUB_SUB_PERSISTENT_ITEMS = PUB_SUB + "#persistent-items";
|
public static final String PUB_SUB_PERSISTENT_ITEMS = PUB_SUB + "#persistent-items";
|
||||||
|
|
|
@ -21,4 +21,8 @@ public class IqErrorException extends Exception {
|
||||||
final var text = error == null ? null : error.getText();
|
final var text = error == null ? null : error.getText();
|
||||||
return text == null ? null : text.getContent();
|
return text == null ? null : text.getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Iq getResponse() {
|
||||||
|
return this.response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class NodeConfiguration implements Map<String, Object> {
|
||||||
|
|
||||||
|
private static final String PERSIST_ITEMS = "pubsub#persist_items";
|
||||||
|
private static final String ACCESS_MODEL = "pubsub#access_model";
|
||||||
|
private static final String SEND_LAST_PUBLISHED_ITEM = "pubsub#send_last_published_item";
|
||||||
|
private static final String MAX_ITEMS = "pubsub#max_items";
|
||||||
|
private static final String NOTIFY_DELETE = "pubsub#notify_delete";
|
||||||
|
private static final String NOTIFY_RETRACT = "pubsub#notify_retract";
|
||||||
|
|
||||||
|
public static final NodeConfiguration OPEN =
|
||||||
|
new NodeConfiguration(
|
||||||
|
new ImmutableMap.Builder<String, Object>()
|
||||||
|
.put(PERSIST_ITEMS, Boolean.TRUE)
|
||||||
|
.put(ACCESS_MODEL, "open")
|
||||||
|
.build());
|
||||||
|
public static final NodeConfiguration WHITELIST_MAX_ITEMS =
|
||||||
|
new NodeConfiguration(
|
||||||
|
new ImmutableMap.Builder<String, Object>()
|
||||||
|
.put(PERSIST_ITEMS, Boolean.TRUE)
|
||||||
|
.put(ACCESS_MODEL, "whitelist")
|
||||||
|
.put(SEND_LAST_PUBLISHED_ITEM, "never")
|
||||||
|
.put(MAX_ITEMS, "max")
|
||||||
|
.put(NOTIFY_DELETE, Boolean.TRUE)
|
||||||
|
.put(NOTIFY_RETRACT, Boolean.TRUE)
|
||||||
|
.build());
|
||||||
|
private final Map<String, Object> delegate;
|
||||||
|
|
||||||
|
private NodeConfiguration(Map<String, Object> map) {
|
||||||
|
this.delegate = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return this.delegate.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.delegate.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(@Nullable Object o) {
|
||||||
|
return this.delegate.containsKey(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(@Nullable Object o) {
|
||||||
|
return this.delegate.containsValue(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object get(@Nullable Object o) {
|
||||||
|
return this.delegate.get(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object put(String s, Object o) {
|
||||||
|
return this.delegate.put(s, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object remove(@Nullable Object o) {
|
||||||
|
return this.delegate.remove(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(@NonNull Map<? extends String, ?> map) {
|
||||||
|
this.delegate.putAll(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
this.delegate.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
return this.delegate.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<Object> values() {
|
||||||
|
return this.delegate.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Set<Entry<String, Object>> entrySet() {
|
||||||
|
return this.delegate.entrySet();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.model.pubsub.error.PubSubError;
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
|
public class PreconditionNotMetException extends PubSubErrorException {
|
||||||
|
|
||||||
|
public PreconditionNotMetException(final Iq response) {
|
||||||
|
super(response);
|
||||||
|
if (this.pubSubError instanceof PubSubError.PreconditionNotMet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new AssertionError("This exception should only be constructed for PreconditionNotMet errors");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.model.pubsub.error.PubSubError;
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
|
public class PubSubErrorException extends IqErrorException {
|
||||||
|
|
||||||
|
protected final PubSubError pubSubError;
|
||||||
|
|
||||||
|
public PubSubErrorException(Iq response) {
|
||||||
|
super(response);
|
||||||
|
final var error = response.getError();
|
||||||
|
final var pubSubError = error == null ? null : error.getExtension(PubSubError.class);
|
||||||
|
if (pubSubError == null) {
|
||||||
|
throw new AssertionError("This exception should only be constructed for PubSubErrors");
|
||||||
|
}
|
||||||
|
this.pubSubError = pubSubError;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import im.conversations.android.database.AxolotlDatabaseStore;
|
import im.conversations.android.database.AxolotlDatabaseStore;
|
||||||
import im.conversations.android.xmpp.IqErrorException;
|
import im.conversations.android.xmpp.IqErrorException;
|
||||||
|
import im.conversations.android.xmpp.NodeConfiguration;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.axolotl.AxolotlAddress;
|
import im.conversations.android.xmpp.axolotl.AxolotlAddress;
|
||||||
import im.conversations.android.xmpp.model.axolotl.Bundle;
|
import im.conversations.android.xmpp.model.axolotl.Bundle;
|
||||||
|
@ -215,7 +216,11 @@ public class AxolotlManager extends AbstractManager {
|
||||||
final var deviceList = new DeviceList();
|
final var deviceList = new DeviceList();
|
||||||
deviceList.setDeviceIds(deviceIds);
|
deviceList.setDeviceIds(deviceIds);
|
||||||
return getManager(PubSubManager.class)
|
return getManager(PubSubManager.class)
|
||||||
.publishSingleton(getAccount().address, deviceList, Namespace.AXOLOTL_DEVICE_LIST);
|
.publishSingleton(
|
||||||
|
getAccount().address,
|
||||||
|
deviceList,
|
||||||
|
Namespace.AXOLOTL_DEVICE_LIST,
|
||||||
|
NodeConfiguration.OPEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture<Void> publishBundle() {
|
private ListenableFuture<Void> publishBundle() {
|
||||||
|
@ -231,7 +236,8 @@ public class AxolotlManager extends AbstractManager {
|
||||||
Namespace.AXOLOTL_BUNDLES,
|
Namespace.AXOLOTL_BUNDLES,
|
||||||
signalProtocolStore.getLocalRegistrationId());
|
signalProtocolStore.getLocalRegistrationId());
|
||||||
return getManager(PubSubManager.class)
|
return getManager(PubSubManager.class)
|
||||||
.publishSingleton(getAccount().address, bundle, node);
|
.publishSingleton(
|
||||||
|
getAccount().address, bundle, node, NodeConfiguration.OPEN);
|
||||||
},
|
},
|
||||||
MoreExecutors.directExecutor());
|
MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
package im.conversations.android.xmpp.manager;
|
package im.conversations.android.xmpp.manager;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import com.google.common.util.concurrent.AsyncFunction;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import im.conversations.android.xmpp.ExtensionFactory;
|
import im.conversations.android.xmpp.ExtensionFactory;
|
||||||
|
import im.conversations.android.xmpp.IqErrorException;
|
||||||
|
import im.conversations.android.xmpp.NodeConfiguration;
|
||||||
|
import im.conversations.android.xmpp.PreconditionNotMetException;
|
||||||
|
import im.conversations.android.xmpp.PubSubErrorException;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
import im.conversations.android.xmpp.model.pubsub.Items;
|
import im.conversations.android.xmpp.model.pubsub.Items;
|
||||||
import im.conversations.android.xmpp.model.pubsub.PubSub;
|
import im.conversations.android.xmpp.model.pubsub.PubSub;
|
||||||
|
import im.conversations.android.xmpp.model.pubsub.PublishOptions;
|
||||||
|
import im.conversations.android.xmpp.model.pubsub.error.PubSubError;
|
||||||
import im.conversations.android.xmpp.model.pubsub.event.Event;
|
import im.conversations.android.xmpp.model.pubsub.event.Event;
|
||||||
import im.conversations.android.xmpp.model.pubsub.event.Purge;
|
import im.conversations.android.xmpp.model.pubsub.event.Purge;
|
||||||
import im.conversations.android.xmpp.model.stanza.Iq;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
@ -164,34 +172,105 @@ public class PubSubManager extends AbstractManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Void> publishSingleton(Jid address, Extension item) {
|
public ListenableFuture<Void> publishSingleton(
|
||||||
|
Jid address, Extension item, final NodeConfiguration nodeConfiguration) {
|
||||||
final var id = ExtensionFactory.id(item.getClass());
|
final var id = ExtensionFactory.id(item.getClass());
|
||||||
return publish(address, item, SINGLETON_ITEM_ID, id.namespace);
|
return publish(address, item, SINGLETON_ITEM_ID, id.namespace, nodeConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Void> publishSingleton(Jid address, Extension item, final String node) {
|
public ListenableFuture<Void> publishSingleton(
|
||||||
return publish(address, item, SINGLETON_ITEM_ID, node);
|
Jid address,
|
||||||
|
Extension item,
|
||||||
|
final String node,
|
||||||
|
final NodeConfiguration nodeConfiguration) {
|
||||||
|
return publish(address, item, SINGLETON_ITEM_ID, node, nodeConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Void> publish(Jid address, Extension item, final String itemId) {
|
public ListenableFuture<Void> publish(
|
||||||
|
Jid address,
|
||||||
|
Extension item,
|
||||||
|
final String itemId,
|
||||||
|
final NodeConfiguration nodeConfiguration) {
|
||||||
final var id = ExtensionFactory.id(item.getClass());
|
final var id = ExtensionFactory.id(item.getClass());
|
||||||
return publish(address, item, itemId, id.namespace);
|
return publish(address, item, itemId, id.namespace, nodeConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Void> publish(
|
public ListenableFuture<Void> publish(
|
||||||
final Jid address,
|
final Jid address,
|
||||||
final Extension itemPayload,
|
final Extension itemPayload,
|
||||||
final String itemId,
|
final String itemId,
|
||||||
final String node) {
|
final String node,
|
||||||
|
final NodeConfiguration nodeConfiguration) {
|
||||||
|
final var future = publishNoRetry(address, itemPayload, itemId, node, nodeConfiguration);
|
||||||
|
return Futures.catchingAsync(
|
||||||
|
future,
|
||||||
|
PreconditionNotMetException.class,
|
||||||
|
ex -> {
|
||||||
|
LOGGER.info("Node {} on {} requires reconfiguration", node, address);
|
||||||
|
final var reconfigurationFuture =
|
||||||
|
reconfigureNode(address, node, nodeConfiguration);
|
||||||
|
return Futures.transformAsync(
|
||||||
|
reconfigurationFuture,
|
||||||
|
ignored ->
|
||||||
|
publishNoRetry(
|
||||||
|
address, itemPayload, itemId, node, nodeConfiguration),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<Void> publishNoRetry(
|
||||||
|
final Jid address,
|
||||||
|
final Extension itemPayload,
|
||||||
|
final String itemId,
|
||||||
|
final String node,
|
||||||
|
final NodeConfiguration nodeConfiguration) {
|
||||||
final var iq = new Iq(Iq.Type.SET);
|
final var iq = new Iq(Iq.Type.SET);
|
||||||
iq.setTo(address);
|
iq.setTo(address);
|
||||||
final var pubSub = iq.addExtension(new PubSub());
|
final var pubSub = iq.addExtension(new PubSub());
|
||||||
|
pubSub.addExtension(PublishOptions.of(nodeConfiguration));
|
||||||
final var pubSubItemsWrapper = pubSub.addExtension(new PubSub.ItemsWrapper());
|
final var pubSubItemsWrapper = pubSub.addExtension(new PubSub.ItemsWrapper());
|
||||||
pubSubItemsWrapper.setNode(node);
|
pubSubItemsWrapper.setNode(node);
|
||||||
final var item = pubSubItemsWrapper.addExtension(new PubSub.Item());
|
final var item = pubSubItemsWrapper.addExtension(new PubSub.Item());
|
||||||
item.setId(itemId);
|
item.setId(itemId);
|
||||||
item.addExtension(itemPayload);
|
item.addExtension(itemPayload);
|
||||||
return Futures.transform(
|
final ListenableFuture<Void> iqFuture =
|
||||||
connection.sendIqPacket(iq), result -> null, MoreExecutors.directExecutor());
|
Futures.transform(
|
||||||
|
connection.sendIqPacket(iq),
|
||||||
|
result -> null,
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
return Futures.catchingAsync(
|
||||||
|
iqFuture,
|
||||||
|
IqErrorException.class,
|
||||||
|
new PubSubExceptionTransformer<>(),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<Void> reconfigureNode(
|
||||||
|
final Jid address, final String node, final NodeConfiguration nodeConfiguration) {
|
||||||
|
|
||||||
|
return Futures.immediateVoidFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PubSubExceptionTransformer<V>
|
||||||
|
implements AsyncFunction<IqErrorException, V> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public ListenableFuture<V> apply(@NonNull IqErrorException ex) {
|
||||||
|
final var error = ex.getError();
|
||||||
|
if (error == null) {
|
||||||
|
return Futures.immediateFailedFuture(ex);
|
||||||
|
}
|
||||||
|
final PubSubError pubSubError = error.getExtension(PubSubError.class);
|
||||||
|
if (pubSubError instanceof PubSubError.PreconditionNotMet) {
|
||||||
|
return Futures.immediateFailedFuture(
|
||||||
|
new PreconditionNotMetException(ex.getResponse()));
|
||||||
|
} else if (pubSubError != null) {
|
||||||
|
return Futures.immediateFailedFuture(new PubSubErrorException(ex.getResponse()));
|
||||||
|
} else {
|
||||||
|
return Futures.immediateFailedFuture(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.google.common.collect.Iterables;
|
||||||
import im.conversations.android.annotation.XmlElement;
|
import im.conversations.android.annotation.XmlElement;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@XmlElement(name = "x")
|
@XmlElement(name = "x")
|
||||||
public class Data extends Extension {
|
public class Data extends Extension {
|
||||||
|
@ -25,4 +26,37 @@ public class Data extends Extension {
|
||||||
return Collections2.filter(
|
return Collections2.filter(
|
||||||
this.getExtensions(Field.class), f -> !FORM_TYPE.equals(f.getFieldName()));
|
this.getExtensions(Field.class), f -> !FORM_TYPE.equals(f.getFieldName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addField(final String name, final Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalArgumentException("Null values are not supported on data fields");
|
||||||
|
}
|
||||||
|
final var field = this.addExtension(new Field());
|
||||||
|
field.setFieldName(name);
|
||||||
|
final var valueExtension = field.addExtension(new Value());
|
||||||
|
if (value instanceof String) {
|
||||||
|
valueExtension.setContent((String) value);
|
||||||
|
} else if (value instanceof Integer) {
|
||||||
|
valueExtension.setContent(String.valueOf(value));
|
||||||
|
} else if (value instanceof Boolean) {
|
||||||
|
valueExtension.setContent(Boolean.TRUE.equals(value) ? "true" : "false");
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format(
|
||||||
|
"%s is not a supported field value", value.getClass().getSimpleName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFormType(final String formType) {
|
||||||
|
this.addField(FORM_TYPE, formType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Data of(final String formType, final Map<String, Object> values) {
|
||||||
|
final var data = new Data();
|
||||||
|
data.setFormType(formType);
|
||||||
|
for (final Map.Entry<String, Object> entry : values.entrySet()) {
|
||||||
|
data.addField(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,8 @@ public class Field extends Extension {
|
||||||
public Collection<String> getValues() {
|
public Collection<String> getValues() {
|
||||||
return Collections2.transform(getExtensions(Value.class), Element::getContent);
|
return Collections2.transform(getExtensions(Value.class), Element::getContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFieldName(String name) {
|
||||||
|
this.setAttribute("var", name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
public abstract class Condition extends Extension {
|
public abstract class Condition extends Extension {
|
||||||
|
|
||||||
private Condition(Class<? extends Extension> clazz) {
|
private Condition(Class<? extends Condition> clazz) {
|
||||||
super(clazz);
|
super(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package im.conversations.android.xmpp.model.pubsub;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.NodeConfiguration;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import im.conversations.android.xmpp.model.data.Data;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class PublishOptions extends Extension {
|
||||||
|
|
||||||
|
public PublishOptions() {
|
||||||
|
super(PublishOptions.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublishOptions of(NodeConfiguration nodeConfiguration) {
|
||||||
|
final var publishOptions = new PublishOptions();
|
||||||
|
publishOptions.addExtension(Data.of(Namespace.PUB_SUB_PUBLISH_OPTIONS, nodeConfiguration));
|
||||||
|
return publishOptions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package im.conversations.android.xmpp.model.pubsub.error;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
public abstract class PubSubError extends Extension {
|
||||||
|
|
||||||
|
private PubSubError(Class<? extends PubSubError> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public static class PreconditionNotMet extends PubSubError {
|
||||||
|
|
||||||
|
private PreconditionNotMet() {
|
||||||
|
super(PreconditionNotMet.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@XmlPackage(namespace = Namespace.PUB_SUB_ERRORS)
|
||||||
|
package im.conversations.android.xmpp.model.pubsub.error;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
Loading…
Reference in a new issue