support mam:1

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/MucOptions.java            | 12 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java          |  4 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |  7 
src/main/java/eu/siacs/conversations/services/MessageArchiveService.java | 80 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  6 
src/main/java/eu/siacs/conversations/xml/Namespace.java                  |  2 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java            | 11 
7 files changed, 93 insertions(+), 29 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/MucOptions.java 🔗

@@ -11,6 +11,7 @@ import java.util.Set;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.services.MessageArchiveService;
 import eu.siacs.conversations.utils.JidHelper;
 import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xml.Namespace;
@@ -66,6 +67,10 @@ public class MucOptions {
 		}
 	}
 
+	public boolean mamSupport() {
+		return MessageArchiveService.Version.has(getFeatures());
+	}
+
 	public enum Affiliation {
 		OWNER("owner", 4, R.string.owner),
 		ADMIN("admin", 3, R.string.admin),
@@ -456,12 +461,9 @@ public class MucOptions {
 		return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, false);
 	}
 
-	public boolean mamSupport() {
-		return hasFeature(Namespace.MAM) || hasFeature(Namespace.MAM_LEGACY);
-	}
 
-	public boolean mamLegacy() {
-		return hasFeature(Namespace.MAM_LEGACY) && !hasFeature(Namespace.MAM);
+	public List<String> getFeatures() {
+		return this.serviceDiscoveryResult != null ? this.serviceDiscoveryResult.features : Collections.emptyList();
 	}
 
 	public boolean nonanonymous() {

src/main/java/eu/siacs/conversations/generator/IqGenerator.java 🔗

@@ -248,10 +248,10 @@ public class IqGenerator extends AbstractGenerator {
 
 	public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
 		final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
-		final Element query = packet.query(mam.isLegacy() ? Namespace.MAM_LEGACY : Namespace.MAM);
+		final Element query = packet.query(mam.version.namespace);
 		query.setAttribute("queryid", mam.getQueryId());
 		final Data data = new Data();
-		data.setFormType(mam.isLegacy() ? Namespace.MAM_LEGACY : Namespace.MAM);
+		data.setFormType(mam.version.namespace);
 		if (mam.muc()) {
 			packet.setTo(mam.getWith());
 		} else if (mam.getWith() != null) {

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -239,16 +239,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 		Long timestamp = null;
 		boolean isCarbon = false;
 		String serverMsgId = null;
-		final Element fin = original.findChild("fin", Namespace.MAM_LEGACY);
+		final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
 		if (fin != null) {
 			mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
 			return;
 		}
-		final boolean mamLegacy = original.hasChild("result", Namespace.MAM_LEGACY);
-		final Element result = original.findChild("result", mamLegacy ? Namespace.MAM_LEGACY : Namespace.MAM);
+		final Element result = MessageArchiveService.Version.findResult(original);
 		final MessageArchiveService.Query query = result == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid"));
 		if (query != null && query.validFrom(original.getFrom())) {
-			Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", mamLegacy ? Namespace.MAM_LEGACY : Namespace.MAM);
+			Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace);
 			if (f == null) {
 				return;
 			}

src/main/java/eu/siacs/conversations/services/MessageArchiveService.java 🔗

@@ -20,6 +20,7 @@ import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
 import eu.siacs.conversations.xmpp.mam.MamReference;
 import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 import rocks.xmpp.addr.Jid;
 
 public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
@@ -29,6 +30,66 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 	private final HashSet<Query> queries = new HashSet<>();
 	private final ArrayList<Query> pendingQueries = new ArrayList<>();
 
+	public enum Version {
+		MAM_0("urn:xmpp:mam:0", true),
+		MAM_1("urn:xmpp:mam:1", false),
+		MAM_2("urn:xmpp:mam:2", false);
+
+		public final boolean legacy;
+		public final String namespace;
+
+		Version(String namespace, boolean legacy) {
+			this.namespace = namespace;
+			this.legacy = legacy;
+		}
+
+		public static Version get(Account account) {
+			return get(account,null);
+		}
+
+		public static Version get(Account account, Conversation conversation) {
+			if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) {
+				return get(account.getXmppConnection().getFeatures().getAccountFeatures());
+			} else {
+				return get(conversation.getMucOptions().getFeatures());
+			}
+		}
+
+		private static Version get(List<String> features) {
+			final Version[] values = values();
+			for(int i = values.length -1; i >= 0; --i) {
+				for(String feature : features) {
+					if (values[i].namespace.equals(feature)) {
+						return values[i];
+					}
+				}
+			}
+			return MAM_0;
+		}
+
+		public static boolean has(List<String> features) {
+			for(String feature : features) {
+				for(Version version : values()) {
+					if (version.namespace.equals(feature)) {
+						return true;
+					}
+				}
+			}
+			return false;
+		}
+
+		public static Element findResult(MessagePacket packet) {
+			for(Version version : values()) {
+				Element result = packet.findChild("result", version.namespace);
+				if (result != null) {
+					return result;
+				}
+			}
+			return null;
+		}
+
+	};
+
 	MessageArchiveService(final XmppConnectionService service) {
 		this.mXmppConnectionService = service;
 	}
@@ -168,7 +229,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 			Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString());
 			IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
 			this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> {
-				Element fin = p.findChild("fin", Namespace.MAM);
+				Element fin = p.findChild("fin", query.version.namespace);
 				if (p.getType() == IqPacket.TYPE.TIMEOUT) {
 					synchronized (MessageArchiveService.this.queries) {
 						MessageArchiveService.this.queries.remove(query);
@@ -390,16 +451,21 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 		private PagingOrder pagingOrder = PagingOrder.NORMAL;
 		private XmppConnectionService.OnMoreMessagesLoaded callback = null;
 		private boolean catchup = true;
+		public final Version version;
 
 
 		Query(Conversation conversation, MamReference start, long end, boolean catchup) {
-			this(conversation.getAccount(), catchup ? start : start.timeOnly(), end);
+			this(conversation.getAccount(), Version.get(conversation.getAccount(), conversation), catchup ? start : start.timeOnly(), end);
 			this.conversation = conversation;
 			this.pagingOrder = catchup ? PagingOrder.NORMAL : PagingOrder.REVERSE;
 			this.catchup = catchup;
 		}
 
 		Query(Account account, MamReference start, long end) {
+			this(account, Version.get(account), start, end);
+		}
+
+		Query(Account account, Version version, MamReference start, long end) {
 			this.account = account;
 			if (start.getReference() != null) {
 				this.reference = start.getReference();
@@ -408,10 +474,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 			}
 			this.end = end;
 			this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32);
+			this.version = version;
 		}
 
 		private Query page(String reference) {
-			Query query = new Query(this.account, new MamReference(this.start, reference), this.end);
+			Query query = new Query(this.account, this.version, new MamReference(this.start, reference), this.end);
 			query.conversation = conversation;
 			query.totalCount = totalCount;
 			query.actualCount = actualCount;
@@ -433,11 +500,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 		}
 
 		public boolean isLegacy() {
-			if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) {
-				return account.getXmppConnection().getFeatures().mamLegacy();
-			} else {
-				return conversation.getMucOptions().mamLegacy();
-			}
+			return version.legacy;
 		}
 
 		public boolean safeToExtractTrueCounterpart() {
@@ -570,6 +633,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 				builder.append(this.reference);
 			}
 			builder.append(", catchup=").append(Boolean.toString(catchup));
+			builder.append(", ns=").append(version.namespace);
 			return builder.toString();
 		}
 

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -3766,11 +3766,11 @@ public class XmppConnectionService extends Service {
 	}
 
 	public void fetchMamPreferences(Account account, final OnMamPreferencesFetched callback) {
-		final boolean legacy = account.getXmppConnection().getFeatures().mamLegacy();
+		final MessageArchiveService.Version version = MessageArchiveService.Version.get(account);
 		IqPacket request = new IqPacket(IqPacket.TYPE.GET);
-		request.addChild("prefs", legacy ? Namespace.MAM_LEGACY : Namespace.MAM);
+		request.addChild("prefs", version.namespace);
 		sendIqPacket(account, request, (account1, packet) -> {
-			Element prefs = packet.findChild("prefs", legacy ? Namespace.MAM_LEGACY : Namespace.MAM);
+			Element prefs = packet.findChild("prefs", version.namespace);
 			if (packet.getType() == IqPacket.TYPE.RESULT && prefs != null) {
 				callback.onPreferencesFetched(prefs);
 			} else {

src/main/java/eu/siacs/conversations/xml/Namespace.java 🔗

@@ -8,8 +8,6 @@ public final class Namespace {
 	public static final String HTTP_UPLOAD = "urn:xmpp:http:upload:0";
 	public static final String HTTP_UPLOAD_LEGACY = "urn:xmpp:http:upload";
 	public static final String STANZA_IDS = "urn:xmpp:sid:0";
-	public static final String MAM = "urn:xmpp:mam:2";
-	public static final String MAM_LEGACY = "urn:xmpp:mam:0";
 	public static final String IDLE = "urn:xmpp:idle:1";
 	public static final String DATA = "jabber:x:data";
 	public static final String OOB = "jabber:x:oob";

src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java 🔗

@@ -31,6 +31,7 @@ import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -70,6 +71,7 @@ import eu.siacs.conversations.entities.ServiceDiscoveryResult;
 import eu.siacs.conversations.generator.IqGenerator;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.services.MemorizingTrustManager;
+import eu.siacs.conversations.services.MessageArchiveService;
 import eu.siacs.conversations.services.NotificationService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.utils.CryptoHelper;
@@ -1786,13 +1788,12 @@ public class XmppConnection implements Runnable {
 		}
 
 		public boolean mam() {
-			return hasDiscoFeature(account.getJid().asBareJid(), Namespace.MAM)
-					|| hasDiscoFeature(account.getJid().asBareJid(), Namespace.MAM_LEGACY);
+			return MessageArchiveService.Version.has(getAccountFeatures());
 		}
 
-		public boolean mamLegacy() {
-			return !hasDiscoFeature(account.getJid().asBareJid(), Namespace.MAM)
-					&& hasDiscoFeature(account.getJid().asBareJid(), Namespace.MAM_LEGACY);
+		public List<String> getAccountFeatures() {
+			ServiceDiscoveryResult result = connection.disco.get(account.getJid().asBareJid());
+			return result == null ? Collections.emptyList() : result.getFeatures();
 		}
 
 		public boolean push() {