MessageArchiveService.java

  1package eu.siacs.conversations.services;
  2
  3import android.util.Log;
  4
  5import java.math.BigInteger;
  6import java.util.HashSet;
  7import java.util.List;
  8
  9import eu.siacs.conversations.Config;
 10import eu.siacs.conversations.entities.Account;
 11import eu.siacs.conversations.entities.Conversation;
 12import eu.siacs.conversations.generator.AbstractGenerator;
 13import eu.siacs.conversations.parser.AbstractParser;
 14import eu.siacs.conversations.xml.Element;
 15import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
 16import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 17import eu.siacs.conversations.xmpp.jid.Jid;
 18import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 19
 20public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
 21
 22	private final XmppConnectionService mXmppConnectionService;
 23
 24	private final HashSet<Query> queries = new HashSet<Query>();
 25
 26	public enum PagingOrder {
 27		NORMAL,
 28		REVERSE
 29	};
 30
 31	public MessageArchiveService(final XmppConnectionService service) {
 32		this.mXmppConnectionService = service;
 33	}
 34
 35	public void catchup(final Account account) {
 36		long startCatchup = getLastMessageTransmitted(account);
 37		long endCatchup = account.getXmppConnection().getLastSessionEstablished();
 38		if (startCatchup == 0) {
 39			return;
 40		} else if (endCatchup - startCatchup >= Config.MAX_CATCHUP) {
 41			startCatchup = endCatchup - Config.MAX_CATCHUP;
 42			List<Conversation> conversations = mXmppConnectionService.getConversations();
 43			for (Conversation conversation : conversations) {
 44				if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted()) {
 45					this.query(conversation,startCatchup);
 46				}
 47			}
 48		}
 49		final Query query = new Query(account, startCatchup, endCatchup);
 50		this.queries.add(query);
 51		this.execute(query);
 52	}
 53
 54	private long getLastMessageTransmitted(final Account account) {
 55		long timestamp = 0;
 56		for(final Conversation conversation : mXmppConnectionService.getConversations()) {
 57			if (conversation.getAccount() == account) {
 58				long tmp = conversation.getLastMessageTransmitted();
 59				if (tmp > timestamp) {
 60					timestamp = tmp;
 61				}
 62			}
 63		}
 64		return timestamp;
 65	}
 66
 67	public void query(final Conversation conversation) {
 68		query(conversation,conversation.getAccount().getXmppConnection().getLastSessionEstablished());
 69	}
 70
 71	public void query(final Conversation conversation, long end) {
 72		synchronized (this.queries) {
 73			final Account account = conversation.getAccount();
 74			long start = conversation.getLastMessageTransmitted();
 75			if (start > end) {
 76				return;
 77			} else if (end - start >= Config.MAX_HISTORY_AGE) {
 78				start = end - Config.MAX_HISTORY_AGE;
 79			}
 80			final Query query = new Query(conversation, start, end,PagingOrder.REVERSE);
 81			this.queries.add(query);
 82			this.execute(query);
 83		}
 84	}
 85
 86	private void execute(final Query query) {
 87		Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid().toString()+": running mam query "+query.toString());
 88		IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
 89			this.mXmppConnectionService.sendIqPacket(query.getAccount(), packet, new OnIqPacketReceived() {
 90				@Override
 91				public void onIqPacketReceived(Account account, IqPacket packet) {
 92					if (packet.getType() == IqPacket.TYPE_ERROR) {
 93						Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": error executing mam: "+packet.toString());
 94						finalizeQuery(query);
 95					}
 96				}
 97			});
 98	}
 99
100	private void finalizeQuery(Query query) {
101		synchronized (this.queries) {
102			this.queries.remove(query);
103		}
104		final Conversation conversation = query.getConversation();
105		if (conversation != null) {
106			conversation.sort();
107			if (conversation.setLastMessageTransmitted(query.getEnd())) {
108				this.mXmppConnectionService.databaseBackend.updateConversation(conversation);
109			}
110			this.mXmppConnectionService.updateConversationUi();
111		} else {
112			for(Conversation tmp : this.mXmppConnectionService.getConversations()) {
113				if (tmp.getAccount() == query.getAccount()) {
114					tmp.sort();
115					if (tmp.setLastMessageTransmitted(query.getEnd())) {
116						this.mXmppConnectionService.databaseBackend.updateConversation(tmp);
117					}
118				}
119			}
120		}
121	}
122
123	public boolean queryInProgress(Conversation conversation) {
124		synchronized (this.queries) {
125			for(Query query : queries) {
126				if (query.conversation == conversation) {
127					return true;
128				}
129			}
130			return false;
131		}
132	}
133
134	public void processFin(Element fin) {
135		if (fin == null) {
136			return;
137		}
138		Query query = findQuery(fin.getAttribute("queryid"));
139		if (query == null) {
140			return;
141		}
142		boolean complete = fin.getAttributeAsBoolean("complete");
143		Element set = fin.findChild("set","http://jabber.org/protocol/rsm");
144		Element last = set == null ? null : set.findChild("last");
145		Element first = set == null ? null : set.findChild("first");
146		Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first;
147		if (complete || relevant == null) {
148			this.finalizeQuery(query);
149		} else {
150			final Query nextQuery;
151			if (query.getPagingOrder() == PagingOrder.NORMAL) {
152				nextQuery = query.next(last == null ? null : last.getContent());
153			} else {
154				nextQuery = query.prev(first == null ? null : first.getContent());
155			}
156			this.execute(nextQuery);
157			this.finalizeQuery(query);
158			synchronized (this.queries) {
159				this.queries.remove(query);
160				this.queries.add(nextQuery);
161			}
162		}
163	}
164
165	public Query findQuery(String id) {
166		if (id == null) {
167			return null;
168		}
169		synchronized (this.queries) {
170			for(Query query : this.queries) {
171				if (query.getQueryId().equals(id)) {
172					return query;
173				}
174			}
175			return null;
176		}
177	}
178
179	@Override
180	public void onAdvancedStreamFeaturesAvailable(Account account) {
181		if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) {
182			this.catchup(account);
183		}
184	}
185
186	public class Query {
187		private long start;
188		private long end;
189		private Jid with = null;
190		private String queryId;
191		private String reference = null;
192		private Account account;
193		private Conversation conversation;
194		private PagingOrder pagingOrder = PagingOrder.NORMAL;
195
196
197		public Query(Conversation conversation, long start, long end) {
198			this(conversation.getAccount(), start, end);
199			this.conversation = conversation;
200			this.with = conversation.getContactJid().toBareJid();
201		}
202
203		public Query(Conversation conversation, long start, long end, PagingOrder order) {
204			this(conversation,start,end);
205			this.pagingOrder = order;
206		}
207
208		public Query(Account account, long start, long end) {
209			this.account = account;
210			this.start = start;
211			this.end = end;
212			this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32);
213		}
214
215		private Query page(String reference) {
216			Query query = new Query(this.account,this.start,this.end);
217			query.reference = reference;
218			query.conversation = conversation;
219			query.with = with;
220			return query;
221		}
222
223		public Query next(String reference) {
224			Query query = page(reference);
225			query.pagingOrder = PagingOrder.NORMAL;
226			return query;
227		}
228
229		public Query prev(String reference) {
230			Query query = page(reference);
231			query.pagingOrder = PagingOrder.REVERSE;
232			return query;
233		}
234
235		public String getReference() {
236			return reference;
237		}
238
239		public PagingOrder getPagingOrder() {
240			return this.pagingOrder;
241		}
242
243		public String getQueryId() {
244			return queryId;
245		}
246
247		public Jid getWith() {
248			return with;
249		}
250
251		public long getStart() {
252			return start;
253		}
254
255		public long getEnd() {
256			return end;
257		}
258
259		public Conversation getConversation() {
260			return conversation;
261		}
262
263		public Account getAccount() {
264			return this.account;
265		}
266
267		@Override
268		public String toString() {
269			StringBuilder builder = new StringBuilder();
270			builder.append("with=");
271			if (this.with==null) {
272				builder.append("*");
273			} else {
274				builder.append(with.toString());
275			}
276			builder.append(", start=");
277			builder.append(AbstractGenerator.getTimestamp(this.start));
278			builder.append(", end=");
279			builder.append(AbstractGenerator.getTimestamp(this.end));
280			if (this.reference!=null) {
281				if (this.pagingOrder == PagingOrder.NORMAL) {
282					builder.append(", after=");
283				} else {
284					builder.append(", before=");
285				}
286				builder.append(this.reference);
287			}
288			return builder.toString();
289		}
290	}
291}