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.xml.Element;
13import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
14import eu.siacs.conversations.xmpp.OnIqPacketReceived;
15import eu.siacs.conversations.xmpp.jid.Jid;
16import eu.siacs.conversations.xmpp.stanzas.IqPacket;
17
18public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
19
20 private final XmppConnectionService mXmppConnectionService;
21
22 private final HashSet<Query> queries = new HashSet<Query>();
23
24 public MessageArchiveService(final XmppConnectionService service) {
25 this.mXmppConnectionService = service;
26 }
27
28 public void query(final Conversation conversation) {
29 synchronized (this.queries) {
30 final Account account = conversation.getAccount();
31 long start = conversation.getLastMessageTransmitted();
32 long end = account.getXmppConnection().getLastSessionEstablished();
33 if (end - start >= Config.MAX_HISTORY_AGE) {
34 start = end - Config.MAX_HISTORY_AGE;
35 }
36 final Query query = new Query(conversation, start, end);
37 this.queries.add(query);
38 IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
39 this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
40 @Override
41 public void onIqPacketReceived(Account account, IqPacket packet) {
42 if (packet.getType() == IqPacket.TYPE_ERROR) {
43 finalizeQuery(query);
44 }
45 }
46 });
47 }
48 }
49
50 private void finalizeQuery(Query query) {
51 synchronized (this.queries) {
52 this.queries.remove(query);
53 }
54 final Conversation conversation = query.getConversation();
55 conversation.sort();
56 if (conversation.setLastMessageTransmitted(query.getEnd())) {
57 this.mXmppConnectionService.databaseBackend.updateConversation(conversation);
58 }
59 this.mXmppConnectionService.updateConversationUi();
60 }
61
62 public void processFin(Element fin) {
63 if (fin == null) {
64 return;
65 }
66 Query query = findQuery(fin.getAttribute("queryid"));
67 if (query == null) {
68 return;
69 }
70 boolean complete = fin.getAttributeAsBoolean("complete");
71 Element set = fin.findChild("set","http://jabber.org/protocol/rsm");
72 Element last = set == null ? null : set.findChild("last");
73 if (complete || last == null) {
74 final Account account = query.getConversation().getAccount();
75 Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": completed mam query for "+query.getWith().toString());
76 this.finalizeQuery(query);
77 } else {
78 final Query nextQuery = query.next(last == null ? null : last.getContent());
79 IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(nextQuery);
80 synchronized (this.queries) {
81 this.queries.remove(query);
82 this.queries.add(nextQuery);
83 }
84 this.mXmppConnectionService.sendIqPacket(query.getConversation().getAccount(),packet,new OnIqPacketReceived() {
85 @Override
86 public void onIqPacketReceived(Account account, IqPacket packet) {
87 if (packet.getType() == IqPacket.TYPE_ERROR) {
88 finalizeQuery(nextQuery);
89 }
90 }
91 });
92 }
93 }
94
95 private Query findQuery(String id) {
96 if (id == null) {
97 return null;
98 }
99 synchronized (this.queries) {
100 for(Query query : this.queries) {
101 if (query.getQueryId().equals(id)) {
102 return query;
103 }
104 }
105 return null;
106 }
107 }
108
109 @Override
110 public void onAdvancedStreamFeaturesAvailable(Account account) {
111 if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) {
112 List<Conversation> conversations = mXmppConnectionService.getConversations();
113 for (Conversation conversation : conversations) {
114 if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account) {
115 this.query(conversation);
116 }
117 }
118 } else {
119 Log.d(Config.LOGTAG,"no mam available");
120 }
121 }
122
123 public class Query {
124 private long start;
125 private long end;
126 private Jid with;
127 private String queryId;
128 private String after = null;
129 private Conversation conversation;
130
131 public Query(Conversation conversation, long start, long end) {
132 this.conversation = conversation;
133 this.with = conversation.getContactJid().toBareJid();
134 this.start = start;
135 this.end = end;
136 this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32);
137 }
138
139 public Query next(String after) {
140 Query query = new Query(this.conversation,this.start,this.end);
141 query.after = after;
142 return query;
143 }
144
145 public String getAfter() {
146 return after;
147 }
148
149 public String getQueryId() {
150 return queryId;
151 }
152
153 public Jid getWith() {
154 return with;
155 }
156
157 public long getStart() {
158 return start;
159 }
160
161 public long getEnd() {
162 return end;
163 }
164
165 public Conversation getConversation() {
166 return conversation;
167 }
168 }
169}