2014-12-04 19:54:16 -05:00
package eu.siacs.conversations.services ;
import android.util.Log ;
2015-12-11 07:52:04 -05:00
import android.util.Pair ;
2014-12-04 19:54:16 -05:00
import java.math.BigInteger ;
2014-12-14 17:23:32 -05:00
import java.util.ArrayList ;
2014-12-04 19:54:16 -05:00
import java.util.HashSet ;
2014-12-14 17:23:32 -05:00
import java.util.Iterator ;
2014-12-08 15:59:14 -05:00
import java.util.List ;
2014-12-04 19:54:16 -05:00
import eu.siacs.conversations.Config ;
2014-12-17 04:50:51 -05:00
import eu.siacs.conversations.R ;
2014-12-04 19:54:16 -05:00
import eu.siacs.conversations.entities.Account ;
import eu.siacs.conversations.entities.Conversation ;
2014-12-13 06:25:52 -05:00
import eu.siacs.conversations.generator.AbstractGenerator ;
2014-12-04 19:54:16 -05:00
import eu.siacs.conversations.xml.Element ;
2014-12-08 15:59:14 -05:00
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded ;
2014-12-04 19:54:16 -05:00
import eu.siacs.conversations.xmpp.OnIqPacketReceived ;
import eu.siacs.conversations.xmpp.jid.Jid ;
import eu.siacs.conversations.xmpp.stanzas.IqPacket ;
2014-12-08 15:59:14 -05:00
public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
2014-12-04 19:54:16 -05:00
private final XmppConnectionService mXmppConnectionService ;
2016-02-04 08:39:16 -05:00
private final HashSet < Query > queries = new HashSet < > ( ) ;
private final ArrayList < Query > pendingQueries = new ArrayList < > ( ) ;
2014-12-04 19:54:16 -05:00
2014-12-13 09:32:11 -05:00
public enum PagingOrder {
NORMAL ,
REVERSE
2016-02-04 08:39:16 -05:00
}
2014-12-13 09:32:11 -05:00
2014-12-04 19:54:16 -05:00
public MessageArchiveService ( final XmppConnectionService service ) {
this . mXmppConnectionService = service ;
}
2015-12-10 12:28:47 -05:00
private void catchup ( final Account account ) {
synchronized ( this . queries ) {
for ( Iterator < Query > iterator = this . queries . iterator ( ) ; iterator . hasNext ( ) ; ) {
Query query = iterator . next ( ) ;
if ( query . getAccount ( ) = = account ) {
iterator . remove ( ) ;
}
}
}
2014-12-13 06:25:52 -05:00
long startCatchup = getLastMessageTransmitted ( account ) ;
long endCatchup = account . getXmppConnection ( ) . getLastSessionEstablished ( ) ;
if ( startCatchup = = 0 ) {
return ;
2014-12-15 17:06:29 -05:00
} else if ( endCatchup - startCatchup > = Config . MAM_MAX_CATCHUP ) {
startCatchup = endCatchup - Config . MAM_MAX_CATCHUP ;
2014-12-13 06:25:52 -05:00
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 ) ;
}
2015-10-04 18:45:16 -04:00
public void catchupMUC ( final Conversation conversation ) {
if ( conversation . getLastMessageTransmitted ( ) < 0 & & conversation . countMessages ( ) = = 0 ) {
query ( conversation ,
0 ,
System . currentTimeMillis ( ) ) ;
} else {
query ( conversation ,
conversation . getLastMessageTransmitted ( ) ,
System . currentTimeMillis ( ) ) ;
}
}
2014-12-13 06:25:52 -05:00
private long getLastMessageTransmitted ( final Account account ) {
2015-12-11 07:52:04 -05:00
Pair < Long , String > pair = mXmppConnectionService . databaseBackend . getLastMessageReceived ( account ) ;
return pair = = null ? 0 : pair . first ;
2014-12-13 06:25:52 -05:00
}
2014-12-15 17:06:29 -05:00
public Query query ( final Conversation conversation ) {
2015-08-30 05:24:37 -04:00
if ( conversation . getLastMessageTransmitted ( ) < 0 & & conversation . countMessages ( ) = = 0 ) {
return query ( conversation ,
0 ,
System . currentTimeMillis ( ) ) ;
} else {
return query ( conversation ,
conversation . getLastMessageTransmitted ( ) ,
conversation . getAccount ( ) . getXmppConnection ( ) . getLastSessionEstablished ( ) ) ;
}
2014-12-13 06:25:52 -05:00
}
2014-12-15 17:06:29 -05:00
public Query query ( final Conversation conversation , long end ) {
return this . query ( conversation , conversation . getLastMessageTransmitted ( ) , end ) ;
}
public Query query ( Conversation conversation , long start , long end ) {
2014-12-04 19:54:16 -05:00
synchronized ( this . queries ) {
2014-12-13 06:25:52 -05:00
if ( start > end ) {
2014-12-15 17:06:29 -05:00
return null ;
2014-12-08 15:59:14 -05:00
}
2014-12-13 09:32:11 -05:00
final Query query = new Query ( conversation , start , end , PagingOrder . REVERSE ) ;
2016-02-04 05:55:42 -05:00
query . reference = conversation . getFirstMamReference ( ) ;
2014-12-04 19:54:16 -05:00
this . queries . add ( query ) ;
2014-12-13 06:25:52 -05:00
this . execute ( query ) ;
2014-12-15 17:06:29 -05:00
return query ;
2014-12-13 06:25:52 -05:00
}
}
2014-12-14 17:23:32 -05:00
public void executePendingQueries ( final Account account ) {
List < Query > pending = new ArrayList < > ( ) ;
synchronized ( this . pendingQueries ) {
for ( Iterator < Query > iterator = this . pendingQueries . iterator ( ) ; iterator . hasNext ( ) ; ) {
Query query = iterator . next ( ) ;
if ( query . getAccount ( ) = = account ) {
pending . add ( query ) ;
iterator . remove ( ) ;
}
}
}
for ( Query query : pending ) {
this . execute ( query ) ;
}
}
2014-12-13 06:25:52 -05:00
private void execute ( final Query query ) {
2014-12-14 17:23:32 -05:00
final Account account = query . getAccount ( ) ;
if ( account . getStatus ( ) = = Account . State . ONLINE ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : running mam query " + query . toString ( ) ) ;
IqPacket packet = this . mXmppConnectionService . getIqGenerator ( ) . queryMessageArchiveManagement ( query ) ;
this . mXmppConnectionService . sendIqPacket ( account , packet , new OnIqPacketReceived ( ) {
2014-12-04 19:54:16 -05:00
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
2015-12-10 12:28:47 -05:00
if ( packet . getType ( ) = = IqPacket . TYPE . TIMEOUT ) {
synchronized ( MessageArchiveService . this . queries ) {
MessageArchiveService . this . queries . remove ( query ) ;
if ( query . hasCallback ( ) ) {
2016-02-04 08:39:16 -05:00
query . callback ( false ) ;
2015-12-10 12:28:47 -05:00
}
}
} else if ( packet . getType ( ) ! = IqPacket . TYPE . RESULT ) {
2014-12-14 17:23:32 -05:00
Log . d ( Config . LOGTAG , account . getJid ( ) . toBareJid ( ) . toString ( ) + " : error executing mam: " + packet . toString ( ) ) ;
2016-02-04 05:55:42 -05:00
finalizeQuery ( query , true ) ;
2014-12-08 15:59:14 -05:00
}
2014-12-04 19:54:16 -05:00
}
} ) ;
2014-12-14 17:23:32 -05:00
} else {
synchronized ( this . pendingQueries ) {
this . pendingQueries . add ( query ) ;
}
}
2014-12-04 19:54:16 -05:00
}
2016-02-04 05:55:42 -05:00
private void finalizeQuery ( Query query , boolean done ) {
2014-12-08 15:59:14 -05:00
synchronized ( this . queries ) {
this . queries . remove ( query ) ;
}
2014-12-09 16:50:53 -05:00
final Conversation conversation = query . getConversation ( ) ;
2014-12-13 06:25:52 -05:00
if ( conversation ! = null ) {
conversation . sort ( ) ;
2016-02-04 05:55:42 -05:00
conversation . setHasMessagesLeftOnServer ( ! done ) ;
2014-12-13 06:25:52 -05:00
} else {
for ( Conversation tmp : this . mXmppConnectionService . getConversations ( ) ) {
if ( tmp . getAccount ( ) = = query . getAccount ( ) ) {
tmp . sort ( ) ;
}
}
2014-12-09 16:50:53 -05:00
}
2015-12-11 07:52:04 -05:00
if ( query . hasCallback ( ) ) {
2016-02-04 08:39:16 -05:00
query . callback ( done ) ;
2015-12-11 07:52:04 -05:00
} else {
this . mXmppConnectionService . updateConversationUi ( ) ;
}
2014-12-08 15:59:14 -05:00
}
2014-12-20 06:52:08 -05:00
public boolean queryInProgress ( Conversation conversation , XmppConnectionService . OnMoreMessagesLoaded callback ) {
2014-12-13 09:32:11 -05:00
synchronized ( this . queries ) {
for ( Query query : queries ) {
if ( query . conversation = = conversation ) {
2014-12-20 06:52:08 -05:00
if ( ! query . hasCallback ( ) & & callback ! = null ) {
query . setCallback ( callback ) ;
}
2014-12-13 09:32:11 -05:00
return true ;
}
}
return false ;
}
}
2015-05-15 06:29:45 -04:00
public void processFin ( Element fin , Jid from ) {
2014-12-04 19:54:16 -05:00
if ( fin = = null ) {
return ;
}
Query query = findQuery ( fin . getAttribute ( " queryid " ) ) ;
2015-05-15 06:29:45 -04:00
if ( query = = null | | ! query . validFrom ( from ) ) {
2014-12-04 19:54:16 -05:00
return ;
}
boolean complete = fin . getAttributeAsBoolean ( " complete " ) ;
Element set = fin . findChild ( " set " , " http://jabber.org/protocol/rsm " ) ;
Element last = set = = null ? null : set . findChild ( " last " ) ;
2014-12-13 09:32:11 -05:00
Element first = set = = null ? null : set . findChild ( " first " ) ;
Element relevant = query . getPagingOrder ( ) = = PagingOrder . NORMAL ? last : first ;
2016-02-03 04:40:44 -05:00
boolean abort = ( query . getStart ( ) = = 0 & & query . getTotalCount ( ) > = Config . PAGE_SIZE ) | | query . getTotalCount ( ) > = Config . MAM_MAX_MESSAGES ;
2016-02-04 05:55:42 -05:00
if ( query . getConversation ( ) ! = null ) {
query . getConversation ( ) . setFirstMamReference ( first = = null ? null : first . getContent ( ) ) ;
}
2014-12-15 17:06:29 -05:00
if ( complete | | relevant = = null | | abort ) {
2016-02-04 05:55:42 -05:00
final boolean done = ( complete | | query . getMessageCount ( ) = = 0 ) & & query . getStart ( ) = = 0 ;
this . finalizeQuery ( query , done ) ;
Log . d ( Config . LOGTAG , query . getAccount ( ) . getJid ( ) . toBareJid ( ) + " : finished mam after " + query . getTotalCount ( ) + " messages. messages left= " + Boolean . toString ( ! done ) ) ;
2015-12-11 14:43:50 -05:00
if ( query . getWith ( ) = = null & & query . getMessageCount ( ) > 0 ) {
2015-12-10 17:05:11 -05:00
mXmppConnectionService . getNotificationService ( ) . finishBacklog ( true ) ;
}
2014-12-04 19:54:16 -05:00
} else {
2014-12-13 09:32:11 -05:00
final Query nextQuery ;
if ( query . getPagingOrder ( ) = = PagingOrder . NORMAL ) {
nextQuery = query . next ( last = = null ? null : last . getContent ( ) ) ;
} else {
nextQuery = query . prev ( first = = null ? null : first . getContent ( ) ) ;
}
2014-12-13 06:25:52 -05:00
this . execute ( nextQuery ) ;
2016-02-04 05:55:42 -05:00
this . finalizeQuery ( query , false ) ;
2014-12-04 19:54:16 -05:00
synchronized ( this . queries ) {
this . queries . remove ( query ) ;
this . queries . add ( nextQuery ) ;
}
}
}
2014-12-13 06:25:52 -05:00
public Query findQuery ( String id ) {
2014-12-04 19:54:16 -05:00
if ( id = = null ) {
return null ;
}
synchronized ( this . queries ) {
for ( Query query : this . queries ) {
if ( query . getQueryId ( ) . equals ( id ) ) {
return query ;
}
}
return null ;
}
}
2014-12-08 15:59:14 -05:00
@Override
public void onAdvancedStreamFeaturesAvailable ( Account account ) {
if ( account . getXmppConnection ( ) ! = null & & account . getXmppConnection ( ) . getFeatures ( ) . mam ( ) ) {
2014-12-13 06:25:52 -05:00
this . catchup ( account ) ;
2014-12-08 15:59:14 -05:00
}
}
2014-12-04 19:54:16 -05:00
public class Query {
2016-02-03 04:40:44 -05:00
private int totalCount = 0 ;
2014-12-17 04:50:51 -05:00
private int messageCount = 0 ;
2014-12-04 19:54:16 -05:00
private long start ;
private long end ;
private String queryId ;
2014-12-13 09:32:11 -05:00
private String reference = null ;
2014-12-13 06:25:52 -05:00
private Account account ;
2014-12-04 19:54:16 -05:00
private Conversation conversation ;
2014-12-13 09:32:11 -05:00
private PagingOrder pagingOrder = PagingOrder . NORMAL ;
2014-12-15 17:06:29 -05:00
private XmppConnectionService . OnMoreMessagesLoaded callback = null ;
2014-12-13 09:32:11 -05:00
2014-12-04 19:54:16 -05:00
public Query ( Conversation conversation , long start , long end ) {
2014-12-13 06:25:52 -05:00
this ( conversation . getAccount ( ) , start , end ) ;
2014-12-04 19:54:16 -05:00
this . conversation = conversation ;
2014-12-13 06:25:52 -05:00
}
2014-12-13 09:32:11 -05:00
public Query ( Conversation conversation , long start , long end , PagingOrder order ) {
this ( conversation , start , end ) ;
this . pagingOrder = order ;
}
2014-12-13 06:25:52 -05:00
public Query ( Account account , long start , long end ) {
this . account = account ;
2014-12-04 19:54:16 -05:00
this . start = start ;
this . end = end ;
this . queryId = new BigInteger ( 50 , mXmppConnectionService . getRNG ( ) ) . toString ( 32 ) ;
}
2014-12-13 09:32:11 -05:00
private Query page ( String reference ) {
2014-12-13 06:25:52 -05:00
Query query = new Query ( this . account , this . start , this . end ) ;
2014-12-13 09:32:11 -05:00
query . reference = reference ;
2014-12-13 06:25:52 -05:00
query . conversation = conversation ;
2016-02-03 04:40:44 -05:00
query . totalCount = totalCount ;
2014-12-15 17:06:29 -05:00
query . callback = callback ;
2014-12-04 19:54:16 -05:00
return query ;
}
2014-12-13 09:32:11 -05:00
public Query next ( String reference ) {
Query query = page ( reference ) ;
query . pagingOrder = PagingOrder . NORMAL ;
return query ;
}
public Query prev ( String reference ) {
Query query = page ( reference ) ;
query . pagingOrder = PagingOrder . REVERSE ;
return query ;
}
public String getReference ( ) {
return reference ;
}
public PagingOrder getPagingOrder ( ) {
return this . pagingOrder ;
2014-12-04 19:54:16 -05:00
}
public String getQueryId ( ) {
return queryId ;
}
public Jid getWith ( ) {
2015-01-23 18:22:51 -05:00
return conversation = = null ? null : conversation . getJid ( ) . toBareJid ( ) ;
}
public boolean muc ( ) {
return conversation ! = null & & conversation . getMode ( ) = = Conversation . MODE_MULTI ;
2014-12-04 19:54:16 -05:00
}
public long getStart ( ) {
return start ;
}
2014-12-15 17:06:29 -05:00
public void setCallback ( XmppConnectionService . OnMoreMessagesLoaded callback ) {
this . callback = callback ;
}
2016-02-04 08:39:16 -05:00
public void callback ( boolean done ) {
2014-12-15 17:06:29 -05:00
if ( this . callback ! = null ) {
2014-12-17 04:50:51 -05:00
this . callback . onMoreMessagesLoaded ( messageCount , conversation ) ;
2016-02-04 08:39:16 -05:00
if ( done ) {
2014-12-17 04:50:51 -05:00
this . callback . informUser ( R . string . no_more_history_on_server ) ;
}
2014-12-15 17:06:29 -05:00
}
}
2014-12-04 19:54:16 -05:00
public long getEnd ( ) {
return end ;
}
public Conversation getConversation ( ) {
return conversation ;
}
2014-12-13 06:25:52 -05:00
public Account getAccount ( ) {
return this . account ;
}
2014-12-17 00:59:58 -05:00
public void incrementMessageCount ( ) {
2014-12-17 04:50:51 -05:00
this . messageCount + + ;
2016-02-03 10:04:21 -05:00
this . totalCount + + ;
2014-12-17 00:59:58 -05:00
}
2016-02-03 04:40:44 -05:00
public int getTotalCount ( ) {
return this . totalCount ;
}
2015-01-02 18:47:22 -05:00
public int getMessageCount ( ) {
return this . messageCount ;
}
2015-05-15 06:29:45 -04:00
public boolean validFrom ( Jid from ) {
if ( muc ( ) ) {
return getWith ( ) . equals ( from ) ;
} else {
return ( from = = null ) | | account . getJid ( ) . toBareJid ( ) . equals ( from . toBareJid ( ) ) ;
}
}
2014-12-13 06:25:52 -05:00
@Override
public String toString ( ) {
StringBuilder builder = new StringBuilder ( ) ;
2015-01-23 18:22:51 -05:00
if ( this . muc ( ) ) {
2016-02-04 08:39:16 -05:00
builder . append ( " to= " ) ;
builder . append ( this . getWith ( ) . toString ( ) ) ;
2014-12-13 06:25:52 -05:00
} else {
2015-01-23 18:22:51 -05:00
builder . append ( " with= " ) ;
if ( this . getWith ( ) = = null ) {
builder . append ( " * " ) ;
} else {
builder . append ( getWith ( ) . toString ( ) ) ;
}
2014-12-13 06:25:52 -05:00
}
builder . append ( " , start= " ) ;
builder . append ( AbstractGenerator . getTimestamp ( this . start ) ) ;
builder . append ( " , end= " ) ;
builder . append ( AbstractGenerator . getTimestamp ( this . end ) ) ;
2014-12-13 09:32:11 -05:00
if ( this . reference ! = null ) {
if ( this . pagingOrder = = PagingOrder . NORMAL ) {
builder . append ( " , after= " ) ;
} else {
builder . append ( " , before= " ) ;
}
builder . append ( this . reference ) ;
2014-12-13 06:25:52 -05:00
}
return builder . toString ( ) ;
}
2014-12-15 17:06:29 -05:00
public boolean hasCallback ( ) {
return this . callback ! = null ;
}
2014-12-04 19:54:16 -05:00
}
}