diff --git a/src/main/java/im/conversations/android/database/dao/AxolotlDao.java b/src/main/java/im/conversations/android/database/dao/AxolotlDao.java index d723283b0..d841eeefc 100644 --- a/src/main/java/im/conversations/android/database/dao/AxolotlDao.java +++ b/src/main/java/im/conversations/android/database/dao/AxolotlDao.java @@ -45,7 +45,7 @@ public abstract class AxolotlDao { @Query( "SELECT EXISTS(SELECT deviceId FROM axolotl_device_list JOIN axolotl_device_list_item" - + " ON axolotl_device_list.id=axolotl_device_list_item.deviceId WHERE" + + " ON axolotl_device_list.id=axolotl_device_list_item.deviceListId WHERE" + " accountId=:account AND address=:address AND deviceId=:deviceId)") public abstract boolean hasDeviceId(final long account, final Jid address, final int deviceId); diff --git a/src/main/java/im/conversations/android/xmpp/IqErrorException.java b/src/main/java/im/conversations/android/xmpp/IqErrorException.java index e064ee491..7959c8ec4 100644 --- a/src/main/java/im/conversations/android/xmpp/IqErrorException.java +++ b/src/main/java/im/conversations/android/xmpp/IqErrorException.java @@ -1,5 +1,7 @@ package im.conversations.android.xmpp; +import com.google.common.base.Strings; +import im.conversations.android.xmpp.model.error.Condition; import im.conversations.android.xmpp.model.error.Error; import im.conversations.android.xmpp.model.stanza.Iq; @@ -19,7 +21,12 @@ public class IqErrorException extends Exception { private static String getErrorText(final Iq response) { final var error = response.getError(); final var text = error == null ? null : error.getText(); - return text == null ? null : text.getContent(); + final var textContent = text == null ? null : text.getContent(); + if (Strings.isNullOrEmpty(textContent)) { + final var condition = error == null ? null : error.getExtension(Condition.class); + return condition == null ? null : condition.getName(); + } + return textContent; } public Iq getResponse() { diff --git a/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java b/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java index 56591efae..81a55f18c 100644 --- a/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java +++ b/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java @@ -22,6 +22,12 @@ public class NodeConfiguration implements Map { .put(PERSIST_ITEMS, Boolean.TRUE) .put(ACCESS_MODEL, "open") .build()); + public static final NodeConfiguration PRESENCE = + new NodeConfiguration( + new ImmutableMap.Builder() + .put(PERSIST_ITEMS, Boolean.TRUE) + .put(ACCESS_MODEL, "presence") + .build()); public static final NodeConfiguration WHITELIST_MAX_ITEMS = new NodeConfiguration( new ImmutableMap.Builder() diff --git a/src/main/java/im/conversations/android/xmpp/PreconditionNotMetException.java b/src/main/java/im/conversations/android/xmpp/PreconditionNotMetException.java index 00ac5fe57..c27776677 100644 --- a/src/main/java/im/conversations/android/xmpp/PreconditionNotMetException.java +++ b/src/main/java/im/conversations/android/xmpp/PreconditionNotMetException.java @@ -10,6 +10,7 @@ public class PreconditionNotMetException extends PubSubErrorException { if (this.pubSubError instanceof PubSubError.PreconditionNotMet) { return; } - throw new AssertionError("This exception should only be constructed for PreconditionNotMet errors"); + throw new AssertionError( + "This exception should only be constructed for PreconditionNotMet errors"); } } diff --git a/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java b/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java index 870756878..383cfd9b7 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java @@ -172,7 +172,7 @@ public class AxolotlManager extends AbstractManager { new FutureCallback<>() { @Override public void onSuccess(Void result) { - LOGGER.info("Successfully publish bundle and device ID {}", myDeviceId); + LOGGER.info("Successfully published bundle and device ID {}", myDeviceId); } @Override diff --git a/src/main/java/im/conversations/android/xmpp/manager/PresenceManager.java b/src/main/java/im/conversations/android/xmpp/manager/PresenceManager.java index 5fabb6012..24d7860bc 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/PresenceManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/PresenceManager.java @@ -39,9 +39,6 @@ public class PresenceManager extends AbstractManager { final var presence = new Presence(); presence.addExtension(capabilities); presence.addExtension(legacyCapabilities); - - LOGGER.info(presence.toString()); - connection.sendPresencePacket(presence); } diff --git a/src/main/java/im/conversations/android/xmpp/manager/PubSubManager.java b/src/main/java/im/conversations/android/xmpp/manager/PubSubManager.java index c50205e9c..7fc4f0d00 100644 --- a/src/main/java/im/conversations/android/xmpp/manager/PubSubManager.java +++ b/src/main/java/im/conversations/android/xmpp/manager/PubSubManager.java @@ -15,12 +15,16 @@ import im.conversations.android.xmpp.PreconditionNotMetException; import im.conversations.android.xmpp.PubSubErrorException; import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.data.Data; import im.conversations.android.xmpp.model.pubsub.Items; import im.conversations.android.xmpp.model.pubsub.PubSub; +import im.conversations.android.xmpp.model.pubsub.Publish; 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.Purge; +import im.conversations.android.xmpp.model.pubsub.owner.Configure; +import im.conversations.android.xmpp.model.pubsub.owner.PubSubOwner; import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Message; import java.util.Map; @@ -229,9 +233,9 @@ public class PubSubManager extends AbstractManager { iq.setTo(address); final var pubSub = iq.addExtension(new PubSub()); pubSub.addExtension(PublishOptions.of(nodeConfiguration)); - final var pubSubItemsWrapper = pubSub.addExtension(new PubSub.ItemsWrapper()); - pubSubItemsWrapper.setNode(node); - final var item = pubSubItemsWrapper.addExtension(new PubSub.Item()); + final var publish = pubSub.addExtension(new Publish()); + publish.setNode(node); + final var item = publish.addExtension(new PubSub.Item()); item.setId(itemId); item.addExtension(itemPayload); final ListenableFuture iqFuture = @@ -248,8 +252,45 @@ public class PubSubManager extends AbstractManager { private ListenableFuture reconfigureNode( final Jid address, final String node, final NodeConfiguration nodeConfiguration) { - - return Futures.immediateVoidFuture(); + final Iq iq = new Iq(Iq.Type.GET); + iq.setTo(address); + final var pubSub = iq.addExtension(new PubSubOwner()); + final var configure = pubSub.addExtension(new Configure()); + configure.setNode(node); + return Futures.transformAsync( + connection.sendIqPacket(iq), + result -> { + final var pubSubOwnerResult = result.getExtension(PubSubOwner.class); + final Configure configureResult = + pubSubOwnerResult == null + ? null + : pubSubOwnerResult.getExtension(Configure.class); + if (configureResult == null) { + throw new IllegalStateException( + "No configuration found in configuration request result"); + } + final var data = configureResult.getData(); + return setNodeConfiguration(address, node, data.submit(nodeConfiguration)); + }, + MoreExecutors.directExecutor()); + } + + private ListenableFuture setNodeConfiguration( + final Jid address, final String node, final Data data) { + LOGGER.info("Trying to set node configuration to {}", data.toString()); + final Iq iq = new Iq(Iq.Type.SET); + iq.setTo(address); + final var pubSub = iq.addExtension(new PubSubOwner()); + final var configure = pubSub.addExtension(new Configure()); + configure.setNode(node); + configure.addExtension(data); + return Futures.transform( + connection.sendIqPacket(iq), + result -> { + LOGGER.info("Modified node configuration {} on {}", node, address); + return null; + }, + MoreExecutors.directExecutor()); } private static class PubSubExceptionTransformer diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Data.java b/src/main/java/im/conversations/android/xmpp/model/data/Data.java index 1b281f3e3..39102c099 100644 --- a/src/main/java/im/conversations/android/xmpp/model/data/Data.java +++ b/src/main/java/im/conversations/android/xmpp/model/data/Data.java @@ -11,6 +11,8 @@ import java.util.Map; public class Data extends Extension { private static final String FORM_TYPE = "FORM_TYPE"; + private static final String FIELD_TYPE_HIDDEN = "hidden"; + private static final String FORM_TYPE_SUBMIT = "submit"; public Data() { super(Data.class); @@ -28,35 +30,81 @@ public class Data extends Extension { } private void addField(final String name, final Object value) { + addField(name, value, null); + } + + private void addField(final String name, final Object value, final String type) { 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"); + if (type != null) { + field.setType(type); + } + if (value instanceof Collection) { + for (final Object subValue : (Collection) value) { + if (subValue instanceof String) { + final var valueExtension = field.addExtension(new Value()); + valueExtension.setContent((String) subValue); + } else { + throw new IllegalArgumentException( + String.format( + "%s is not a supported field value", + subValue.getClass().getSimpleName())); + } + } } else { - throw new IllegalArgumentException( - String.format( - "%s is not a supported field value", value.getClass().getSimpleName())); + 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); + this.addField(FORM_TYPE, formType, FIELD_TYPE_HIDDEN); } public static Data of(final String formType, final Map values) { final var data = new Data(); + data.setType(FORM_TYPE_SUBMIT); data.setFormType(formType); for (final Map.Entry entry : values.entrySet()) { data.addField(entry.getKey(), entry.getValue()); } return data; } + + public Data submit(final Map values) { + final String formType = this.getFormType(); + final var submit = new Data(); + submit.setType(FORM_TYPE_SUBMIT); + if (formType != null) { + submit.setFormType(formType); + } + for (final Field existingField : this.getFields()) { + final var fieldName = existingField.getFieldName(); + final Object submittedValue = values.get(fieldName); + if (submittedValue != null) { + submit.addField(fieldName, submittedValue); + } else { + submit.addField(fieldName, existingField.getValues()); + } + } + return submit; + } + + private void setType(final String type) { + this.setAttribute("type", type); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Field.java b/src/main/java/im/conversations/android/xmpp/model/data/Field.java index a362bf9f5..c16bc6309 100644 --- a/src/main/java/im/conversations/android/xmpp/model/data/Field.java +++ b/src/main/java/im/conversations/android/xmpp/model/data/Field.java @@ -23,4 +23,8 @@ public class Field extends Extension { public void setFieldName(String name) { this.setAttribute("var", name); } + + public void setType(String type) { + this.setAttribute("type", type); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java new file mode 100644 index 000000000..7a384f548 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.pubsub; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement +public class Publish extends Extension { + + public Publish() { + super(Publish.class); + } + + public void setNode(String node) { + this.setAttribute("node", node); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java new file mode 100644 index 000000000..53b987f53 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java @@ -0,0 +1,21 @@ +package im.conversations.android.xmpp.model.pubsub.owner; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; +import im.conversations.android.xmpp.model.data.Data; + +@XmlElement +public class Configure extends Extension { + + public Configure() { + super(Configure.class); + } + + public void setNode(final String node) { + this.setAttribute("node", node); + } + + public Data getData() { + return this.getExtension(Data.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java new file mode 100644 index 000000000..c3a61e619 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.pubsub.owner; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "pubsub") +public class PubSubOwner extends Extension { + + public PubSubOwner() { + super(PubSubOwner.class); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java new file mode 100644 index 000000000..6732ee0c0 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java @@ -0,0 +1,5 @@ +@XmlPackage(namespace = Namespace.PUB_SUB_OWNER) +package im.conversations.android.xmpp.model.pubsub.owner; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlPackage;