diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index 13376ddc55..8ffd838551 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -411,10 +411,6 @@ class Core { messages.addAll(similar.values()); switch (op.name) { - case EntityOperation.MOVE: - onMove(context, jargs, account, folder, messages, (POP3Folder) ifolder, (POP3Store) istore, state); - break; - case EntityOperation.DELETE: onDelete(context, jargs, account, folder, messages, (POP3Folder) ifolder, (POP3Store) istore, state); break; @@ -1676,49 +1672,6 @@ class Core { } } - private static void onMove(Context context, JSONArray jargs, EntityAccount account, EntityFolder folder, List messages, POP3Folder ifolder, POP3Store istore, State state) throws JSONException, FolderNotFoundException, IOException, MessagingException { - // Move message - DB db = DB.getInstance(context); - - // Get arguments - long id = jargs.getLong(0); - boolean seen = jargs.optBoolean(1); - boolean unflag = jargs.optBoolean(3); - - // Get target folder - EntityFolder target = db.folder().getFolder(id); - if (target == null) - throw new FolderNotFoundException(); - if (folder.id.equals(target.id)) - throw new IllegalArgumentException("self"); - - if (Boolean.TRUE.equals(account.leave_deleted) && - EntityFolder.INBOX.equals(folder.type) && - EntityFolder.TRASH.equals(target.type)) { - onDelete(context, jargs, account, folder, messages, ifolder, istore, state); - return; - } - - // Move from drafts/sent/trash+leave_delete only - if (!EntityFolder.DRAFTS.equals(folder.type) && - !EntityFolder.SENT.equals(folder.type) && - !(EntityFolder.TRASH.equals(folder.type) && account.leave_deleted)) - throw new IllegalArgumentException("Invalid POP3 folder" + - " source=" + folder.type + " target=" + target.type + - " leave deleted=" + account.leave_deleted); - - for (EntityMessage message : messages) { - message.folder = target.id; - if (seen) - message.ui_seen = seen; - if (unflag) - message.ui_flagged = false; - message.ui_hide = false; - - db.message().updateMessage(message); - } - } - private static void onFetch(Context context, JSONArray jargs, EntityFolder folder, IMAPStore istore, IMAPFolder ifolder, State state) throws JSONException, MessagingException, IOException { long uid = jargs.getLong(0); boolean invalidate = jargs.optBoolean(1); @@ -1942,85 +1895,28 @@ class Core { } private static void onDelete(Context context, JSONArray jargs, EntityAccount account, EntityFolder folder, List messages, POP3Folder ifolder, POP3Store istore, State state) throws MessagingException, IOException { - // Delete message - DB db = DB.getInstance(context); - // Delete from server - if (EntityFolder.INBOX.equals(folder.type) && !account.leave_deleted) { - Map map = findMessages(context, folder, messages, istore, ifolder); - for (EntityMessage message : messages) { - Message imessage = map.get(message); - if (imessage != null) { - Log.i(folder.name + " POP delete=" + message.uidl + "/" + message.msgid); - imessage.setFlag(Flags.Flag.DELETED, true); - } - } - - if (map.size() > 0) - try { - Log.i(folder.name + " POP expunge"); - ifolder.close(true); - ifolder.open(Folder.READ_WRITE); - } catch (Throwable ex) { - Log.e(ex); - state.error(new FolderClosedException(ifolder, "POP", new Exception(ex))); - } - } + if (!EntityFolder.INBOX.equals(folder.type) || account.leave_deleted) + throw new IllegalArgumentException("POP3: invalid DELETE"); + Map map = findMessages(context, folder, messages, istore, ifolder); for (EntityMessage message : messages) { - // Move to trash folder - if (!EntityFolder.DRAFTS.equals(folder.type) && - !EntityFolder.TRASH.equals(folder.type)) { - EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH); - if (trash == null) { - trash = new EntityFolder(); - trash.account = account.id; - trash.name = context.getString(R.string.title_folder_trash); - trash.type = EntityFolder.TRASH; - trash.synchronize = false; - trash.unified = false; - trash.notify = false; - trash.sync_days = Integer.MAX_VALUE; - trash.keep_days = Integer.MAX_VALUE; - trash.initialize = 0; - trash.id = db.folder().insertFolder(trash); - } - - long id = message.id; - - message.id = null; - message.folder = trash.id; - message.msgid = null; // virtual message - message.ui_hide = false; - message.ui_seen = true; - message.id = db.message().insertMessage(message); - - try { - File source = EntityMessage.getFile(context, id); - File target = message.getFile(context); - Helper.copy(source, target); - } catch (IOException ex) { - Log.e(ex); - } - - EntityAttachment.copy(context, id, message.id); - - message.id = id; + Message imessage = map.get(message); + if (imessage != null) { + Log.i(folder.name + " POP delete=" + message.uidl + "/" + message.msgid); + imessage.setFlag(Flags.Flag.DELETED, true); } - - // Delete from device - if (EntityFolder.INBOX.equals(folder.type)) { - if (account.leave_deleted) { - // Remove message/attachments files on cleanup - db.message().resetMessageContent(message.id); - db.attachment().resetAvailable(message.id); - } - - // Synchronize will delete messages when needed - db.message().setMessageUiHide(message.id, true); - } else - db.message().deleteMessage(message.id); } + + if (map.size() > 0) + try { + Log.i(folder.name + " POP expunge"); + ifolder.close(true); + ifolder.open(Folder.READ_WRITE); + } catch (Throwable ex) { + Log.e(ex); + state.error(new FolderClosedException(ifolder, "POP", new Exception(ex))); + } } private static void onHeaders(Context context, JSONArray jargs, EntityFolder folder, EntityMessage message, IMAPFolder ifolder) throws MessagingException, IOException { diff --git a/app/src/main/java/eu/faircode/email/EntityOperation.java b/app/src/main/java/eu/faircode/email/EntityOperation.java index 1cffa72189..8df0ae9ad1 100644 --- a/app/src/main/java/eu/faircode/email/EntityOperation.java +++ b/app/src/main/java/eu/faircode/email/EntityOperation.java @@ -543,33 +543,9 @@ public class EntityOperation { } } - if (account != null) { - EntityAccount a = db.account().getAccount(account); - if (a != null && a.protocol == EntityAccount.TYPE_POP) { - // TODO: special cases for MOVE, DELETE, PURGE - - if (SEEN.equals(name) || - FLAG.equals(name) || - ANSWERED.equals(name) || - KEYWORD.equals(name) || - ADD.equals(name) || - REPORT.equals(name)) { - Log.i("POP3: skipping op=" + name); - return; - } - - if (DELETE.equals(name)) { - EntityFolder f = db.folder().getFolder(folder); - if (f != null && - (EntityFolder.DRAFTS.equals(f.type) || - EntityFolder.TRASH.equals(f.type))) { - Log.i("POP3: inline DELETE folder=" + f.name); - db.message().deleteMessage(message); - return; - } - } - } - } + // Check for offline POP3 operations + if (inlinePOP3(context, account, folder, message, name, jargs)) + return; EntityOperation op = new EntityOperation(); op.account = account; @@ -593,6 +569,149 @@ public class EntityOperation { Log.breadcrumb("queued", crumb); } + private static boolean inlinePOP3(Context context, Long account, long folder, Long message, String name, JSONArray jargs) { + if (account == null || message == null) + return false; + + DB db = DB.getInstance(context); + EntityAccount a = db.account().getAccount(account); + if (a == null || a.protocol != EntityAccount.TYPE_POP) + return false; + + // TODO: special case for PURGE + + if (SEEN.equals(name) || + FLAG.equals(name) || + ANSWERED.equals(name) || + KEYWORD.equals(name) || + ADD.equals(name) || + REPORT.equals(name)) { + Log.i("POP3: skipping op=" + name); + return true; + } + + if (MOVE.equals(name)) { + try { + long target = jargs.getLong(0); + boolean seen = jargs.optBoolean(1); + boolean unflag = jargs.optBoolean(3); + + EntityFolder f = db.folder().getFolder(folder); + EntityFolder t = db.folder().getFolder(target); + if (f == null || t == null || f.id.equals(t.id)) { + Log.e("POP3: invalid MOVE/folders"); + return true; + } + + if (a.leave_deleted && + EntityFolder.INBOX.equals(f.type) && + EntityFolder.TRASH.equals(t.type)) { + Log.i("POP3 convert MOVE into DELETE"); + name = DELETE; + } else { + EntityMessage m = db.message().getMessage(message); + if (m == null) { + Log.e("POP3: invalid MOVE/message"); + return true; + } + + Log.i("POP3: local MOVE " + f.type + " > " + t.type); + + m.folder = t.id; + if (seen) + m.ui_seen = seen; + if (unflag) + m.ui_flagged = false; + m.ui_hide = false; + + db.message().updateMessage(m); + return true; + } + } catch (JSONException ex) { + Log.e(ex); + return true; + } + } + + if (DELETE.equals(name)) { + EntityFolder f = db.folder().getFolder(folder); + EntityMessage m = db.message().getMessage(message); + if (f == null || m == null) { + Log.e("POP3: invalid DELETE"); + return true; + } + + if (!EntityFolder.DRAFTS.equals(f.type) && + !EntityFolder.TRASH.equals(f.type)) { + + Log.i("POP3: local TRASH " + f.type); + + EntityFolder trash = db.folder().getFolderByType(m.account, EntityFolder.TRASH); + if (trash == null) { + trash = new EntityFolder(); + trash.account = m.id; + trash.name = context.getString(R.string.title_folder_trash); + trash.type = EntityFolder.TRASH; + trash.synchronize = false; + trash.unified = false; + trash.notify = false; + trash.sync_days = Integer.MAX_VALUE; + trash.keep_days = Integer.MAX_VALUE; + trash.initialize = 0; + trash.id = db.folder().insertFolder(trash); + } + + long id = m.id; + + m.id = null; + m.folder = trash.id; + m.msgid = null; // virtual message + m.ui_hide = false; + m.ui_seen = true; + m.id = db.message().insertMessage(m); + + try { + File source = EntityMessage.getFile(context, id); + File target = m.getFile(context); + Helper.copy(source, target); + } catch (IOException ex) { + Log.e(ex); + } + + EntityAttachment.copy(context, id, m.id); + + m.id = id; + } + + // Delete from device + if (EntityFolder.INBOX.equals(f.type)) { + if (a.leave_deleted) { + // Remove message/attachments files on cleanup + Log.i("POP3: DELETE reset content"); + db.message().resetMessageContent(m.id); + db.attachment().resetAvailable(m.id); + } + + // Synchronize will delete messages when needed + Log.i("POP3: DELETE hide " + f.type); + db.message().setMessageUiHide(m.id, true); + } else { + Log.i("POP3: local DELETE " + f.type); + db.message().deleteMessage(m.id); + } + + if (EntityFolder.INBOX.equals(f.type) && !a.leave_deleted) { + Log.i("POP3: DELETE remote " + f.type); + return false; + } else { + Log.i("POP3: local only " + f.type); + return true; + } + } + + return false; + } + static void poll(Context context, long fid) throws JSONException { DB db = DB.getInstance(context);