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