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(
"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);

View file

@ -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() {

View file

@ -22,6 +22,12 @@ public class NodeConfiguration implements Map<String, Object> {
.put(PERSIST_ITEMS, Boolean.TRUE)
.put(ACCESS_MODEL, "open")
.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 =
new NodeConfiguration(
new ImmutableMap.Builder<String, Object>()

View file

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

View file

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

View file

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

View file

@ -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<Void> iqFuture =
@ -248,8 +252,45 @@ public class PubSubManager extends AbstractManager {
private ListenableFuture<Void> reconfigureNode(
final Jid address, final String node, final NodeConfiguration nodeConfiguration) {
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());
}
return Futures.immediateVoidFuture();
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>

View file

@ -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<String, Object> values) {
final var data = new Data();
data.setType(FORM_TYPE_SUBMIT);
data.setFormType(formType);
for (final Map.Entry<String, Object> entry : values.entrySet()) {
data.addField(entry.getKey(), entry.getValue());
}
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) {
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;