reconfigure node when precondition is not met

This commit is contained in:
Daniel Gultsch 2023-02-06 15:29:28 +01:00
parent 58b1e26367
commit 3be56b6775
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
13 changed files with 181 additions and 23 deletions

View file

@ -45,7 +45,7 @@ public abstract class AxolotlDao {
@Query( @Query(
"SELECT EXISTS(SELECT deviceId FROM axolotl_device_list JOIN axolotl_device_list_item" "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)") + " accountId=:account AND address=:address AND deviceId=:deviceId)")
public abstract boolean hasDeviceId(final long account, final Jid address, final int deviceId); public abstract boolean hasDeviceId(final long account, final Jid address, final int deviceId);

View file

@ -1,5 +1,7 @@
package im.conversations.android.xmpp; 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.error.Error;
import im.conversations.android.xmpp.model.stanza.Iq; import im.conversations.android.xmpp.model.stanza.Iq;
@ -19,7 +21,12 @@ public class IqErrorException extends Exception {
private static String getErrorText(final Iq response) { private static String getErrorText(final Iq response) {
final var error = response.getError(); final var error = response.getError();
final var text = error == null ? null : error.getText(); 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() { public Iq getResponse() {

View file

@ -22,6 +22,12 @@ public class NodeConfiguration implements Map<String, Object> {
.put(PERSIST_ITEMS, Boolean.TRUE) .put(PERSIST_ITEMS, Boolean.TRUE)
.put(ACCESS_MODEL, "open") .put(ACCESS_MODEL, "open")
.build()); .build());
public static final NodeConfiguration PRESENCE =
new NodeConfiguration(
new ImmutableMap.Builder<String, Object>()
.put(PERSIST_ITEMS, Boolean.TRUE)
.put(ACCESS_MODEL, "presence")
.build());
public static final NodeConfiguration WHITELIST_MAX_ITEMS = public static final NodeConfiguration WHITELIST_MAX_ITEMS =
new NodeConfiguration( new NodeConfiguration(
new ImmutableMap.Builder<String, Object>() new ImmutableMap.Builder<String, Object>()

View file

@ -10,6 +10,7 @@ public class PreconditionNotMetException extends PubSubErrorException {
if (this.pubSubError instanceof PubSubError.PreconditionNotMet) { if (this.pubSubError instanceof PubSubError.PreconditionNotMet) {
return; 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");
} }
} }

View file

@ -172,7 +172,7 @@ public class AxolotlManager extends AbstractManager {
new FutureCallback<>() { new FutureCallback<>() {
@Override @Override
public void onSuccess(Void result) { public void onSuccess(Void result) {
LOGGER.info("Successfully publish bundle and device ID {}", myDeviceId); LOGGER.info("Successfully published bundle and device ID {}", myDeviceId);
} }
@Override @Override

View file

@ -39,9 +39,6 @@ public class PresenceManager extends AbstractManager {
final var presence = new Presence(); final var presence = new Presence();
presence.addExtension(capabilities); presence.addExtension(capabilities);
presence.addExtension(legacyCapabilities); presence.addExtension(legacyCapabilities);
LOGGER.info(presence.toString());
connection.sendPresencePacket(presence); connection.sendPresencePacket(presence);
} }

View file

@ -15,12 +15,16 @@ import im.conversations.android.xmpp.PreconditionNotMetException;
import im.conversations.android.xmpp.PubSubErrorException; 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.data.Data;
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.Publish;
import im.conversations.android.xmpp.model.pubsub.PublishOptions; import im.conversations.android.xmpp.model.pubsub.PublishOptions;
import im.conversations.android.xmpp.model.pubsub.error.PubSubError; 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.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.Iq;
import im.conversations.android.xmpp.model.stanza.Message; import im.conversations.android.xmpp.model.stanza.Message;
import java.util.Map; import java.util.Map;
@ -229,9 +233,9 @@ public class PubSubManager extends AbstractManager {
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)); pubSub.addExtension(PublishOptions.of(nodeConfiguration));
final var pubSubItemsWrapper = pubSub.addExtension(new PubSub.ItemsWrapper()); final var publish = pubSub.addExtension(new Publish());
pubSubItemsWrapper.setNode(node); publish.setNode(node);
final var item = pubSubItemsWrapper.addExtension(new PubSub.Item()); final var item = publish.addExtension(new PubSub.Item());
item.setId(itemId); item.setId(itemId);
item.addExtension(itemPayload); item.addExtension(itemPayload);
final ListenableFuture<Void> iqFuture = final ListenableFuture<Void> iqFuture =
@ -248,8 +252,45 @@ public class PubSubManager extends AbstractManager {
private ListenableFuture<Void> reconfigureNode( private ListenableFuture<Void> reconfigureNode(
final Jid address, final String node, final NodeConfiguration nodeConfiguration) { final Jid address, final String node, final NodeConfiguration nodeConfiguration) {
final Iq iq = new Iq(Iq.Type.GET);
return Futures.immediateVoidFuture(); 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<Void> 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<V> private static class PubSubExceptionTransformer<V>

View file

@ -11,6 +11,8 @@ import java.util.Map;
public class Data extends Extension { public class Data extends Extension {
private static final String FORM_TYPE = "FORM_TYPE"; 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() { public Data() {
super(Data.class); super(Data.class);
@ -28,35 +30,81 @@ public class Data extends Extension {
} }
private void addField(final String name, final Object value) { 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) { if (value == null) {
throw new IllegalArgumentException("Null values are not supported on data fields"); throw new IllegalArgumentException("Null values are not supported on data fields");
} }
final var field = this.addExtension(new Field()); final var field = this.addExtension(new Field());
field.setFieldName(name); field.setFieldName(name);
final var valueExtension = field.addExtension(new Value()); if (type != null) {
if (value instanceof String) { field.setType(type);
valueExtension.setContent((String) value); }
} else if (value instanceof Integer) { if (value instanceof Collection) {
valueExtension.setContent(String.valueOf(value)); for (final Object subValue : (Collection<?>) value) {
} else if (value instanceof Boolean) { if (subValue instanceof String) {
valueExtension.setContent(Boolean.TRUE.equals(value) ? "true" : "false"); 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 { } else {
throw new IllegalArgumentException( final var valueExtension = field.addExtension(new Value());
String.format( if (value instanceof String) {
"%s is not a supported field value", value.getClass().getSimpleName())); 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) { 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<String, Object> values) { public static Data of(final String formType, final Map<String, Object> values) {
final var data = new Data(); final var data = new Data();
data.setType(FORM_TYPE_SUBMIT);
data.setFormType(formType); data.setFormType(formType);
for (final Map.Entry<String, Object> entry : values.entrySet()) { for (final Map.Entry<String, Object> entry : values.entrySet()) {
data.addField(entry.getKey(), entry.getValue()); data.addField(entry.getKey(), entry.getValue());
} }
return data; return data;
} }
public Data submit(final Map<String, Object> 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);
}
} }

View file

@ -23,4 +23,8 @@ public class Field extends Extension {
public void setFieldName(String name) { public void setFieldName(String name) {
this.setAttribute("var", name); this.setAttribute("var", name);
} }
public void setType(String type) {
this.setAttribute("type", type);
}
} }

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;