diff options
Diffstat (limited to 'src/main/java/eu/siacs/conversations/services')
-rw-r--r-- | src/main/java/eu/siacs/conversations/services/MessageArchiveService.java | 240 | ||||
-rw-r--r-- | src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 63 |
2 files changed, 272 insertions, 31 deletions
diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java new file mode 100644 index 00000000..3b84d411 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -0,0 +1,240 @@ +package eu.siacs.conversations.services; + +import android.util.Log; + +import java.math.BigInteger; +import java.util.HashSet; +import java.util.List; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.generator.AbstractGenerator; +import eu.siacs.conversations.parser.AbstractParser; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { + + private final XmppConnectionService mXmppConnectionService; + + private final HashSet<Query> queries = new HashSet<Query>(); + + public MessageArchiveService(final XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public void catchup(final Account account) { + long startCatchup = getLastMessageTransmitted(account); + long endCatchup = account.getXmppConnection().getLastSessionEstablished(); + if (startCatchup == 0) { + return; + } else if (endCatchup - startCatchup >= Config.MAX_CATCHUP) { + startCatchup = endCatchup - Config.MAX_CATCHUP; + List<Conversation> conversations = mXmppConnectionService.getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted()) { + this.query(conversation,startCatchup); + } + } + } + final Query query = new Query(account, startCatchup, endCatchup); + this.queries.add(query); + this.execute(query); + } + + private long getLastMessageTransmitted(final Account account) { + long timestamp = 0; + for(final Conversation conversation : mXmppConnectionService.getConversations()) { + if (conversation.getAccount() == account) { + long tmp = conversation.getLastMessageTransmitted(); + if (tmp > timestamp) { + timestamp = tmp; + } + } + } + return timestamp; + } + + public void query(final Conversation conversation) { + query(conversation,conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + } + + public void query(final Conversation conversation, long end) { + synchronized (this.queries) { + final Account account = conversation.getAccount(); + long start = conversation.getLastMessageTransmitted(); + if (start > end) { + return; + } else if (end - start >= Config.MAX_HISTORY_AGE) { + start = end - Config.MAX_HISTORY_AGE; + } + final Query query = new Query(conversation, start, end); + this.queries.add(query); + this.execute(query); + } + } + + private void execute(final Query query) { + Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid().toString()+": running mam query "+query.toString()); + IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket(query.getAccount(), packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE_ERROR) { + Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": error executing mam: "+packet.toString()); + finalizeQuery(query); + } + } + }); + } + + private void finalizeQuery(Query query) { + synchronized (this.queries) { + this.queries.remove(query); + } + final Conversation conversation = query.getConversation(); + if (conversation != null) { + conversation.sort(); + if (conversation.setLastMessageTransmitted(query.getEnd())) { + this.mXmppConnectionService.databaseBackend.updateConversation(conversation); + } + this.mXmppConnectionService.updateConversationUi(); + } else { + for(Conversation tmp : this.mXmppConnectionService.getConversations()) { + if (tmp.getAccount() == query.getAccount()) { + tmp.sort(); + if (tmp.setLastMessageTransmitted(query.getEnd())) { + this.mXmppConnectionService.databaseBackend.updateConversation(tmp); + } + } + } + } + } + + public void processFin(Element fin) { + if (fin == null) { + return; + } + Query query = findQuery(fin.getAttribute("queryid")); + if (query == null) { + return; + } + boolean complete = fin.getAttributeAsBoolean("complete"); + Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); + Element last = set == null ? null : set.findChild("last"); + if (complete || last == null) { + this.finalizeQuery(query); + } else { + final Query nextQuery = query.next(last == null ? null : last.getContent()); + this.execute(nextQuery); + synchronized (this.queries) { + this.queries.remove(query); + this.queries.add(nextQuery); + } + } + } + + public Query findQuery(String id) { + if (id == null) { + return null; + } + synchronized (this.queries) { + for(Query query : this.queries) { + if (query.getQueryId().equals(id)) { + return query; + } + } + return null; + } + } + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + this.catchup(account); + } + } + + public class Query { + private long start; + private long end; + private Jid with = null; + private String queryId; + private String after = null; + private Account account; + private Conversation conversation; + + public Query(Conversation conversation, long start, long end) { + this(conversation.getAccount(), start, end); + this.conversation = conversation; + this.with = conversation.getContactJid().toBareJid(); + } + + public Query(Account account, long start, long end) { + this.account = account; + this.start = start; + this.end = end; + this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); + } + + public Query next(String after) { + Query query = new Query(this.account,this.start,this.end); + query.after = after; + query.conversation = conversation; + query.with = with; + return query; + } + + public String getAfter() { + return after; + } + + public String getQueryId() { + return queryId; + } + + public Jid getWith() { + return with; + } + + public long getStart() { + return start; + } + + public long getEnd() { + return end; + } + + public Conversation getConversation() { + return conversation; + } + + public Account getAccount() { + return this.account; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("with="); + if (this.with==null) { + builder.append("*"); + } else { + builder.append(with.toString()); + } + builder.append(", start="); + builder.append(AbstractGenerator.getTimestamp(this.start)); + builder.append(", end="); + builder.append(AbstractGenerator.getTimestamp(this.end)); + if (this.after!=null) { + builder.append(", after="); + builder.append(this.after); + } + return builder.toString(); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 2d5d0154..16a92d4f 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -32,20 +32,14 @@ import net.java.otr4j.session.SessionStatus; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.OutputStream; import java.math.BigInteger; import java.security.SecureRandom; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Locale; -import java.util.TimeZone; import java.util.concurrent.CopyOnWriteArrayList; import de.duenndns.ssl.MemorizingTrustManager; @@ -147,6 +141,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( this); private AvatarService mAvatarService = new AvatarService(this); + private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); private OnConversationUpdate mOnConversationUpdate = null; private Integer convChangedListenerCount = 0; private OnAccountUpdate mOnAccountUpdate = null; @@ -209,6 +204,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa getNotificationService().updateErrorNotification(); } }; + private int accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; private int rosterChangedListenerCount = 0; @@ -260,15 +256,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onMessageAcknowledged(Account account, String uuid) { - for (Conversation conversation : getConversations()) { + for (final Conversation conversation : getConversations()) { if (conversation.getAccount() == account) { - for (Message message : conversation.getMessages()) { - if ((message.getStatus() == Message.STATUS_UNSEND || message - .getStatus() == Message.STATUS_WAITING) - && message.getUuid().equals(uuid)) { + for (final Message message : conversation.getMessages()) { + final int s = message.getStatus(); + if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) { markMessage(message, Message.STATUS_SEND); + if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { + databaseBackend.updateConversation(conversation); + } return; - } + } } } } @@ -590,8 +588,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); connection.setOnJinglePacketReceivedListener(this.jingleListener); connection.setOnBindListener(this.mOnBindListener); - connection - .setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); + connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); + connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); return connection; } @@ -995,8 +993,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return null; } - public Conversation findOrCreateConversation(final Account account, final Jid jid, - final boolean muc) { + public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc) { + return this.findOrCreateConversation(account,jid,muc,null); + } + + public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc, final MessageArchiveService.Query query) { synchronized (this.conversations) { Conversation conversation = find(account, jid); if (conversation != null) { @@ -1030,6 +1031,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } this.databaseBackend.createConversation(conversation); } + if (query == null) { + this.mMessageArchiveService.query(conversation); + } else { + if (query.getConversation() == null) { + this.mMessageArchiveService.query(conversation,query.getStart()); + } + } this.conversations.add(conversation); updateConversationUi(); return conversation; @@ -1256,27 +1264,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa PresencePacket packet = new PresencePacket(); packet.setFrom(conversation.getAccount().getJid()); packet.setTo(joinJid); - Element x = new Element("x"); - x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + Element x = packet.addChild("x","http://jabber.org/protocol/muc"); if (conversation.getMucOptions().getPassword() != null) { - Element password = x.addChild("password"); - password.setContent(conversation.getMucOptions().getPassword()); + x.addChild("password").setContent(conversation.getMucOptions().getPassword()); } + x.addChild("history").setAttribute("since",PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); String sig = account.getPgpSignature(); if (sig != null) { packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } - if (conversation.getMessages().size() != 0) { - final SimpleDateFormat mDateFormat = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); - mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - Date date = new Date(conversation.getLatestMessage() - .getTimeSent() + 1000); - x.addChild("history").setAttribute("since", - mDateFormat.format(date)); - } - packet.addChild(x); sendPresencePacket(account, packet); if (!joinJid.equals(conversation.getContactJid())) { conversation.setContactJid(joinJid); @@ -1320,7 +1317,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa @Override public void onFailure() { - callback.error(R.string.nick_in_use,conversation); + callback.error(R.string.nick_in_use, conversation); } }); @@ -2054,6 +2051,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return this.mJingleConnectionManager; } + public MessageArchiveService getMessageArchiveService() { + return this.mMessageArchiveService; + } + public List<Contact> findContacts(Jid jid) { ArrayList<Contact> contacts = new ArrayList<>(); for (Account account : getAccounts()) { |