Remove old Push code

This commit is contained in:
cketti 2020-04-05 19:40:23 +02:00
parent 3f60e41155
commit 2a78418911
24 changed files with 0 additions and 1700 deletions

View file

@ -7,14 +7,12 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@ -69,8 +67,6 @@ import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mailstore.LocalFolder;
@ -127,7 +123,6 @@ public class MessagingController {
private final BlockingQueue<Command> queuedCommands = new PriorityBlockingQueue<>();
private final Set<MessagingListener> listeners = new CopyOnWriteArraySet<>();
private final ConcurrentHashMap<Account, Pusher> pushers = new ConcurrentHashMap<>();
private final ExecutorService threadPool = Executors.newCachedThreadPool();
private final MemorizingMessagingListener memorizingMessagingListener = new MemorizingMessagingListener();
private final UnreadMessageCountProvider unreadMessageCountProvider;
@ -2657,116 +2652,6 @@ public class MessagingController {
}
}
public Collection<Pusher> getPushers() {
return pushers.values();
}
public Pusher getPusher(Account account) {
return pushers.get(account);
}
public boolean setupPushing(final Account account) {
try {
Pusher previousPusher = pushers.remove(account);
if (previousPusher != null) {
previousPusher.stop();
}
Account.FolderMode aDisplayMode = account.getFolderDisplayMode();
Account.FolderMode aPushMode = account.getFolderPushMode();
List<String> names = new ArrayList<>();
LocalStore localStore = localStoreProvider.getInstance(account);
for (final LocalFolder folder : localStore.getPersonalNamespaces(false)) {
if (folder.getServerId().equals(account.getOutboxFolder())) {
continue;
}
folder.open();
FolderClass fDisplayClass = folder.getDisplayClass();
FolderClass fPushClass = folder.getPushClass();
if (LocalFolder.isModeMismatch(aDisplayMode, fDisplayClass)) {
// Never push a folder that isn't displayed
/*
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Not pushing folder " + folder.getName() +
" which is in display class " + fDisplayClass + " while account is in display mode " + aDisplayMode);
}
*/
continue;
}
if (LocalFolder.isModeMismatch(aPushMode, fPushClass)) {
// Do not push folders in the wrong class
/*
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Not pushing folder " + folder.getName() +
" which is in push mode " + fPushClass + " while account is in push mode " + aPushMode);
}
*/
continue;
}
Timber.i("Starting pusher for %s:%s", account.getDescription(), folder.getServerId());
names.add(folder.getServerId());
}
if (!names.isEmpty()) {
PushReceiver receiver = new MessagingControllerPushReceiver(context, localStoreProvider, account, this);
int maxPushFolders = account.getMaxPushFolders();
if (names.size() > maxPushFolders) {
Timber.i("Count of folders to push for account %s is %d, greater than limit of %d, truncating",
account.getDescription(), names.size(), maxPushFolders);
names = names.subList(0, maxPushFolders);
}
try {
Backend backend = getBackend(account);
if (!backend.isPushCapable()) {
Timber.i("Account %s is not push capable, skipping", account.getDescription());
return false;
}
Pusher pusher = backend.createPusher(receiver);
Pusher oldPusher = pushers.putIfAbsent(account, pusher);
if (oldPusher == null) {
pusher.start(names);
}
} catch (Exception e) {
Timber.e(e, "Could not get remote store");
return false;
}
return true;
} else {
Timber.i("No folders are configured for pushing in account %s", account.getDescription());
return false;
}
} catch (Exception e) {
Timber.e(e, "Got exception while setting up pushing");
}
return false;
}
public void stopAllPushing() {
Timber.i("Stopping all pushers");
Iterator<Pusher> iter = pushers.values().iterator();
while (iter.hasNext()) {
Pusher pusher = iter.next();
iter.remove();
pusher.stop();
}
}
public void cancelNotificationsForAccount(Account account) {
notificationController.clearNewMailNotifications(account);
}

View file

@ -1,111 +0,0 @@
package com.fsck.k9.controller;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import android.content.Context;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.power.WakeLock;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.mailstore.LocalStoreProvider;
import com.fsck.k9.service.SleepService;
import timber.log.Timber;
public class MessagingControllerPushReceiver implements PushReceiver {
final Account account;
final MessagingController controller;
final Context context;
final LocalStoreProvider localStoreProvider;
public MessagingControllerPushReceiver(Context context, LocalStoreProvider localStoreProvider,
Account nAccount, MessagingController nController) {
account = nAccount;
controller = nController;
this.localStoreProvider = localStoreProvider;
this.context = context;
}
public void messagesFlagsChanged(String folderServerId, List<Message> messages) {
syncFolder(folderServerId);
}
public void messagesArrived(String folderServerId, List<Message> messages) {
syncFolder(folderServerId);
}
public void messagesRemoved(String folderServerId, List<Message> messages) {
syncFolder(folderServerId);
}
public void syncFolder(String folderServerId) {
Timber.v("syncFolder(%s)", folderServerId);
final CountDownLatch latch = new CountDownLatch(1);
controller.synchronizeMailbox(account, folderServerId, new SimpleMessagingListener() {
@Override
public void synchronizeMailboxFinished(Account account, String folderServerId) {
latch.countDown();
}
@Override
public void synchronizeMailboxFailed(Account account, String folderServerId,
String message) {
latch.countDown();
}
});
Timber.v("syncFolder(%s) about to await latch release", folderServerId);
try {
latch.await();
Timber.v("syncFolder(%s) got latch release", folderServerId);
} catch (Exception e) {
Timber.e(e, "Interrupted while awaiting latch release");
}
}
@Override
public void sleep(WakeLock wakeLock, long millis) {
SleepService.sleep(context, millis, wakeLock, K9.PUSH_WAKE_LOCK_TIMEOUT);
}
public void pushError(String errorMessage, Exception e) {
String errMess = errorMessage;
controller.notifyUserIfCertificateProblem(account, e, true);
if (errMess == null && e != null) {
errMess = e.getMessage();
}
Timber.e(e, errMess);
}
@Override
public void authenticationFailed() {
controller.handleAuthenticationFailure(account, true);
}
public String getPushState(String folderServerId) {
LocalFolder localFolder = null;
try {
LocalStore localStore = localStoreProvider.getInstance(account);
localFolder = localStore.getFolder(folderServerId);
localFolder.open();
return localFolder.getPushState();
} catch (Exception e) {
Timber.e(e, "Unable to get push state from account %s, folder %s", account.getDescription(), folderServerId);
return null;
} finally {
if (localFolder != null) {
localFolder.close();
}
}
}
public void setPushActive(String folderServerId, boolean enabled) {
// Nothing to do for now
}
}

View file

@ -115,14 +115,6 @@ class K9BackendFolder(
database.setString(column = "status", value = status)
}
override fun getPushState(): String? {
return database.getString(column = "push_state")
}
override fun setPushState(pushState: String?) {
return database.setString(column = "push_state", value = pushState)
}
override fun isMessagePresent(messageServerId: String): Boolean {
return database.execute(false) { db ->
val cursor = db.query(

View file

@ -1,54 +0,0 @@
package com.fsck.k9.service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import timber.log.Timber;
public class PushService extends CoreService {
private static final String START_SERVICE = "com.fsck.k9.service.PushService.startService";
private static final String STOP_SERVICE = "com.fsck.k9.service.PushService.stopService";
public static void startService(Context context) {
Intent i = new Intent();
i.setClass(context, PushService.class);
i.setAction(PushService.START_SERVICE);
addWakeLock(context, i);
context.startService(i);
}
public static void stopService(Context context) {
Intent i = new Intent();
i.setClass(context, PushService.class);
i.setAction(PushService.STOP_SERVICE);
addWakeLock(context, i);
context.startService(i);
}
@Override
public int startService(Intent intent, int startId) {
int startFlag = START_STICKY;
if (START_SERVICE.equals(intent.getAction())) {
Timber.i("PushService started with startId = %d", startId);
} else if (STOP_SERVICE.equals(intent.getAction())) {
Timber.i("PushService stopping with startId = %d", startId);
stopSelf(startId);
startFlag = START_NOT_STICKY;
}
return startFlag;
}
@Override
public void onCreate() {
super.onCreate();
setAutoShutdown(false);
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
}

View file

@ -1,139 +0,0 @@
package com.fsck.k9.service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import com.fsck.k9.mail.power.WakeLock;
import timber.log.Timber;
import static java.lang.Thread.currentThread;
public class SleepService extends CoreService {
private static final String ALARM_FIRED = "com.fsck.k9.service.SleepService.ALARM_FIRED";
private static final String LATCH_ID = "com.fsck.k9.service.SleepService.LATCH_ID_EXTRA";
private static ConcurrentHashMap<Integer, SleepDatum> sleepData = new ConcurrentHashMap<>();
private static AtomicInteger latchId = new AtomicInteger();
public static void sleep(Context context, long sleepTime, WakeLock wakeLock, long wakeLockTimeout) {
Integer id = latchId.getAndIncrement();
Timber.d("SleepService Preparing CountDownLatch with id = %d, thread %s", id, currentThread().getName());
SleepDatum sleepDatum = new SleepDatum();
CountDownLatch latch = new CountDownLatch(1);
sleepDatum.latch = latch;
sleepDatum.reacquireLatch = new CountDownLatch(1);
sleepData.put(id, sleepDatum);
Intent i = new Intent(context, SleepService.class);
i.putExtra(LATCH_ID, id);
i.setAction(ALARM_FIRED + "." + id);
long startTime = SystemClock.elapsedRealtime();
long nextTime = startTime + sleepTime;
BootReceiver.scheduleIntent(context, nextTime, i);
if (wakeLock != null) {
sleepDatum.wakeLock = wakeLock;
sleepDatum.timeout = wakeLockTimeout;
wakeLock.release();
}
try {
boolean countedDown = latch.await(sleepTime, TimeUnit.MILLISECONDS);
if (!countedDown) {
Timber.d("SleepService latch timed out for id = %d, thread %s", id, currentThread().getName());
}
} catch (InterruptedException ie) {
Timber.e(ie, "SleepService Interrupted while awaiting latch");
}
SleepDatum releaseDatum = sleepData.remove(id);
if (releaseDatum == null) {
try {
Timber.d("SleepService waiting for reacquireLatch for id = %d, thread %s",
id, currentThread().getName());
if (!sleepDatum.reacquireLatch.await(5000, TimeUnit.MILLISECONDS)) {
Timber.w("SleepService reacquireLatch timed out for id = %d, thread %s",
id, currentThread().getName());
} else {
Timber.d("SleepService reacquireLatch finished for id = %d, thread %s",
id, currentThread().getName());
}
} catch (InterruptedException ie) {
Timber.e(ie, "SleepService Interrupted while awaiting reacquireLatch");
}
} else {
reacquireWakeLock(releaseDatum);
}
long endTime = SystemClock.elapsedRealtime();
long actualSleep = endTime - startTime;
if (actualSleep < sleepTime) {
Timber.w("SleepService sleep time too short: requested was %d, actual was %d", sleepTime, actualSleep);
} else {
Timber.d("SleepService requested sleep time was %d, actual was %d", sleepTime, actualSleep);
}
}
private static void endSleep(Integer id) {
if (id != -1) {
SleepDatum sleepDatum = sleepData.remove(id);
if (sleepDatum != null) {
CountDownLatch latch = sleepDatum.latch;
if (latch == null) {
Timber.e("SleepService No CountDownLatch available with id = %s", id);
} else {
Timber.d("SleepService Counting down CountDownLatch with id = %d", id);
latch.countDown();
}
reacquireWakeLock(sleepDatum);
sleepDatum.reacquireLatch.countDown();
} else {
Timber.d("SleepService Sleep for id %d already finished", id);
}
}
}
private static void reacquireWakeLock(SleepDatum sleepDatum) {
WakeLock wakeLock = sleepDatum.wakeLock;
if (wakeLock != null) {
synchronized (wakeLock) {
long timeout = sleepDatum.timeout;
Timber.d("SleepService Acquiring wakeLock for %d ms", timeout);
wakeLock.acquire(timeout);
}
}
}
@Override
public int startService(Intent intent, int startId) {
try {
if (intent.getAction().startsWith(ALARM_FIRED)) {
Integer id = intent.getIntExtra(LATCH_ID, -1);
endSleep(id);
}
return START_NOT_STICKY;
}
finally {
stopSelf(startId);
}
}
private static class SleepDatum {
CountDownLatch latch;
WakeLock wakeLock;
long timeout;
CountDownLatch reacquireLatch;
}
}

View file

@ -278,14 +278,6 @@
android:name="com.fsck.k9.notification.NotificationActionService"
android:enabled="true"/>
<service
android:name="com.fsck.k9.service.PushService"
android:enabled="true"/>
<service
android:name="com.fsck.k9.service.SleepService"
android:enabled="true"/>
<service
android:name="com.fsck.k9.service.DatabaseUpgradeService"
android:exported="false"/>

View file

@ -360,14 +360,6 @@
android:name=".notification.NotificationActionService"
android:enabled="true"/>
<service
android:name=".service.PushService"
android:enabled="true"/>
<service
android:name=".service.SleepService"
android:enabled="true"/>
<service
android:name=".service.DatabaseUpgradeService"
android:exported="false"/>

View file

@ -6,8 +6,6 @@ import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.Part
import com.fsck.k9.mail.PushReceiver
import com.fsck.k9.mail.Pusher
interface Backend {
val supportsSeenFlag: Boolean
@ -88,8 +86,6 @@ interface Backend {
@Throws(MessagingException::class)
fun uploadMessage(folderServerId: String, message: Message): String?
fun createPusher(receiver: PushReceiver): Pusher
@Throws(MessagingException::class)
fun checkIncomingServerSettings()

View file

@ -18,8 +18,6 @@ interface BackendFolder {
fun setMoreMessages(moreMessages: MoreMessages)
fun setLastChecked(timestamp: Long)
fun setStatus(status: String?)
fun getPushState(): String?
fun setPushState(pushState: String?)
fun isMessagePresent(messageServerId: String): Boolean
fun getMessageFlags(messageServerId: String): Set<Flag>
fun setMessageFlag(messageServerId: String, flag: Flag, value: Boolean)

View file

@ -16,10 +16,7 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.power.PowerManager;
import com.fsck.k9.mail.store.imap.ImapPusher;
import com.fsck.k9.mail.store.imap.ImapStore;
import com.fsck.k9.mail.transport.smtp.SmtpTransport;
import org.jetbrains.annotations.NotNull;
@ -215,12 +212,6 @@ public class ImapBackend implements Backend {
return commandUploadMessage.uploadMessage(folderServerId, message);
}
@NotNull
@Override
public Pusher createPusher(@NotNull PushReceiver receiver) {
return new ImapPusher(imapStore, receiver, powerManager);
}
@Override
public void checkIncomingServerSettings() throws MessagingException {
imapStore.checkSettings();

View file

@ -336,15 +336,6 @@ class ImapSync {
fetchUnsyncedMessages(syncConfig, remoteFolder, unsyncedMessages, smallMessages, largeMessages, progress,
todo, fp, listener);
String updatedPushState = backendFolder.getPushState();
for (ImapMessage message : unsyncedMessages) {
String newPushState = remoteFolder.getNewPushState(updatedPushState, message);
if (newPushState != null) {
updatedPushState = newPushState;
}
}
backendFolder.setPushState(updatedPushState);
Timber.d("SYNC: Synced unsynced messages for folder %s", folder);
}
@ -416,10 +407,6 @@ class ImapSync {
unsyncedMessages.add(message);
} else {
String newPushState = remoteFolder.getNewPushState(backendFolder.getPushState(), message);
if (newPushState != null) {
backendFolder.setPushState(newPushState);
}
syncFlagMessages.add(message);
}
} else {

View file

@ -9,8 +9,6 @@ import com.fsck.k9.mail.FetchProfile
import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.Part
import com.fsck.k9.mail.PushReceiver
import com.fsck.k9.mail.Pusher
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import rs.ltt.jmap.client.JmapClient
@ -121,10 +119,6 @@ class JmapBackend(
return commandUpload.uploadMessage(folderServerId, message)
}
override fun createPusher(receiver: PushReceiver): Pusher {
throw UnsupportedOperationException("not implemented")
}
override fun checkIncomingServerSettings() {
jmapClient.call(EchoMethodCall()).get()
}

View file

@ -91,14 +91,6 @@ class InMemoryBackendFolder(override var name: String, var type: FolderType) : B
throw UnsupportedOperationException("not implemented")
}
override fun getPushState(): String? {
throw UnsupportedOperationException("not implemented")
}
override fun setPushState(pushState: String?) {
throw UnsupportedOperationException("not implemented")
}
override fun isMessagePresent(messageServerId: String): Boolean {
throw UnsupportedOperationException("not implemented")
}

View file

@ -9,8 +9,6 @@ import com.fsck.k9.mail.FetchProfile
import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.Part
import com.fsck.k9.mail.PushReceiver
import com.fsck.k9.mail.Pusher
import com.fsck.k9.mail.store.pop3.Pop3Store
import com.fsck.k9.mail.transport.smtp.SmtpTransport
@ -120,10 +118,6 @@ class Pop3Backend(
throw UnsupportedOperationException("not supported")
}
override fun createPusher(receiver: PushReceiver): Pusher {
throw UnsupportedOperationException("not supported")
}
override fun checkIncomingServerSettings() {
pop3Store.checkSettings()
}

View file

@ -10,8 +10,6 @@ import com.fsck.k9.mail.Flag
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.Part
import com.fsck.k9.mail.PushReceiver
import com.fsck.k9.mail.Pusher
import com.fsck.k9.mail.store.webdav.WebDavStore
import com.fsck.k9.mail.transport.WebDavTransport
import timber.log.Timber
@ -130,10 +128,6 @@ class WebDavBackend(
return commandUploadMessage.uploadMessage(folderServerId, message)
}
override fun createPusher(receiver: PushReceiver): Pusher {
throw UnsupportedOperationException("not supported")
}
override fun checkIncomingServerSettings() {
webDavStore.checkSettings()
}

View file

@ -1,19 +0,0 @@
package com.fsck.k9.mail;
import java.util.List;
import com.fsck.k9.mail.power.WakeLock;
public interface PushReceiver {
void syncFolder(String folderServerId);
void messagesArrived(String folderServerId, List<Message> mess);
void messagesFlagsChanged(String folderServerId, List<Message> mess);
void messagesRemoved(String folderServerId, List<Message> mess);
String getPushState(String folderServerId);
void pushError(String errorMessage, Exception e);
void authenticationFailed();
void setPushActive(String folderServerId, boolean enabled);
void sleep(WakeLock wakeLock, long millis);
}

View file

@ -1,17 +0,0 @@
package com.fsck.k9.mail;
import java.util.List;
public interface Pusher {
void start(List<String> folderServerIds);
void refresh();
void stop();
/**
*
* @return milliseconds of required refresh interval
*/
int getRefreshInterval();
void setLastRefresh(long lastRefresh);
long getLastRefresh();
}

View file

@ -1261,27 +1261,6 @@ public class ImapFolder {
}
}
public String getNewPushState(String oldSerializedPushState, Message message) {
try {
String uid = message.getUid();
long messageUid = Long.parseLong(uid);
ImapPushState oldPushState = ImapPushState.parse(oldSerializedPushState);
if (messageUid >= oldPushState.uidNext) {
long uidNext = messageUid + 1;
ImapPushState newPushState = new ImapPushState(uidNext);
return newPushState.toString();
} else {
return null;
}
} catch (Exception e) {
Timber.e(e, "Exception while updated push state for %s", getLogId());
return null;
}
}
public void setFlags(List<? extends Message> messages, final Set<Flag> flags, boolean value)
throws MessagingException {
open(OPEN_MODE_RW);

View file

@ -1,728 +0,0 @@
package com.fsck.k9.mail.store.imap;
import java.io.IOException;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.K9MailLib;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.power.PowerManager;
import com.fsck.k9.mail.power.WakeLock;
import timber.log.Timber;
import static com.fsck.k9.mail.K9MailLib.PUSH_WAKE_LOCK_TIMEOUT;
import static com.fsck.k9.mail.NetworkTimeouts.SOCKET_READ_TIMEOUT;
import static com.fsck.k9.mail.store.imap.ImapResponseParser.equalsIgnoreCase;
class ImapFolderPusher extends ImapFolder {
private static final int IDLE_READ_TIMEOUT_INCREMENT = 5 * 60 * 1000;
private static final int IDLE_FAILURE_COUNT_LIMIT = 10;
private static final int MAX_DELAY_TIME = 5 * 60 * 1000; // 5 minutes
private static final int NORMAL_DELAY_TIME = 5000;
private final PushReceiver pushReceiver;
private final Object threadLock = new Object();
private final IdleStopper idleStopper = new IdleStopper();
private final WakeLock wakeLock;
private final List<ImapResponse> storedUntaggedResponses = new ArrayList<>();
private Thread listeningThread;
private volatile boolean stop = false;
private volatile boolean idling = false;
public ImapFolderPusher(ImapStore store, String serverId, PushReceiver pushReceiver, PowerManager powerManager) {
super(store, serverId);
this.pushReceiver = pushReceiver;
String tag = "ImapFolderPusher " + store.getStoreConfig().toString() + ":" + getServerId();
wakeLock = powerManager.newWakeLock(tag);
wakeLock.setReferenceCounted(false);
}
public void start() {
synchronized (threadLock) {
if (listeningThread != null) {
throw new IllegalStateException("start() called twice");
}
listeningThread = new Thread(new PushRunnable());
listeningThread.start();
}
}
public void refresh() {
if (idling) {
wakeLock.acquire(PUSH_WAKE_LOCK_TIMEOUT);
idleStopper.stopIdle();
}
}
public void stop() {
synchronized (threadLock) {
if (listeningThread == null) {
throw new IllegalStateException("stop() called twice");
}
stop = true;
listeningThread.interrupt();
listeningThread = null;
}
ImapConnection conn = connection;
if (conn != null) {
if (K9MailLib.isDebug()) {
Timber.v("Closing connection to stop pushing for %s", getLogId());
}
conn.close();
} else {
Timber.w("Attempt to interrupt null connection to stop pushing on folderPusher for %s", getLogId());
}
}
@Override
protected void handleUntaggedResponse(ImapResponse response) {
if (response.getTag() == null && response.size() > 1) {
Object responseType = response.get(1);
if (equalsIgnoreCase(responseType, "FETCH") || equalsIgnoreCase(responseType, "EXPUNGE") ||
equalsIgnoreCase(responseType, "EXISTS")) {
if (K9MailLib.isDebug()) {
Timber.d("Storing response %s for later processing", response);
}
synchronized (storedUntaggedResponses) {
storedUntaggedResponses.add(response);
}
}
handlePossibleUidNext(response);
}
}
private void superHandleUntaggedResponse(ImapResponse response) {
super.handleUntaggedResponse(response);
}
private class PushRunnable implements Runnable, UntaggedHandler {
private int delayTime = NORMAL_DELAY_TIME;
private int idleFailureCount = 0;
private boolean needsPoll = false;
@Override
public void run() {
wakeLock.acquire(PUSH_WAKE_LOCK_TIMEOUT);
if (K9MailLib.isDebug()) {
Timber.i("Pusher starting for %s", getLogId());
}
long lastUidNext = -1L;
while (!stop) {
try {
long oldUidNext = getOldUidNext();
/*
* This makes sure 'oldUidNext' is never smaller than 'UIDNEXT' from
* the last loop iteration. This way we avoid looping endlessly causing
* the battery to drain.
*
* See issue 4907
*/
if (oldUidNext < lastUidNext) {
oldUidNext = lastUidNext;
}
boolean openedNewConnection = openConnectionIfNecessary();
if (stop) {
break;
}
boolean pushPollOnConnect = store.getStoreConfig().isPushPollOnConnect();
if (pushPollOnConnect && (openedNewConnection || needsPoll)) {
needsPoll = false;
syncFolderOnConnect();
}
if (stop) {
break;
}
long newUidNext = getNewUidNext();
lastUidNext = newUidNext;
long startUid = getStartUid(oldUidNext, newUidNext);
if (newUidNext > startUid) {
notifyMessagesArrived(startUid, newUidNext);
} else {
processStoredUntaggedResponses();
if (K9MailLib.isDebug()) {
Timber.i("About to IDLE for %s", getLogId());
}
prepareForIdle();
ImapConnection conn = connection;
setReadTimeoutForIdle(conn);
sendIdle(conn);
returnFromIdle();
}
} catch (AuthenticationFailedException e) {
reacquireWakeLockAndCleanUp();
if (K9MailLib.isDebug()) {
Timber.e(e, "Authentication failed. Stopping ImapFolderPusher.");
}
pushReceiver.authenticationFailed();
stop = true;
} catch (CertificateValidationException e) {
reacquireWakeLockAndCleanUp();
Timber.e(e, "Certificate check failed. Stopping ImapFolderPusher.");
stop = true;
pushReceiver.pushError("Push error for " + getServerId(), e);
} catch (Exception e) {
reacquireWakeLockAndCleanUp();
if (stop) {
Timber.i("Got exception while idling, but stop is set for %s", getLogId());
} else {
pushReceiver.pushError("Push error for " + getServerId(), e);
Timber.e("Got exception while idling for %s", getLogId());
pushReceiver.sleep(wakeLock, delayTime);
delayTime *= 2;
if (delayTime > MAX_DELAY_TIME) {
delayTime = MAX_DELAY_TIME;
}
idleFailureCount++;
if (idleFailureCount > IDLE_FAILURE_COUNT_LIMIT) {
Timber.e("Disabling pusher for %s after %d consecutive errors", getLogId(), idleFailureCount);
pushReceiver.pushError("Push disabled for " + getServerId() + " after " + idleFailureCount +
" consecutive errors", e);
stop = true;
}
}
}
}
pushReceiver.setPushActive(getServerId(), false);
try {
if (K9MailLib.isDebug()) {
Timber.i("Pusher for %s is exiting", getLogId());
}
close();
} catch (Exception me) {
Timber.e(me, "Got exception while closing for %s", getLogId());
} finally {
wakeLock.release();
}
}
private void reacquireWakeLockAndCleanUp() {
wakeLock.acquire(PUSH_WAKE_LOCK_TIMEOUT);
clearStoredUntaggedResponses();
idling = false;
pushReceiver.setPushActive(getServerId(), false);
try {
connection.close();
} catch (Exception me) {
Timber.e(me, "Got exception while closing for exception for %s", getLogId());
}
connection = null;
}
private long getNewUidNext() throws MessagingException {
long newUidNext = uidNext;
if (newUidNext != -1L) {
return newUidNext;
}
if (K9MailLib.isDebug()) {
Timber.d("uidNext is -1, using search to find highest UID");
}
long highestUid = getHighestUid();
if (highestUid == -1L) {
return -1L;
}
newUidNext = highestUid + 1;
if (K9MailLib.isDebug()) {
Timber.d("highest UID = %d, set newUidNext to %d", highestUid, newUidNext);
}
return newUidNext;
}
private long getStartUid(long oldUidNext, long newUidNext) {
long startUid = oldUidNext;
int displayCount = store.getStoreConfig().getDisplayCount();
if (startUid < newUidNext - displayCount) {
startUid = newUidNext - displayCount;
}
if (startUid < 1) {
startUid = 1;
}
return startUid;
}
private void prepareForIdle() {
pushReceiver.setPushActive(getServerId(), true);
idling = true;
}
private void sendIdle(ImapConnection conn) throws MessagingException, IOException {
String tag = conn.sendCommand(Commands.IDLE, false);
List<ImapResponse> responses;
try {
try {
responses = conn.readStatusResponse(tag, Commands.IDLE, this);
} finally {
idleStopper.stopAcceptingDoneContinuation();
}
} catch (IOException e) {
conn.close();
throw e;
}
handleUntaggedResponses(responses);
}
private void returnFromIdle() {
idling = false;
delayTime = NORMAL_DELAY_TIME;
idleFailureCount = 0;
}
private boolean openConnectionIfNecessary() throws MessagingException {
ImapConnection oldConnection = connection;
internalOpen(OPEN_MODE_RO);
ImapConnection conn = connection;
checkConnectionNotNull(conn);
checkConnectionIdleCapable(conn);
return conn != oldConnection;
}
private void checkConnectionNotNull(ImapConnection conn) throws MessagingException {
if (conn == null) {
String message = "Could not establish connection for IDLE";
pushReceiver.pushError(message, null);
throw new MessagingException(message);
}
}
private void checkConnectionIdleCapable(ImapConnection conn) throws MessagingException {
if (!conn.isIdleCapable()) {
stop = true;
String message = "IMAP server is not IDLE capable: " + conn.toString();
pushReceiver.pushError(message, null);
throw new MessagingException(message);
}
}
private void setReadTimeoutForIdle(ImapConnection conn) throws SocketException {
int idleRefreshTimeout = store.getStoreConfig().getIdleRefreshMinutes() * 60 * 1000;
conn.setReadTimeout(idleRefreshTimeout + IDLE_READ_TIMEOUT_INCREMENT);
}
@Override
public void handleAsyncUntaggedResponse(ImapResponse response) {
if (K9MailLib.isDebug()) {
Timber.v("Got async response: %s", response);
}
if (stop) {
if (K9MailLib.isDebug()) {
Timber.d("Got async untagged response: %s, but stop is set for %s", response, getLogId());
}
idleStopper.stopIdle();
} else {
if (response.getTag() == null) {
if (response.size() > 1) {
Object responseType = response.get(1);
if (equalsIgnoreCase(responseType, "EXISTS") || equalsIgnoreCase(responseType, "EXPUNGE") ||
equalsIgnoreCase(responseType, "FETCH")) {
wakeLock.acquire(PUSH_WAKE_LOCK_TIMEOUT);
if (K9MailLib.isDebug()) {
Timber.d("Got useful async untagged response: %s for %s", response, getLogId());
}
idleStopper.stopIdle();
}
} else if (response.isContinuationRequested()) {
if (K9MailLib.isDebug()) {
Timber.d("Idling %s", getLogId());
}
idleStopper.startAcceptingDoneContinuation(connection);
wakeLock.release();
}
}
}
}
private void clearStoredUntaggedResponses() {
synchronized (storedUntaggedResponses) {
storedUntaggedResponses.clear();
}
}
private void processStoredUntaggedResponses() throws MessagingException {
while (true) {
List<ImapResponse> untaggedResponses = getAndClearStoredUntaggedResponses();
if (untaggedResponses.isEmpty()) {
break;
}
if (K9MailLib.isDebug()) {
Timber.i("Processing %d untagged responses from previous commands for %s",
untaggedResponses.size(), getLogId());
}
processUntaggedResponses(untaggedResponses);
}
}
private List<ImapResponse> getAndClearStoredUntaggedResponses() {
synchronized (storedUntaggedResponses) {
if (storedUntaggedResponses.isEmpty()) {
return Collections.emptyList();
}
List<ImapResponse> untaggedResponses = new ArrayList<>(storedUntaggedResponses);
storedUntaggedResponses.clear();
return untaggedResponses;
}
}
private void processUntaggedResponses(List<ImapResponse> responses) throws MessagingException {
boolean skipSync = false;
int oldMessageCount = messageCount;
if (oldMessageCount == -1) {
skipSync = true;
}
List<Long> flagSyncMsgSeqs = new ArrayList<>();
List<String> removeMsgUids = new LinkedList<>();
for (ImapResponse response : responses) {
oldMessageCount += processUntaggedResponse(oldMessageCount, response, flagSyncMsgSeqs, removeMsgUids);
}
if (!skipSync) {
if (oldMessageCount < 0) {
oldMessageCount = 0;
}
if (messageCount > oldMessageCount) {
syncMessages(messageCount);
}
}
if (K9MailLib.isDebug()) {
Timber.d("UIDs for messages needing flag sync are %s for %s", flagSyncMsgSeqs, getLogId());
}
if (!flagSyncMsgSeqs.isEmpty()) {
syncMessages(flagSyncMsgSeqs);
}
if (!removeMsgUids.isEmpty()) {
removeMessages(removeMsgUids);
}
}
private int processUntaggedResponse(long oldMessageCount, ImapResponse response, List<Long> flagSyncMsgSeqs,
List<String> removeMsgUids) {
superHandleUntaggedResponse(response);
int messageCountDelta = 0;
if (response.getTag() == null && response.size() > 1) {
try {
Object responseType = response.get(1);
if (equalsIgnoreCase(responseType, "FETCH")) {
Timber.i("Got FETCH %s", response);
long msgSeq = response.getLong(0);
if (K9MailLib.isDebug()) {
Timber.d("Got untagged FETCH for msgseq %d for %s", msgSeq, getLogId());
}
if (!flagSyncMsgSeqs.contains(msgSeq)) {
flagSyncMsgSeqs.add(msgSeq);
}
}
if (equalsIgnoreCase(responseType, "EXPUNGE")) {
long msgSeq = response.getLong(0);
if (msgSeq <= oldMessageCount) {
messageCountDelta = -1;
}
if (K9MailLib.isDebug()) {
Timber.d("Got untagged EXPUNGE for msgseq %d for %s", msgSeq, getLogId());
}
List<Long> newSeqs = new ArrayList<>();
Iterator<Long> flagIter = flagSyncMsgSeqs.iterator();
while (flagIter.hasNext()) {
long flagMsg = flagIter.next();
if (flagMsg >= msgSeq) {
flagIter.remove();
if (flagMsg > msgSeq) {
newSeqs.add(flagMsg);
}
}
}
flagSyncMsgSeqs.addAll(newSeqs);
List<Long> msgSeqs = new ArrayList<>(msgSeqUidMap.keySet());
Collections.sort(msgSeqs); // Have to do comparisons in order because of msgSeq reductions
for (long msgSeqNum : msgSeqs) {
if (K9MailLib.isDebug()) {
Timber.v("Comparing EXPUNGEd msgSeq %d to %d", msgSeq, msgSeqNum);
}
if (msgSeqNum == msgSeq) {
String uid = msgSeqUidMap.get(msgSeqNum);
if (K9MailLib.isDebug()) {
Timber.d("Scheduling removal of UID %s because msgSeq %d was expunged", uid, msgSeqNum);
}
removeMsgUids.add(uid);
msgSeqUidMap.remove(msgSeqNum);
} else if (msgSeqNum > msgSeq) {
String uid = msgSeqUidMap.get(msgSeqNum);
if (K9MailLib.isDebug()) {
Timber.d("Reducing msgSeq for UID %s from %d to %d", uid, msgSeqNum, (msgSeqNum - 1));
}
msgSeqUidMap.remove(msgSeqNum);
msgSeqUidMap.put(msgSeqNum - 1, uid);
}
}
}
} catch (Exception e) {
Timber.e(e, "Could not handle untagged FETCH for %s", getLogId());
}
}
return messageCountDelta;
}
private void syncMessages(int end) throws MessagingException {
long oldUidNext = getOldUidNext();
List<ImapMessage> messageList = getMessages(end, end, null, true, null);
if (messageList != null && messageList.size() > 0) {
long newUid = Long.parseLong(messageList.get(0).getUid());
if (K9MailLib.isDebug()) {
Timber.i("Got newUid %s for message %d on %s", newUid, end, getLogId());
}
long startUid = oldUidNext;
if (startUid < newUid - 10) {
startUid = newUid - 10;
}
if (startUid < 1) {
startUid = 1;
}
if (newUid >= startUid) {
if (K9MailLib.isDebug()) {
Timber.i("Needs sync from uid %d to %d for %s", startUid, newUid, getLogId());
}
List<Message> messages = new ArrayList<>();
for (long uid = startUid; uid <= newUid; uid++) {
ImapMessage message = new ImapMessage(Long.toString(uid));
messages.add(message);
}
if (!messages.isEmpty()) {
pushReceiver.messagesArrived(getServerId(), messages);
}
}
}
}
private void syncMessages(List<Long> flagSyncMsgSeqs) {
try {
Set<Long> messageSeqSet = new HashSet<>(flagSyncMsgSeqs);
List<? extends Message> messageList = getMessages(messageSeqSet, true, null);
List<Message> messages = new ArrayList<>(messageList);
pushReceiver.messagesFlagsChanged(getServerId(), messages);
} catch (Exception e) {
pushReceiver.pushError("Exception while processing Push untagged responses", e);
}
}
private void removeMessages(List<String> removeUids) {
List<Message> messages = new ArrayList<>(removeUids.size());
try {
List<ImapMessage> existingMessages = getMessagesFromUids(removeUids);
for (Message existingMessage : existingMessages) {
needsPoll = true;
msgSeqUidMap.clear();
String existingUid = existingMessage.getUid();
Timber.w("Message with UID %s still exists on server, not expunging", existingUid);
removeUids.remove(existingUid);
}
for (String uid : removeUids) {
ImapMessage message = new ImapMessage(uid);
try {
message.setFlag(Flag.DELETED, true);
} catch (MessagingException me) {
Timber.e("Unable to set DELETED flag on message %s", message.getUid());
}
messages.add(message);
}
pushReceiver.messagesRemoved(getServerId(), messages);
} catch (Exception e) {
Timber.e("Cannot remove EXPUNGEd messages");
}
}
private void syncFolderOnConnect() throws MessagingException {
processStoredUntaggedResponses();
if (messageCount == -1) {
throw new MessagingException("Message count = -1 for idling");
}
pushReceiver.syncFolder(getServerId());
}
private void notifyMessagesArrived(long startUid, long uidNext) {
if (K9MailLib.isDebug()) {
Timber.i("Needs sync from uid %d to %d for %s", startUid, uidNext, getLogId());
}
int count = (int) (uidNext - startUid);
List<Message> messages = new ArrayList<>(count);
for (long uid = startUid; uid < uidNext; uid++) {
ImapMessage message = new ImapMessage(Long.toString(uid));
messages.add(message);
}
pushReceiver.messagesArrived(getServerId(), messages);
}
private long getOldUidNext() {
long oldUidNext = -1L;
try {
String serializedPushState = pushReceiver.getPushState(getServerId());
ImapPushState pushState = ImapPushState.parse(serializedPushState);
oldUidNext = pushState.uidNext;
if (K9MailLib.isDebug()) {
Timber.i("Got oldUidNext %d for %s", oldUidNext, getLogId());
}
} catch (Exception e) {
Timber.e(e, "Unable to get oldUidNext for %s", getLogId());
}
return oldUidNext;
}
}
/**
* Ensure the DONE continuation is only sent when the IDLE command was sent and hasn't completed yet.
*/
private static class IdleStopper {
private boolean acceptDoneContinuation = false;
private ImapConnection imapConnection;
public synchronized void startAcceptingDoneContinuation(ImapConnection connection) {
if (connection == null) {
throw new NullPointerException("connection must not be null");
}
acceptDoneContinuation = true;
imapConnection = connection;
}
public synchronized void stopAcceptingDoneContinuation() {
acceptDoneContinuation = false;
imapConnection = null;
}
public synchronized void stopIdle() {
if (acceptDoneContinuation) {
acceptDoneContinuation = false;
sendDone();
}
}
private void sendDone() {
try {
imapConnection.setReadTimeout(SOCKET_READ_TIMEOUT);
imapConnection.sendContinuation("DONE");
} catch (IOException e) {
imapConnection.close();
}
}
}
}

View file

@ -1,44 +0,0 @@
package com.fsck.k9.mail.store.imap;
import timber.log.Timber;
class ImapPushState {
private static final long DEFAULT_UID_NEXT = -1L;
private static final String PUSH_STATE_PREFIX = "uidNext=";
private static final int PUSH_STATE_PREFIX_LENGTH = 8;
public final long uidNext;
public static ImapPushState parse(String pushState) {
if (pushState == null || !pushState.startsWith(PUSH_STATE_PREFIX)) {
return createDefaultImapPushState();
}
String value = pushState.substring(PUSH_STATE_PREFIX_LENGTH);
try {
long newUidNext = Long.parseLong(value);
return new ImapPushState(newUidNext);
} catch (NumberFormatException e) {
Timber.e(e, "Unable to part uidNext value %s", value);
}
return createDefaultImapPushState();
}
static ImapPushState createDefaultImapPushState() {
return new ImapPushState(DEFAULT_UID_NEXT);
}
public ImapPushState(long uidNext) {
this.uidNext = uidNext;
}
@Override
public String toString() {
return "uidNext=" + uidNext;
}
}

View file

@ -1,104 +0,0 @@
package com.fsck.k9.mail.store.imap;
import java.util.ArrayList;
import java.util.List;
import com.fsck.k9.mail.K9MailLib;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.power.PowerManager;
import timber.log.Timber;
public class ImapPusher implements Pusher {
private final ImapStore store;
private final PushReceiver pushReceiver;
private final PowerManager powerManager;
private final List<ImapFolderPusher> folderPushers = new ArrayList<>();
private long lastRefresh = -1;
public ImapPusher(ImapStore store, PushReceiver pushReceiver, PowerManager powerManager) {
this.store = store;
this.pushReceiver = pushReceiver;
this.powerManager = powerManager;
}
@Override
public void start(List<String> folderServerIds) {
synchronized (folderPushers) {
stop();
setLastRefresh(currentTimeMillis());
for (String folderName : folderServerIds) {
ImapFolderPusher pusher = createImapFolderPusher(folderName);
folderPushers.add(pusher);
pusher.start();
}
}
}
@Override
public void refresh() {
synchronized (folderPushers) {
for (ImapFolderPusher folderPusher : folderPushers) {
try {
folderPusher.refresh();
} catch (Exception e) {
Timber.e(e, "Got exception while refreshing for %s", folderPusher.getServerId());
}
}
}
}
@Override
public void stop() {
if (K9MailLib.isDebug()) {
Timber.i("Requested stop of IMAP pusher");
}
synchronized (folderPushers) {
for (ImapFolderPusher folderPusher : folderPushers) {
try {
if (K9MailLib.isDebug()) {
Timber.i("Requesting stop of IMAP folderPusher %s", folderPusher.getServerId());
}
folderPusher.stop();
} catch (Exception e) {
Timber.e(e, "Got exception while stopping %s", folderPusher.getServerId());
}
}
folderPushers.clear();
}
}
@Override
public int getRefreshInterval() {
return (store.getStoreConfig().getIdleRefreshMinutes() * 60 * 1000);
}
@Override
public long getLastRefresh() {
return lastRefresh;
}
@Override
public void setLastRefresh(long lastRefresh) {
this.lastRefresh = lastRefresh;
}
ImapFolderPusher createImapFolderPusher(String folderName) {
return new ImapFolderPusher(store, folderName, pushReceiver, powerManager);
}
long currentTimeMillis() {
return System.currentTimeMillis();
}
}

View file

@ -966,28 +966,6 @@ public class ImapFolderTest {
assertCommandIssued("UID STORE 1:* +FLAGS.SILENT (\\Seen)");
}
@Test
public void getNewPushState_withNewerUid_shouldReturnNewPushState() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapMessage message = createImapMessage("2");
String newPushState = folder.getNewPushState("uidNext=2", message);
assertEquals("uidNext=3", newPushState);
}
@Test
public void getNewPushState_withoutNewerUid_shouldReturnNull() throws Exception {
ImapFolder folder = createFolder("Folder");
prepareImapFolderForOpen(OPEN_MODE_RW);
ImapMessage message = createImapMessage("1");
String newPushState = folder.getNewPushState("uidNext=2", message);
assertNull(newPushState);
}
@Test
public void search_withFullTextSearchEnabled_shouldIssueRespectiveCommand() throws Exception {
ImapFolder folder = createFolder("Folder");

View file

@ -1,67 +0,0 @@
package com.fsck.k9.mail.store.imap;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class ImapPushStateTest {
@Test
public void parse_withValidArgument() throws Exception {
ImapPushState result = ImapPushState.parse("uidNext=42");
assertNotNull(result);
assertEquals(42L, result.uidNext);
}
@Test
public void parse_withNullArgument_shouldReturnUidNextOfMinusOne() throws Exception {
ImapPushState result = ImapPushState.parse(null);
assertNotNull(result);
assertEquals(-1L, result.uidNext);
}
@Test
public void parse_withEmptyArgument_shouldReturnUidNextOfMinusOne() throws Exception {
ImapPushState result = ImapPushState.parse("");
assertNotNull(result);
assertEquals(-1L, result.uidNext);
}
@Test
public void parse_withInvalidArgument_shouldReturnUidNextOfMinusOne() throws Exception {
ImapPushState result = ImapPushState.parse("xyz");
assertNotNull(result);
assertEquals(-1L, result.uidNext);
}
@Test
public void parse_withIncompleteArgument_shouldReturnUidNextOfMinusOne() throws Exception {
ImapPushState result = ImapPushState.parse("uidNext=");
assertNotNull(result);
assertEquals(-1L, result.uidNext);
}
@Test
public void parse_withoutIntegerAsUidNext_shouldReturnUidNextOfMinusOne() throws Exception {
ImapPushState result = ImapPushState.parse("uidNext=xyz");
assertNotNull(result);
assertEquals(-1L, result.uidNext);
}
@Test
public void toString_shouldReturnExpectedResult() throws Exception {
ImapPushState imapPushState = new ImapPushState(23L);
String result = imapPushState.toString();
assertEquals("uidNext=23", result);
}
}

View file

@ -1,181 +0,0 @@
package com.fsck.k9.mail.store.imap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.power.PowerManager;
import com.fsck.k9.mail.store.StoreConfig;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ImapPusherTest {
private ImapStore imapStore;
private TestImapPusher imapPusher;
@Before
public void setUp() throws Exception {
imapStore = mock(ImapStore.class);
PushReceiver pushReceiver = mock(PushReceiver.class);
PowerManager powerManager = mock(PowerManager.class);
imapPusher = new TestImapPusher(imapStore, pushReceiver, powerManager);
}
@Test
public void start_shouldSetLastRefreshToCurrentTime() throws Exception {
List<String> folderNames = Collections.singletonList("INBOX");
imapPusher.start(folderNames);
assertEquals(TestImapPusher.CURRENT_TIME_MILLIS, imapPusher.getLastRefresh());
}
@Test
public void start_withSingleFolderName_shouldCreateImapFolderPusherAndCallStartOnIt() throws Exception {
List<String> folderNames = Collections.singletonList("INBOX");
imapPusher.start(folderNames);
List<ImapFolderPusher> imapFolderPushers = imapPusher.getImapFolderPushers();
assertEquals(1, imapFolderPushers.size());
ImapFolderPusher imapFolderPusher = imapFolderPushers.get(0);
verify(imapFolderPusher).start();
}
@Test
public void start_calledAfterStart_shouldStopFirstImapFolderPusher() throws Exception {
imapPusher.start(Collections.singletonList("Drafts"));
imapPusher.start(Collections.singletonList("INBOX"));
ImapFolderPusher draftsPusher = imapPusher.getImapFolderPushers().get(0);
verify(draftsPusher).stop();
}
@Test
public void start_withTwoFolderNames_shouldCreateTwoImapFolderPushersAndCallStart() throws Exception {
List<String> folderNames = Arrays.asList("Important", "Drafts");
imapPusher.start(folderNames);
List<ImapFolderPusher> imapFolderPushers = imapPusher.getImapFolderPushers();
assertEquals(2, imapFolderPushers.size());
ImapFolderPusher imapFolderPusherOne = imapFolderPushers.get(0);
ImapFolderPusher imapFolderPusherTwo = imapFolderPushers.get(1);
verify(imapFolderPusherOne).start();
verify(imapFolderPusherTwo).start();
}
@Test
public void stop_withoutStartBeingCalled_shouldNotCreateAnyImapFolderPushers() throws Exception {
imapPusher.stop();
List<ImapFolderPusher> imapFolderPushers = imapPusher.getImapFolderPushers();
assertEquals(0, imapFolderPushers.size());
}
@Test
public void stop_afterStartWithSingleFolderName_shouldStopImapFolderPusher() throws Exception {
List<String> folderNames = Collections.singletonList("Archive");
imapPusher.start(folderNames);
imapPusher.stop();
List<ImapFolderPusher> imapFolderPushers = imapPusher.getImapFolderPushers();
assertEquals(1, imapFolderPushers.size());
ImapFolderPusher imapFolderPusher = imapFolderPushers.get(0);
verify(imapFolderPusher).stop();
}
@Test
public void stop_withImapFolderPusherThrowing_shouldNotThrow() throws Exception {
List<String> folderNames = Collections.singletonList("Archive");
imapPusher.start(folderNames);
ImapFolderPusher imapFolderPusher = imapPusher.getImapFolderPushers().get(0);
doThrow(RuntimeException.class).when(imapFolderPusher).stop();
imapPusher.stop();
}
@Test
public void refresh_shouldCallRefreshOnStartedImapFolderPusher() throws Exception {
List<String> folderNames = Collections.singletonList("Trash");
imapPusher.start(folderNames);
imapPusher.refresh();
List<ImapFolderPusher> imapFolderPushers = imapPusher.getImapFolderPushers();
assertEquals(1, imapFolderPushers.size());
ImapFolderPusher imapFolderPusher = imapFolderPushers.get(0);
verify(imapFolderPusher).refresh();
}
@Test
public void refresh_withImapFolderPusherThrowing_shouldNotThrow() throws Exception {
List<String> folderNames = Collections.singletonList("Folder");
imapPusher.start(folderNames);
ImapFolderPusher imapFolderPusher = imapPusher.getImapFolderPushers().get(0);
doThrow(RuntimeException.class).when(imapFolderPusher).refresh();
imapPusher.refresh();
}
@Test
public void getRefreshInterval() throws Exception {
StoreConfig storeConfig = mock(StoreConfig.class);
when(storeConfig.getIdleRefreshMinutes()).thenReturn(23);
when(imapStore.getStoreConfig()).thenReturn(storeConfig);
int result = imapPusher.getRefreshInterval();
assertEquals(23 * 60 * 1000, result);
}
@Test
public void getLastRefresh_shouldBeMinusOneInitially() throws Exception {
long result = imapPusher.getLastRefresh();
assertEquals(-1L, result);
}
static class TestImapPusher extends ImapPusher {
public static final long CURRENT_TIME_MILLIS = 1454375675162L;
private final List<ImapFolderPusher> imapFolderPushers = new ArrayList<>();
public TestImapPusher(ImapStore store, PushReceiver receiver, PowerManager powerManager) {
super(store, receiver, powerManager);
}
@Override
ImapFolderPusher createImapFolderPusher(String folderName) {
ImapFolderPusher imapFolderPusher = mock(ImapFolderPusher.class);
imapFolderPushers.add(imapFolderPusher);
return imapFolderPusher;
}
public List<ImapFolderPusher> getImapFolderPushers() {
return imapFolderPushers;
}
@Override
long currentTimeMillis() {
return CURRENT_TIME_MILLIS;
}
}
}