|
|
|
@ -2460,6 +2460,7 @@ class Core {
|
|
|
|
|
boolean sync_flagged = prefs.getBoolean("sync_flagged", false);
|
|
|
|
|
boolean sync_kept = prefs.getBoolean("sync_kept", true);
|
|
|
|
|
boolean delete_unseen = prefs.getBoolean("delete_unseen", false);
|
|
|
|
|
boolean use_modseq = prefs.getBoolean("use_modseq", BuildConfig.DEBUG);
|
|
|
|
|
boolean perform_expunge = prefs.getBoolean("perform_expunge", true);
|
|
|
|
|
|
|
|
|
|
Log.i(folder.name + " start sync after=" + sync_days + "/" + keep_days +
|
|
|
|
@ -2482,6 +2483,23 @@ class Core {
|
|
|
|
|
Log.w(folder.name, ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// https://tools.ietf.org/html/rfc4551
|
|
|
|
|
// https://wiki.mozilla.org/Thunderbird:IMAP_RFC_4551_Implementation
|
|
|
|
|
boolean modified = true;
|
|
|
|
|
try {
|
|
|
|
|
if (use_modseq &&
|
|
|
|
|
!force && initialize == 0 &&
|
|
|
|
|
MessageHelper.hasCapability(ifolder, "CONDSTORE")) {
|
|
|
|
|
long modseq = ifolder.getHighestModSeq();
|
|
|
|
|
Log.i(folder.name + " modseq=" + modseq + "/" + folder.modseq);
|
|
|
|
|
modified = (folder.modseq == null || !folder.modseq.equals(modseq));
|
|
|
|
|
folder.modseq = modseq;
|
|
|
|
|
db.folder().setFolderModSeq(folder.id, folder.modseq);
|
|
|
|
|
}
|
|
|
|
|
} catch (MessagingException ex) {
|
|
|
|
|
Log.w(folder.name, ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get reference times
|
|
|
|
|
Calendar cal_sync = Calendar.getInstance();
|
|
|
|
|
cal_sync.add(Calendar.DAY_OF_MONTH, -sync_days);
|
|
|
|
@ -2562,284 +2580,324 @@ class Core {
|
|
|
|
|
stats.search_ms = (SystemClock.elapsedRealtime() - search);
|
|
|
|
|
Log.i(folder.name + " remote count=" + imessages.length + " search=" + stats.search_ms + " ms");
|
|
|
|
|
|
|
|
|
|
long fetch = SystemClock.elapsedRealtime();
|
|
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
|
fp.add(UIDFolder.FetchProfileItem.UID); // To check if message exists
|
|
|
|
|
fp.add(FetchProfile.Item.FLAGS); // To update existing messages
|
|
|
|
|
if (account.isGmail())
|
|
|
|
|
fp.add(GmailFolder.FetchProfileItem.LABELS);
|
|
|
|
|
ifolder.fetch(imessages, fp);
|
|
|
|
|
|
|
|
|
|
stats.flags = imessages.length;
|
|
|
|
|
stats.flags_ms = (SystemClock.elapsedRealtime() - fetch);
|
|
|
|
|
Log.i(folder.name + " remote fetched=" + stats.flags_ms + " ms");
|
|
|
|
|
|
|
|
|
|
// Sort for finding referenced/replied-to messages
|
|
|
|
|
// Sorting on date/time would be better, but requires fetching the headers
|
|
|
|
|
Arrays.sort(imessages, new Comparator<Message>() {
|
|
|
|
|
@Override
|
|
|
|
|
public int compare(Message m1, Message m2) {
|
|
|
|
|
try {
|
|
|
|
|
return Long.compare(ifolder.getUID(m1), ifolder.getUID(m2));
|
|
|
|
|
} catch (MessagingException ex) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
Long[] ids = new Long[imessages.length];
|
|
|
|
|
if (!modified) {
|
|
|
|
|
Log.i(folder.name + " quick check");
|
|
|
|
|
long fetch = SystemClock.elapsedRealtime();
|
|
|
|
|
|
|
|
|
|
int expunge = 0;
|
|
|
|
|
for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++)
|
|
|
|
|
try {
|
|
|
|
|
if (perform_expunge && imessages[i].isSet(Flags.Flag.DELETED))
|
|
|
|
|
expunge++;
|
|
|
|
|
else
|
|
|
|
|
uids.remove(ifolder.getUID(imessages[i]));
|
|
|
|
|
} catch (MessageRemovedException ex) {
|
|
|
|
|
Log.w(folder.name, ex);
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.e(folder.name, ex);
|
|
|
|
|
EntityLog.log(context, folder.name + " " + Log.formatThrowable(ex, false));
|
|
|
|
|
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
|
|
|
|
}
|
|
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
|
fp.add(UIDFolder.FetchProfileItem.UID);
|
|
|
|
|
ifolder.fetch(imessages, fp);
|
|
|
|
|
|
|
|
|
|
stats.flags = imessages.length;
|
|
|
|
|
stats.flags_ms = (SystemClock.elapsedRealtime() - fetch);
|
|
|
|
|
Log.i(folder.name + " remote fetched=" + stats.flags_ms + " ms");
|
|
|
|
|
|
|
|
|
|
if (expunge > 0)
|
|
|
|
|
try {
|
|
|
|
|
Log.i(folder.name + " expunging=" + expunge);
|
|
|
|
|
ifolder.expunge();
|
|
|
|
|
for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++) {
|
|
|
|
|
long uid = ifolder.getUID(imessages[i]);
|
|
|
|
|
ids[i] = db.message().getMessageMsgIdByUid(folder.id, uid);
|
|
|
|
|
if (ids[i] == null) {
|
|
|
|
|
Log.i(folder.name + " missing uid=" + uid);
|
|
|
|
|
modified = true;
|
|
|
|
|
break;
|
|
|
|
|
} else
|
|
|
|
|
uids.remove(uid);
|
|
|
|
|
}
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.w(ex);
|
|
|
|
|
modified = true;
|
|
|
|
|
db.folder().setFolderModSeq(folder.id, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (uids.size() > 0) {
|
|
|
|
|
// This is done outside of JavaMail to prevent changed notifications
|
|
|
|
|
if (!ifolder.isOpen())
|
|
|
|
|
throw new FolderClosedException(ifolder, "UID FETCH");
|
|
|
|
|
if (uids.size() > 0) {
|
|
|
|
|
Log.i(folder.name + " remaining=" + uids.size());
|
|
|
|
|
modified = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EntityLog.log(context, folder.name + " modified=" + modified);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (modified) {
|
|
|
|
|
long fetch = SystemClock.elapsedRealtime();
|
|
|
|
|
|
|
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
|
fp.add(UIDFolder.FetchProfileItem.UID); // To check if message exists
|
|
|
|
|
fp.add(FetchProfile.Item.FLAGS); // To update existing messages
|
|
|
|
|
if (account.isGmail())
|
|
|
|
|
fp.add(GmailFolder.FetchProfileItem.LABELS);
|
|
|
|
|
ifolder.fetch(imessages, fp);
|
|
|
|
|
|
|
|
|
|
stats.flags = imessages.length;
|
|
|
|
|
stats.flags_ms = (SystemClock.elapsedRealtime() - fetch);
|
|
|
|
|
Log.i(folder.name + " remote fetched=" + stats.flags_ms + " ms");
|
|
|
|
|
|
|
|
|
|
long getuid = SystemClock.elapsedRealtime();
|
|
|
|
|
MessagingException ex = (MessagingException) ifolder.doCommand(new IMAPFolder.ProtocolCommand() {
|
|
|
|
|
// Sort for finding referenced/replied-to messages
|
|
|
|
|
// Sorting on date/time would be better, but requires fetching the headers
|
|
|
|
|
Arrays.sort(imessages, new Comparator<Message>() {
|
|
|
|
|
@Override
|
|
|
|
|
public Object doCommand(IMAPProtocol protocol) {
|
|
|
|
|
public int compare(Message m1, Message m2) {
|
|
|
|
|
try {
|
|
|
|
|
protocol.select(folder.name);
|
|
|
|
|
} catch (ProtocolException ex) {
|
|
|
|
|
return new MessagingException("UID FETCH", ex);
|
|
|
|
|
return Long.compare(ifolder.getUID(m1), ifolder.getUID(m2));
|
|
|
|
|
} catch (MessagingException ex) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Build ranges
|
|
|
|
|
List<Pair<Long, Long>> ranges = new ArrayList<>();
|
|
|
|
|
long first = -1;
|
|
|
|
|
long last = -1;
|
|
|
|
|
for (long uid : uids)
|
|
|
|
|
if (first < 0)
|
|
|
|
|
first = uid;
|
|
|
|
|
else if ((last < 0 ? first : last) + 1 == uid)
|
|
|
|
|
last = uid;
|
|
|
|
|
else {
|
|
|
|
|
ranges.add(new Pair<>(first, last < 0 ? first : last));
|
|
|
|
|
first = uid;
|
|
|
|
|
last = -1;
|
|
|
|
|
}
|
|
|
|
|
if (first > 0)
|
|
|
|
|
ranges.add(new Pair<>(first, last < 0 ? first : last));
|
|
|
|
|
int expunge = 0;
|
|
|
|
|
for (int i = 0; i < imessages.length && state.isRunning() && state.isRecoverable(); i++)
|
|
|
|
|
try {
|
|
|
|
|
if (perform_expunge && imessages[i].isSet(Flags.Flag.DELETED))
|
|
|
|
|
expunge++;
|
|
|
|
|
else
|
|
|
|
|
uids.remove(ifolder.getUID(imessages[i]));
|
|
|
|
|
} catch (MessageRemovedException ex) {
|
|
|
|
|
Log.w(folder.name, ex);
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.e(folder.name, ex);
|
|
|
|
|
EntityLog.log(context, folder.name + " " + Log.formatThrowable(ex, false));
|
|
|
|
|
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<List<Pair<Long, Long>>> chunks = Helper.chunkList(ranges, SYNC_CHUNCK_SIZE);
|
|
|
|
|
if (expunge > 0)
|
|
|
|
|
try {
|
|
|
|
|
Log.i(folder.name + " expunging=" + expunge);
|
|
|
|
|
ifolder.expunge();
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.w(ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.i(folder.name + " executing uid fetch count=" + uids.size() +
|
|
|
|
|
" ranges=" + ranges.size() + " chunks=" + chunks.size());
|
|
|
|
|
for (int c = 0; c < chunks.size(); c++) {
|
|
|
|
|
List<Pair<Long, Long>> chunk = chunks.get(c);
|
|
|
|
|
Log.i(folder.name + " chunk #" + c + " size=" + chunk.size());
|
|
|
|
|
if (uids.size() > 0) {
|
|
|
|
|
// This is done outside of JavaMail to prevent changed notifications
|
|
|
|
|
if (!ifolder.isOpen())
|
|
|
|
|
throw new FolderClosedException(ifolder, "UID FETCH");
|
|
|
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (Pair<Long, Long> range : chunk) {
|
|
|
|
|
if (sb.length() > 0)
|
|
|
|
|
sb.append(',');
|
|
|
|
|
if (range.first.equals(range.second))
|
|
|
|
|
sb.append(range.first);
|
|
|
|
|
else
|
|
|
|
|
sb.append(range.first).append(':').append(range.second);
|
|
|
|
|
long getuid = SystemClock.elapsedRealtime();
|
|
|
|
|
MessagingException ex = (MessagingException) ifolder.doCommand(new IMAPFolder.ProtocolCommand() {
|
|
|
|
|
@Override
|
|
|
|
|
public Object doCommand(IMAPProtocol protocol) {
|
|
|
|
|
try {
|
|
|
|
|
protocol.select(folder.name);
|
|
|
|
|
} catch (ProtocolException ex) {
|
|
|
|
|
return new MessagingException("UID FETCH", ex);
|
|
|
|
|
}
|
|
|
|
|
String command = "UID FETCH " + sb + " (UID FLAGS)";
|
|
|
|
|
Response[] responses = protocol.command(command, null);
|
|
|
|
|
|
|
|
|
|
if (responses.length > 0 && responses[responses.length - 1].isOK()) {
|
|
|
|
|
for (Response response : responses)
|
|
|
|
|
if (response instanceof FetchResponse) {
|
|
|
|
|
FetchResponse fr = (FetchResponse) response;
|
|
|
|
|
UID uid = fr.getItem(UID.class);
|
|
|
|
|
FLAGS flags = fr.getItem(FLAGS.class);
|
|
|
|
|
if (uid == null || flags == null)
|
|
|
|
|
continue;
|
|
|
|
|
if (perform_expunge && flags.contains(Flags.Flag.DELETED))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
uids.remove(uid.uid);
|
|
|
|
|
|
|
|
|
|
if (force) {
|
|
|
|
|
EntityMessage message = db.message().getMessageByUid(folder.id, uid.uid);
|
|
|
|
|
if (message != null) {
|
|
|
|
|
boolean update = false;
|
|
|
|
|
boolean seen = flags.contains(Flags.Flag.SEEN);
|
|
|
|
|
boolean answered = flags.contains(Flags.Flag.ANSWERED);
|
|
|
|
|
boolean flagged = flags.contains(Flags.Flag.FLAGGED);
|
|
|
|
|
boolean deleted = flags.contains(Flags.Flag.DELETED);
|
|
|
|
|
if (message.seen != seen) {
|
|
|
|
|
update = true;
|
|
|
|
|
message.seen = seen;
|
|
|
|
|
message.ui_seen = seen;
|
|
|
|
|
Log.i("UID fetch seen=" + seen);
|
|
|
|
|
}
|
|
|
|
|
if (message.answered != answered) {
|
|
|
|
|
update = true;
|
|
|
|
|
message.answered = answered;
|
|
|
|
|
message.ui_answered = answered;
|
|
|
|
|
Log.i("UID fetch answered=" + answered);
|
|
|
|
|
}
|
|
|
|
|
if (message.flagged != flagged) {
|
|
|
|
|
update = true;
|
|
|
|
|
message.flagged = flagged;
|
|
|
|
|
message.ui_flagged = flagged;
|
|
|
|
|
Log.i("UID fetch flagged=" + flagged);
|
|
|
|
|
}
|
|
|
|
|
if (message.deleted != deleted) {
|
|
|
|
|
update = true;
|
|
|
|
|
message.deleted = deleted;
|
|
|
|
|
message.ui_deleted = deleted;
|
|
|
|
|
Log.i("UID fetch deleted=" + deleted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update)
|
|
|
|
|
db.message().updateMessage(message);
|
|
|
|
|
// Build ranges
|
|
|
|
|
List<Pair<Long, Long>> ranges = new ArrayList<>();
|
|
|
|
|
long first = -1;
|
|
|
|
|
long last = -1;
|
|
|
|
|
for (long uid : uids)
|
|
|
|
|
if (first < 0)
|
|
|
|
|
first = uid;
|
|
|
|
|
else if ((last < 0 ? first : last) + 1 == uid)
|
|
|
|
|
last = uid;
|
|
|
|
|
else {
|
|
|
|
|
ranges.add(new Pair<>(first, last < 0 ? first : last));
|
|
|
|
|
first = uid;
|
|
|
|
|
last = -1;
|
|
|
|
|
}
|
|
|
|
|
if (first > 0)
|
|
|
|
|
ranges.add(new Pair<>(first, last < 0 ? first : last));
|
|
|
|
|
|
|
|
|
|
List<List<Pair<Long, Long>>> chunks = Helper.chunkList(ranges, SYNC_CHUNCK_SIZE);
|
|
|
|
|
|
|
|
|
|
Log.i(folder.name + " executing uid fetch count=" + uids.size() +
|
|
|
|
|
" ranges=" + ranges.size() + " chunks=" + chunks.size());
|
|
|
|
|
for (int c = 0; c < chunks.size(); c++) {
|
|
|
|
|
List<Pair<Long, Long>> chunk = chunks.get(c);
|
|
|
|
|
Log.i(folder.name + " chunk #" + c + " size=" + chunk.size());
|
|
|
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
for (Pair<Long, Long> range : chunk) {
|
|
|
|
|
if (sb.length() > 0)
|
|
|
|
|
sb.append(',');
|
|
|
|
|
if (range.first.equals(range.second))
|
|
|
|
|
sb.append(range.first);
|
|
|
|
|
else
|
|
|
|
|
sb.append(range.first).append(':').append(range.second);
|
|
|
|
|
}
|
|
|
|
|
String command = "UID FETCH " + sb + " (UID FLAGS)";
|
|
|
|
|
Response[] responses = protocol.command(command, null);
|
|
|
|
|
|
|
|
|
|
if (responses.length > 0 && responses[responses.length - 1].isOK()) {
|
|
|
|
|
for (Response response : responses)
|
|
|
|
|
if (response instanceof FetchResponse) {
|
|
|
|
|
FetchResponse fr = (FetchResponse) response;
|
|
|
|
|
UID uid = fr.getItem(UID.class);
|
|
|
|
|
FLAGS flags = fr.getItem(FLAGS.class);
|
|
|
|
|
if (uid == null || flags == null)
|
|
|
|
|
continue;
|
|
|
|
|
if (perform_expunge && flags.contains(Flags.Flag.DELETED))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
uids.remove(uid.uid);
|
|
|
|
|
|
|
|
|
|
if (force) {
|
|
|
|
|
EntityMessage message = db.message().getMessageByUid(folder.id, uid.uid);
|
|
|
|
|
if (message != null) {
|
|
|
|
|
boolean update = false;
|
|
|
|
|
boolean seen = flags.contains(Flags.Flag.SEEN);
|
|
|
|
|
boolean answered = flags.contains(Flags.Flag.ANSWERED);
|
|
|
|
|
boolean flagged = flags.contains(Flags.Flag.FLAGGED);
|
|
|
|
|
boolean deleted = flags.contains(Flags.Flag.DELETED);
|
|
|
|
|
if (message.seen != seen) {
|
|
|
|
|
update = true;
|
|
|
|
|
message.seen = seen;
|
|
|
|
|
message.ui_seen = seen;
|
|
|
|
|
Log.i("UID fetch seen=" + seen);
|
|
|
|
|
}
|
|
|
|
|
if (message.answered != answered) {
|
|
|
|
|
update = true;
|
|
|
|
|
message.answered = answered;
|
|
|
|
|
message.ui_answered = answered;
|
|
|
|
|
Log.i("UID fetch answered=" + answered);
|
|
|
|
|
}
|
|
|
|
|
if (message.flagged != flagged) {
|
|
|
|
|
update = true;
|
|
|
|
|
message.flagged = flagged;
|
|
|
|
|
message.ui_flagged = flagged;
|
|
|
|
|
Log.i("UID fetch flagged=" + flagged);
|
|
|
|
|
}
|
|
|
|
|
if (message.deleted != deleted) {
|
|
|
|
|
update = true;
|
|
|
|
|
message.deleted = deleted;
|
|
|
|
|
message.ui_deleted = deleted;
|
|
|
|
|
Log.i("UID fetch deleted=" + deleted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (update)
|
|
|
|
|
db.message().updateMessage(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (Response response : responses)
|
|
|
|
|
if (response.isBYE())
|
|
|
|
|
return new MessagingException("UID FETCH", new IOException(response.toString()));
|
|
|
|
|
else if (response.isNO() || response.isBAD())
|
|
|
|
|
return new MessagingException(response.toString());
|
|
|
|
|
return new MessagingException("UID FETCH failed");
|
|
|
|
|
} else {
|
|
|
|
|
for (Response response : responses)
|
|
|
|
|
if (response.isBYE())
|
|
|
|
|
return new MessagingException("UID FETCH", new IOException(response.toString()));
|
|
|
|
|
else if (response.isNO() || response.isBAD())
|
|
|
|
|
return new MessagingException(response.toString());
|
|
|
|
|
return new MessagingException("UID FETCH failed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (ex != null)
|
|
|
|
|
throw ex;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (ex != null)
|
|
|
|
|
throw ex;
|
|
|
|
|
|
|
|
|
|
stats.uids = uids.size();
|
|
|
|
|
stats.uids_ms = (SystemClock.elapsedRealtime() - getuid);
|
|
|
|
|
Log.i(folder.name + " remote uids=" + stats.uids_ms + " ms");
|
|
|
|
|
}
|
|
|
|
|
stats.uids = uids.size();
|
|
|
|
|
stats.uids_ms = (SystemClock.elapsedRealtime() - getuid);
|
|
|
|
|
Log.i(folder.name + " remote uids=" + stats.uids_ms + " ms");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete local messages not at remote
|
|
|
|
|
Log.i(folder.name + " delete=" + uids.size());
|
|
|
|
|
for (Long uid : uids) {
|
|
|
|
|
int count = db.message().deleteMessage(folder.id, uid);
|
|
|
|
|
Log.i(folder.name + " delete local uid=" + uid + " count=" + count);
|
|
|
|
|
}
|
|
|
|
|
// Delete local messages not at remote
|
|
|
|
|
Log.i(folder.name + " delete=" + uids.size());
|
|
|
|
|
for (Long uid : uids) {
|
|
|
|
|
int count = db.message().deleteMessage(folder.id, uid);
|
|
|
|
|
Log.i(folder.name + " delete local uid=" + uid + " count=" + count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<EntityRule> rules = db.rule().getEnabledRules(folder.id);
|
|
|
|
|
List<EntityRule> rules = db.rule().getEnabledRules(folder.id);
|
|
|
|
|
|
|
|
|
|
fp.add(FetchProfile.Item.ENVELOPE);
|
|
|
|
|
//fp.add(FetchProfile.Item.FLAGS);
|
|
|
|
|
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
|
|
|
|
|
//fp.add(UIDFolder.FetchProfileItem.UID);
|
|
|
|
|
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
|
|
|
|
|
//fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
|
|
|
|
|
fp.add(FetchProfile.Item.SIZE);
|
|
|
|
|
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
|
|
|
|
|
if (account.isGmail())
|
|
|
|
|
fp.add(GmailFolder.FetchProfileItem.THRID);
|
|
|
|
|
fp.add(FetchProfile.Item.ENVELOPE);
|
|
|
|
|
//fp.add(FetchProfile.Item.FLAGS);
|
|
|
|
|
fp.add(FetchProfile.Item.CONTENT_INFO); // body structure
|
|
|
|
|
//fp.add(UIDFolder.FetchProfileItem.UID);
|
|
|
|
|
fp.add(IMAPFolder.FetchProfileItem.HEADERS);
|
|
|
|
|
//fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
|
|
|
|
|
fp.add(FetchProfile.Item.SIZE);
|
|
|
|
|
fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
|
|
|
|
|
if (account.isGmail())
|
|
|
|
|
fp.add(GmailFolder.FetchProfileItem.THRID);
|
|
|
|
|
|
|
|
|
|
// Add/update local messages
|
|
|
|
|
int synced = 0;
|
|
|
|
|
Long[] ids = new Long[imessages.length];
|
|
|
|
|
Log.i(folder.name + " add=" + imessages.length);
|
|
|
|
|
for (int i = imessages.length - 1; i >= 0 && state.isRunning() && state.isRecoverable(); i -= SYNC_BATCH_SIZE) {
|
|
|
|
|
int from = Math.max(0, i - SYNC_BATCH_SIZE + 1);
|
|
|
|
|
Message[] isub = Arrays.copyOfRange(imessages, from, i + 1);
|
|
|
|
|
|
|
|
|
|
// Full fetch new/changed messages only
|
|
|
|
|
List<Message> full = new ArrayList<>();
|
|
|
|
|
for (Message imessage : isub) {
|
|
|
|
|
long uid = ifolder.getUID(imessage); // already fetched
|
|
|
|
|
EntityMessage message = db.message().getMessageByUid(folder.id, uid);
|
|
|
|
|
if (message == null)
|
|
|
|
|
full.add(imessage);
|
|
|
|
|
}
|
|
|
|
|
if (full.size() > 0) {
|
|
|
|
|
long headers = SystemClock.elapsedRealtime();
|
|
|
|
|
ifolder.fetch(full.toArray(new Message[0]), fp);
|
|
|
|
|
stats.headers += full.size();
|
|
|
|
|
stats.headers_ms += (SystemClock.elapsedRealtime() - headers);
|
|
|
|
|
Log.i(folder.name + " fetched headers=" + full.size() + " " + stats.headers_ms + " ms");
|
|
|
|
|
}
|
|
|
|
|
// Add/update local messages
|
|
|
|
|
int synced = 0;
|
|
|
|
|
Log.i(folder.name + " add=" + imessages.length);
|
|
|
|
|
for (int i = imessages.length - 1; i >= 0 && state.isRunning() && state.isRecoverable(); i -= SYNC_BATCH_SIZE) {
|
|
|
|
|
int from = Math.max(0, i - SYNC_BATCH_SIZE + 1);
|
|
|
|
|
Message[] isub = Arrays.copyOfRange(imessages, from, i + 1);
|
|
|
|
|
|
|
|
|
|
int free = Log.getFreeMemMb();
|
|
|
|
|
Map<String, String> crumb = new HashMap<>();
|
|
|
|
|
crumb.put("account", account.id + ":" + account.protocol);
|
|
|
|
|
crumb.put("folder", folder.id + ":" + folder.type);
|
|
|
|
|
crumb.put("start", Integer.toString(from));
|
|
|
|
|
crumb.put("end", Integer.toString(i));
|
|
|
|
|
crumb.put("free", Integer.toString(free));
|
|
|
|
|
crumb.put("partial", Boolean.toString(account.partial_fetch));
|
|
|
|
|
Log.breadcrumb("sync", crumb);
|
|
|
|
|
Log.i("Sync " + from + ".." + i + " free=" + free);
|
|
|
|
|
|
|
|
|
|
for (int j = isub.length - 1; j >= 0 && state.isRunning() && state.isRecoverable(); j--)
|
|
|
|
|
try {
|
|
|
|
|
// Some providers erroneously return old messages
|
|
|
|
|
if (full.contains(isub[j]))
|
|
|
|
|
try {
|
|
|
|
|
Date received = isub[j].getReceivedDate();
|
|
|
|
|
boolean unseen = (sync_unseen && !isub[j].isSet(Flags.Flag.SEEN));
|
|
|
|
|
boolean flagged = (sync_flagged && isub[j].isSet(Flags.Flag.FLAGGED));
|
|
|
|
|
if (received != null && received.getTime() < keep_time && !unseen && !flagged) {
|
|
|
|
|
long uid = ifolder.getUID(isub[j]);
|
|
|
|
|
Log.i(folder.name + " Skipping old uid=" + uid + " date=" + received);
|
|
|
|
|
ids[from + j] = null;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.w(ex);
|
|
|
|
|
}
|
|
|
|
|
// Full fetch new/changed messages only
|
|
|
|
|
List<Message> full = new ArrayList<>();
|
|
|
|
|
for (Message imessage : isub) {
|
|
|
|
|
long uid = ifolder.getUID(imessage); // already fetched
|
|
|
|
|
EntityMessage message = db.message().getMessageByUid(folder.id, uid);
|
|
|
|
|
if (message == null)
|
|
|
|
|
full.add(imessage);
|
|
|
|
|
}
|
|
|
|
|
if (full.size() > 0) {
|
|
|
|
|
long headers = SystemClock.elapsedRealtime();
|
|
|
|
|
ifolder.fetch(full.toArray(new Message[0]), fp);
|
|
|
|
|
stats.headers += full.size();
|
|
|
|
|
stats.headers_ms += (SystemClock.elapsedRealtime() - headers);
|
|
|
|
|
Log.i(folder.name + " fetched headers=" + full.size() + " " + stats.headers_ms + " ms");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EntityMessage message = synchronizeMessage(
|
|
|
|
|
context,
|
|
|
|
|
account, folder,
|
|
|
|
|
istore, ifolder, (MimeMessage) isub[j],
|
|
|
|
|
false, download && initialize == 0,
|
|
|
|
|
rules, state, stats);
|
|
|
|
|
ids[from + j] = (message == null || message.ui_hide ? null : message.id);
|
|
|
|
|
int free = Log.getFreeMemMb();
|
|
|
|
|
Map<String, String> crumb = new HashMap<>();
|
|
|
|
|
crumb.put("account", account.id + ":" + account.protocol);
|
|
|
|
|
crumb.put("folder", folder.id + ":" + folder.type);
|
|
|
|
|
crumb.put("start", Integer.toString(from));
|
|
|
|
|
crumb.put("end", Integer.toString(i));
|
|
|
|
|
crumb.put("free", Integer.toString(free));
|
|
|
|
|
crumb.put("partial", Boolean.toString(account.partial_fetch));
|
|
|
|
|
Log.breadcrumb("sync", crumb);
|
|
|
|
|
Log.i("Sync " + from + ".." + i + " free=" + free);
|
|
|
|
|
|
|
|
|
|
if (message != null && full.contains(isub[j]))
|
|
|
|
|
if ((++synced % SYNC_YIELD_COUNT) == 0)
|
|
|
|
|
for (int j = isub.length - 1; j >= 0 && state.isRunning() && state.isRecoverable(); j--)
|
|
|
|
|
try {
|
|
|
|
|
// Some providers erroneously return old messages
|
|
|
|
|
if (full.contains(isub[j]))
|
|
|
|
|
try {
|
|
|
|
|
Log.i(folder.name + " yield synced=" + synced);
|
|
|
|
|
Thread.sleep(SYNC_YIELD_DURATION);
|
|
|
|
|
} catch (InterruptedException ex) {
|
|
|
|
|
Date received = isub[j].getReceivedDate();
|
|
|
|
|
boolean unseen = (sync_unseen && !isub[j].isSet(Flags.Flag.SEEN));
|
|
|
|
|
boolean flagged = (sync_flagged && isub[j].isSet(Flags.Flag.FLAGGED));
|
|
|
|
|
if (received != null && received.getTime() < keep_time && !unseen && !flagged) {
|
|
|
|
|
long uid = ifolder.getUID(isub[j]);
|
|
|
|
|
Log.i(folder.name + " Skipping old uid=" + uid + " date=" + received);
|
|
|
|
|
ids[from + j] = null;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.w(ex);
|
|
|
|
|
}
|
|
|
|
|
} catch (MessageRemovedException ex) {
|
|
|
|
|
Log.w(folder.name, ex);
|
|
|
|
|
} catch (FolderClosedException ex) {
|
|
|
|
|
throw ex;
|
|
|
|
|
} catch (IOException ex) {
|
|
|
|
|
if (ex.getCause() instanceof MessagingException) {
|
|
|
|
|
|
|
|
|
|
EntityMessage message = synchronizeMessage(
|
|
|
|
|
context,
|
|
|
|
|
account, folder,
|
|
|
|
|
istore, ifolder, (MimeMessage) isub[j],
|
|
|
|
|
false, download && initialize == 0,
|
|
|
|
|
rules, state, stats);
|
|
|
|
|
ids[from + j] = (message == null || message.ui_hide ? null : message.id);
|
|
|
|
|
|
|
|
|
|
if (message != null && full.contains(isub[j]))
|
|
|
|
|
if ((++synced % SYNC_YIELD_COUNT) == 0)
|
|
|
|
|
try {
|
|
|
|
|
Log.i(folder.name + " yield synced=" + synced);
|
|
|
|
|
Thread.sleep(SYNC_YIELD_DURATION);
|
|
|
|
|
} catch (InterruptedException ex) {
|
|
|
|
|
Log.w(ex);
|
|
|
|
|
}
|
|
|
|
|
} catch (MessageRemovedException ex) {
|
|
|
|
|
Log.w(folder.name, ex);
|
|
|
|
|
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
|
|
|
|
} else
|
|
|
|
|
} catch (FolderClosedException ex) {
|
|
|
|
|
throw ex;
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.e(folder.name, ex);
|
|
|
|
|
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
|
|
|
|
} finally {
|
|
|
|
|
// Free memory
|
|
|
|
|
((IMAPMessage) isub[j]).invalidateHeaders();
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException ex) {
|
|
|
|
|
if (ex.getCause() instanceof MessagingException) {
|
|
|
|
|
Log.w(folder.name, ex);
|
|
|
|
|
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
|
|
|
|
} else
|
|
|
|
|
throw ex;
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
|
Log.e(folder.name, ex);
|
|
|
|
|
db.folder().setFolderError(folder.id, Log.formatThrowable(ex));
|
|
|
|
|
} finally {
|
|
|
|
|
// Free memory
|
|
|
|
|
((IMAPMessage) isub[j]).invalidateHeaders();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete not synchronized messages without uid
|
|
|
|
|