|
|
|
@ -79,8 +79,6 @@ import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
import java.util.Properties;
|
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
import java.util.concurrent.ConcurrentMap;
|
|
|
|
|
import java.util.concurrent.Semaphore;
|
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
@ -115,7 +113,6 @@ import static androidx.core.app.NotificationCompat.DEFAULT_SOUND;
|
|
|
|
|
|
|
|
|
|
class Core {
|
|
|
|
|
private static int lastUnseen = -1;
|
|
|
|
|
private static ConcurrentMap<Long, Long> lockFolders = new ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
|
|
private static final int MAX_NOTIFICATION_COUNT = 100; // per group
|
|
|
|
|
private static final int SYNC_CHUNCK_SIZE = 200;
|
|
|
|
@ -167,7 +164,8 @@ class Core {
|
|
|
|
|
db.operation().setOperationState(op.id, "executing");
|
|
|
|
|
|
|
|
|
|
if (message == null) {
|
|
|
|
|
if (!EntityOperation.SYNC.equals(op.name) &&
|
|
|
|
|
if (!EntityOperation.FETCH.equals(op.name) &&
|
|
|
|
|
!EntityOperation.SYNC.equals(op.name) &&
|
|
|
|
|
!EntityOperation.SUBSCRIBE.equals(op.name))
|
|
|
|
|
throw new MessageRemovedException();
|
|
|
|
|
} else {
|
|
|
|
@ -177,6 +175,14 @@ class Core {
|
|
|
|
|
|
|
|
|
|
// Operations should use database transaction when needed
|
|
|
|
|
|
|
|
|
|
// Squash operations
|
|
|
|
|
if (i + 1 < ops.size() && op.canSquash(ops.get(i + 1))) {
|
|
|
|
|
Log.i(folder.name +
|
|
|
|
|
" squashing op=" + op.id + "/" + op.name +
|
|
|
|
|
" msg=" + op.message + " args=" + op.args);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (op.name) {
|
|
|
|
|
case EntityOperation.SEEN:
|
|
|
|
|
onSeen(context, jargs, folder, message, (IMAPFolder) ifolder);
|
|
|
|
@ -195,23 +201,6 @@ class Core {
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case EntityOperation.ADD:
|
|
|
|
|
boolean squash = false;
|
|
|
|
|
for (int j = i + 1; j < ops.size(); j++) {
|
|
|
|
|
EntityOperation next = ops.get(j);
|
|
|
|
|
if (next.message != null &&
|
|
|
|
|
next.message.equals(op.message) &&
|
|
|
|
|
(EntityOperation.ADD.equals(next.name) ||
|
|
|
|
|
EntityOperation.DELETE.equals(next.name))) {
|
|
|
|
|
squash = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (squash)
|
|
|
|
|
Log.i(folder.name +
|
|
|
|
|
" squashing op=" + op.id + "/" + op.name +
|
|
|
|
|
" msg=" + op.message +
|
|
|
|
|
" args=" + op.args);
|
|
|
|
|
else
|
|
|
|
|
onAdd(context, jargs, folder, message, (IMAPStore) istore, (IMAPFolder) ifolder);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -223,6 +212,10 @@ class Core {
|
|
|
|
|
onMove(context, jargs, true, folder, message, (IMAPStore) istore, (IMAPFolder) ifolder);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case EntityOperation.FETCH:
|
|
|
|
|
onFetch(context, jargs, folder, (IMAPFolder) ifolder, state);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case EntityOperation.DELETE:
|
|
|
|
|
onDelete(context, jargs, folder, message, (IMAPFolder) ifolder);
|
|
|
|
|
break;
|
|
|
|
@ -663,10 +656,45 @@ class Core {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void onFetch(Context context, JSONArray jargs, EntityFolder folder, IMAPFolder ifolder, State state) throws JSONException, MessagingException, IOException {
|
|
|
|
|
long uid = jargs.getLong(0);
|
|
|
|
|
|
|
|
|
|
DB db = DB.getInstance(context);
|
|
|
|
|
try {
|
|
|
|
|
EntityAccount account = db.account().getAccount(folder.account);
|
|
|
|
|
boolean download = db.folder().getFolderDownload(folder.id);
|
|
|
|
|
List<EntityRule> rules = db.rule().getEnabledRules(folder.id);
|
|
|
|
|
|
|
|
|
|
IMAPMessage imessage = (IMAPMessage) ifolder.getMessageByUID(uid);
|
|
|
|
|
if (imessage == null)
|
|
|
|
|
throw new MessageRemovedException();
|
|
|
|
|
|
|
|
|
|
FetchProfile fp = new FetchProfile();
|
|
|
|
|
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);
|
|
|
|
|
ifolder.fetch(new Message[]{imessage}, fp);
|
|
|
|
|
|
|
|
|
|
EntityMessage message = synchronizeMessage(context, account, folder, ifolder, imessage, false, download, rules, state);
|
|
|
|
|
if (download)
|
|
|
|
|
downloadMessage(context, folder, ifolder, imessage, message.id, state);
|
|
|
|
|
|
|
|
|
|
imessage.invalidateHeaders();
|
|
|
|
|
} finally {
|
|
|
|
|
int count = ifolder.getMessageCount();
|
|
|
|
|
db.folder().setFolderTotal(folder.id, count < 0 ? null : count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void onDelete(Context context, JSONArray jargs, EntityFolder folder, EntityMessage message, IMAPFolder ifolder) throws MessagingException {
|
|
|
|
|
// Delete message
|
|
|
|
|
DB db = DB.getInstance(context);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
boolean deleted = false;
|
|
|
|
|
|
|
|
|
|
if (message.uid != null) {
|
|
|
|
@ -702,6 +730,10 @@ class Core {
|
|
|
|
|
ifolder.expunge();
|
|
|
|
|
|
|
|
|
|
db.message().deleteMessage(message.id);
|
|
|
|
|
} finally {
|
|
|
|
|
int count = ifolder.getMessageCount();
|
|
|
|
|
db.folder().setFolderTotal(folder.id, count < 0 ? null : count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void onHeaders(Context context, EntityFolder folder, EntityMessage message, IMAPFolder ifolder) throws MessagingException {
|
|
|
|
@ -1387,9 +1419,6 @@ class Core {
|
|
|
|
|
IMAPFolder ifolder, IMAPMessage imessage,
|
|
|
|
|
boolean browsed, boolean download,
|
|
|
|
|
List<EntityRule> rules, State state) throws MessagingException, IOException {
|
|
|
|
|
// Instead of locking the database while performing message I/O
|
|
|
|
|
lockFolders.putIfAbsent(folder.id, folder.id);
|
|
|
|
|
synchronized (lockFolders.get(folder.id)) {
|
|
|
|
|
long uid = ifolder.getUID(imessage);
|
|
|
|
|
|
|
|
|
|
if (imessage.isExpunged()) {
|
|
|
|
@ -1704,7 +1733,6 @@ class Core {
|
|
|
|
|
|
|
|
|
|
return message;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static EntityIdentity matchIdentity(Context context, EntityFolder folder, EntityMessage message) {
|
|
|
|
|
DB db = DB.getInstance(context);
|
|
|
|
|