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.getLastMessageReceived();
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 query.getConversation().sort();
55 this.mXmppConnectionService.updateConversationUi();
56 }
57
58 public void processFin(Element fin) {
59 if (fin == null) {
60 return;
61 }
62 Query query = findQuery(fin.getAttribute("queryid"));
63 if (query == null) {
64 return;
65 }
66 boolean complete = fin.getAttributeAsBoolean("complete");
67 Element set = fin.findChild("set","http://jabber.org/protocol/rsm");
68 Element last = set == null ? null : set.findChild("last");
69 if (complete || last == null) {
70 final Account account = query.getConversation().getAccount();
71 Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": completed mam query for "+query.getWith().toString());
72 this.finalizeQuery(query);
73 } else {
74 final Query nextQuery = query.next(last == null ? null : last.getContent());
75 IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(nextQuery);
76 synchronized (this.queries) {
77 this.queries.remove(query);
78 this.queries.add(nextQuery);
79 }
80 this.mXmppConnectionService.sendIqPacket(query.getConversation().getAccount(),packet,new OnIqPacketReceived() {
81 @Override
82 public void onIqPacketReceived(Account account, IqPacket packet) {
83 if (packet.getType() == IqPacket.TYPE_ERROR) {
84 finalizeQuery(nextQuery);
85 }
86 }
87 });
88 }
89 }
90
91 private Query findQuery(String id) {
92 if (id == null) {
93 return null;
94 }
95 synchronized (this.queries) {
96 for(Query query : this.queries) {
97 if (query.getQueryId().equals(id)) {
98 return query;
99 }
100 }
101 return null;
102 }
103 }
104
105 @Override
106 public void onAdvancedStreamFeaturesAvailable(Account account) {
107 if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) {
108 List<Conversation> conversations = mXmppConnectionService.getConversations();
109 for (Conversation conversation : conversations) {
110 if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account) {
111 this.query(conversation);
112 }
113 }
114 } else {
115 Log.d(Config.LOGTAG,"no mam available");
116 }
117 }
118
119 public class Query {
120 private long start;
121 private long end;
122 private Jid with;
123 private String queryId;
124 private String after = null;
125 private Conversation conversation;
126
127 public Query(Conversation conversation, long start, long end) {
128 this.conversation = conversation;
129 this.with = conversation.getContactJid().toBareJid();
130 this.start = start;
131 this.end = end;
132 this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32);
133 }
134
135 public Query next(String after) {
136 Query query = new Query(this.conversation,this.start,this.end);
137 query.after = after;
138 return query;
139 }
140
141 public String getAfter() {
142 return after;
143 }
144
145 public String getQueryId() {
146 return queryId;
147 }
148
149 public Jid getWith() {
150 return with;
151 }
152
153 public long getStart() {
154 return start;
155 }
156
157 public long getEnd() {
158 return end;
159 }
160
161 public Conversation getConversation() {
162 return conversation;
163 }
164 }
165}