From 6f06d880829ffbd087d475eb74ec0e12ad5c542f Mon Sep 17 00:00:00 2001 From: M66B Date: Sat, 11 Aug 2018 14:13:29 +0000 Subject: [PATCH] Use MOVE again, refactored compose message, find by message ID --- app/schemas/eu.faircode.email.DB/1.json | 12 +- .../java/eu/faircode/email/DaoMessage.java | 10 +- .../java/eu/faircode/email/EntityMessage.java | 12 + .../eu/faircode/email/FragmentCompose.java | 378 ++++++++---------- .../eu/faircode/email/FragmentMessage.java | 50 +-- .../java/eu/faircode/email/MessageHelper.java | 20 +- .../java/eu/faircode/email/MimeMessageEx.java | 45 +++ .../eu/faircode/email/ServiceSynchronize.java | 91 +++-- 8 files changed, 314 insertions(+), 304 deletions(-) create mode 100644 app/src/main/java/eu/faircode/email/MimeMessageEx.java diff --git a/app/schemas/eu.faircode.email.DB/1.json b/app/schemas/eu.faircode.email.DB/1.json index 7e6cce59..8390c9a4 100644 --- a/app/schemas/eu.faircode.email.DB/1.json +++ b/app/schemas/eu.faircode.email.DB/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "9fd2cb9e7b45bf1dbddced278a737dfa", + "identityHash": "7814b856d44afe54b8912106df1e673b", "entities": [ { "tableName": "identity", @@ -478,6 +478,14 @@ ], "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)" }, + { + "name": "index_message_msgid", + "unique": true, + "columnNames": [ + "msgid" + ], + "createSql": "CREATE UNIQUE INDEX `index_message_msgid` ON `${TABLE_NAME}` (`msgid`)" + }, { "name": "index_message_thread", "unique": false, @@ -743,7 +751,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"9fd2cb9e7b45bf1dbddced278a737dfa\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7814b856d44afe54b8912106df1e673b\")" ] } } \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java index afb3324f..68a50b73 100644 --- a/app/src/main/java/eu/faircode/email/DaoMessage.java +++ b/app/src/main/java/eu/faircode/email/DaoMessage.java @@ -78,12 +78,14 @@ public interface DaoMessage { EntityMessage getMessage(long id); @Query("SELECT * FROM message WHERE folder = :folder AND uid = :uid") - EntityMessage getMessage(long folder, long uid); + EntityMessage getMessageByUid(long folder, long uid); @Query("SELECT message.* FROM message" + - " JOIN folder on folder.id = message.folder" + - " WHERE thread = :thread AND folder.type= '" + EntityFolder.ARCHIVE + "'") - EntityMessage getArchivedMessage(String thread); + " JOIN folder ON folder.id = message.folder" + + " WHERE message.account = :account" + + " AND folder.type <> '" + EntityFolder.ARCHIVE + "'" + + " AND msgid = :msgid") + EntityMessage getMessageByMsgId(long account, String msgid); @Query("SELECT message.*, folder.name as folderName, folder.type as folderType" + ", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_hide) AS count" + diff --git a/app/src/main/java/eu/faircode/email/EntityMessage.java b/app/src/main/java/eu/faircode/email/EntityMessage.java index c6771274..dd56c520 100644 --- a/app/src/main/java/eu/faircode/email/EntityMessage.java +++ b/app/src/main/java/eu/faircode/email/EntityMessage.java @@ -45,6 +45,7 @@ import static androidx.room.ForeignKey.CASCADE; @Index(value = {"identity"}), @Index(value = {"replying"}), @Index(value = {"folder", "uid"}, unique = true), + @Index(value = {"msgid"}, unique = true), @Index(value = {"thread"}), @Index(value = {"received"}), @Index(value = {"ui_seen"}), @@ -84,6 +85,17 @@ public class EntityMessage { public Boolean ui_hide; public String error; + String generateMessageId() { + StringBuffer sb = new StringBuffer(); + sb.append('<') + .append(id).append('.') + .append(BuildConfig.APPLICATION_ID).append('.') + .append(System.currentTimeMillis()).append('.') + .append("anonymous@localhost") + .append('>'); + return sb.toString(); + } + @Override public boolean equals(Object obj) { if (obj instanceof EntityMessage) { diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 19dff5eb..a52f192b 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -20,7 +20,6 @@ package eu.faircode.email; */ import android.Manifest; -import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; @@ -71,9 +70,6 @@ import androidx.core.content.ContextCompat; import androidx.cursoradapter.widget.SimpleCursorAdapter; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.Observer; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.AsyncTaskLoader; -import androidx.loader.content.Loader; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -101,6 +97,8 @@ public class FragmentCompose extends FragmentEx { private AdapterAttachment adapter; + private long id = -1; // draft id + private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes @Override @@ -282,9 +280,7 @@ public class FragmentCompose extends FragmentEx { @Override public void run() { // Get might select another identity - LoaderManager.getInstance(FragmentCompose.this) - .restartLoader(ActivityCompose.LOADER_COMPOSE_GET, getArguments(), getLoaderCallbacks).forceLoad(); - + getLoader.load(FragmentCompose.this, ActivityCompose.LOADER_COMPOSE_GET, getArguments()); } }); } @@ -387,7 +383,7 @@ public class FragmentCompose extends FragmentEx { private void handleAddAttachment(Intent data) { Bundle args = new Bundle(); - args.putLong("id", getArguments().getLong("id")); + args.putLong("id", id); args.putParcelable("uri", data.getData()); new SimpleLoader() { @@ -396,7 +392,7 @@ public class FragmentCompose extends FragmentEx { Cursor cursor = null; try { Uri uri = args.getParcelable("uri"); - cursor = getActivity().getContentResolver().query(uri, null, null, null, null, null); + cursor = getContext().getContentResolver().query(uri, null, null, null, null, null); if (cursor == null || !cursor.moveToFirst()) return null; @@ -466,7 +462,6 @@ public class FragmentCompose extends FragmentEx { private void onAction(int action) { bottom_navigation.getMenu().setGroupEnabled(0, false); - long id = getArguments().getLong("id", -1); EntityIdentity identity = (EntityIdentity) spFrom.getSelectedItem(); Bundle args = new Bundle(); @@ -479,37 +474,29 @@ public class FragmentCompose extends FragmentEx { args.putString("subject", etSubject.getText().toString()); args.putString("body", etBody.getText().toString()); - LoaderManager.getInstance(this) - .restartLoader(ActivityCompose.LOADER_COMPOSE_PUT, args, putLoaderCallbacks).forceLoad(); + putLoader.load(this, ActivityCompose.LOADER_COMPOSE_PUT, args); } - private static class GetLoader extends AsyncTaskLoader { - private Bundle args; - - GetLoader(Context context) { - super(context); - } + private SimpleLoader getLoader = new SimpleLoader() { + @Override + public EntityMessage onLoad(Bundle args) { + String action = args.getString("action"); + long id = args.getLong("id", -1); + long account = args.getLong("account", -1); + long reference = args.getLong("reference", -1); + Log.i(Helper.TAG, "Get action=" + action + " id=" + id + " account=" + account + " reference=" + reference); - void setArgs(Bundle args) { - this.args = args; - } + DB db = DB.getInstance(getContext()); - @Nullable - @Override - public EntityMessage loadInBackground() { - EntityMessage draft = null; - try { - String action = args.getString("action"); - long id = args.getLong("id", -1); - long account = args.getLong("account", -1); - long reference = args.getLong("reference", -1); - Log.i(Helper.TAG, "Get action=" + action + " id=" + id + " account=" + account + " reference=" + reference); + EntityMessage draft = db.message().getMessage(id); + if (draft == null) { + if ("edit".equals(action)) + throw new IllegalStateException("Message to edit not found"); - DB db = DB.getInstance(getContext()); + try { + db.beginTransaction(); - draft = db.message().getMessage(id); - if (draft == null) { - EntityMessage ref = DB.getInstance(getContext()).message().getMessage(reference); + EntityMessage ref = db.message().getMessage(reference); if (ref != null) account = ref.account; @@ -562,35 +549,35 @@ public class FragmentCompose extends FragmentEx { } } + if ("new".equals(action)) + draft.body = ""; + draft.received = new Date().getTime(); draft.seen = false; draft.ui_seen = false; draft.ui_hide = false; - draft.id = DB.getInstance(getContext()).message().insertMessage(draft); + draft.id = db.message().insertMessage(draft); + draft.msgid = draft.generateMessageId(); + db.message().updateMessage(draft); + args.putLong("id", draft.id); + + EntityOperation.queue(db, draft, EntityOperation.ADD); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); } - } catch (Throwable ex) { - Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + + EntityOperation.process(getContext()); } return draft; } - } - - private LoaderManager.LoaderCallbacks getLoaderCallbacks = new LoaderManager.LoaderCallbacks() { - @NonNull - @Override - public Loader onCreateLoader(int id, @Nullable Bundle args) { - GetLoader loader = new GetLoader(getContext()); - loader.setArgs(args); - return loader; - } @Override - public void onLoadFinished(@NonNull Loader loader, EntityMessage msg) { - LoaderManager.getInstance(FragmentCompose.this).destroyLoader(loader.getId()); - - getArguments().putLong("id", msg.id); + public void onLoaded(Bundle args, EntityMessage draft) { + id = draft.id; String action = getArguments().getString("action"); menuAttachment.setEnabled(true); @@ -602,23 +589,23 @@ public class FragmentCompose extends FragmentEx { if (aa != null) { for (int pos = 0; pos < aa.getCount(); pos++) { EntityIdentity identity = (EntityIdentity) aa.getItem(pos); - if (msg.identity == null - ? msg.from != null && msg.from.length > 0 && ((InternetAddress) msg.from[0]).getAddress().equals(identity.email) - : msg.identity.equals(identity.id)) { + if (draft.identity == null + ? draft.from != null && draft.from.length > 0 && ((InternetAddress) draft.from[0]).getAddress().equals(identity.email) + : draft.identity.equals(identity.id)) { spFrom.setSelection(pos); break; } } } - etTo.setText(msg.to == null ? null : TextUtils.join(", ", msg.to)); - etCc.setText(msg.cc == null ? null : TextUtils.join(", ", msg.cc)); - etBcc.setText(msg.bcc == null ? null : TextUtils.join(", ", msg.bcc)); - etSubject.setText(msg.subject); + etTo.setText(draft.to == null ? null : TextUtils.join(", ", draft.to)); + etCc.setText(draft.cc == null ? null : TextUtils.join(", ", draft.cc)); + etBcc.setText(draft.bcc == null ? null : TextUtils.join(", ", draft.bcc)); + etSubject.setText(draft.subject); DB db = DB.getInstance(getContext()); - db.attachment().liveAttachments(msg.id).removeObservers(getViewLifecycleOwner()); - db.attachment().liveAttachments(msg.id).observe(getViewLifecycleOwner(), + db.attachment().liveAttachments(draft.id).removeObservers(getViewLifecycleOwner()); + db.attachment().liveAttachments(draft.id).observe(getViewLifecycleOwner(), new Observer>() { @Override public void onChanged(@Nullable List attachments) { @@ -628,7 +615,7 @@ public class FragmentCompose extends FragmentEx { }); - etBody.setText(TextUtils.isEmpty(msg.body) ? null : Html.fromHtml(msg.body)); + etBody.setText(TextUtils.isEmpty(draft.body) ? null : Html.fromHtml(draft.body)); if ("edit".equals(action)) etTo.requestFocus(); @@ -641,177 +628,150 @@ public class FragmentCompose extends FragmentEx { } @Override - public void onLoaderReset(@NonNull Loader loader) { + public void onException(Bundle args, Throwable ex) { + Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); } }; - private static class PutLoader extends AsyncTaskLoader { - private Bundle args; + private SimpleLoader putLoader = new SimpleLoader() { + @Override + public EntityMessage onLoad(Bundle args) throws Throwable { + // Get data + long id = args.getLong("id"); + int action = args.getInt("action"); + long iid = args.getLong("identity"); + String to = args.getString("to"); + String cc = args.getString("cc"); + String bcc = args.getString("bcc"); + String subject = args.getString("subject"); + String body = args.getString("body"); + + // Get draft & selected identity + DB db = DB.getInstance(getContext()); + EntityMessage draft = db.message().getMessage(id); + EntityIdentity identity = db.identity().getIdentity(iid); + + // Convert data + Address afrom[] = (identity == null ? null : new Address[]{new InternetAddress(identity.email, identity.name)}); + Address ato[] = (TextUtils.isEmpty(to) ? null : InternetAddress.parse(to)); + Address acc[] = (TextUtils.isEmpty(cc) ? null : InternetAddress.parse(cc)); + Address abcc[] = (TextUtils.isEmpty(bcc) ? null : InternetAddress.parse(bcc)); + + // Update draft + draft.identity = (identity == null ? null : identity.id); + draft.from = afrom; + draft.to = ato; + draft.cc = acc; + draft.bcc = abcc; + draft.subject = subject; + draft.body = "
" + body.replaceAll("\\r?\\n", "
") + "
"; + draft.received = new Date().getTime(); + + db.message().updateMessage(draft); + + // Check data + try { + db.beginTransaction(); - PutLoader(Context context) { - super(context); - } + if (action == R.id.action_trash) { + draft.ui_hide = true; + db.message().updateMessage(draft); - void setArgs(Bundle args) { - this.args = args; - } + EntityFolder trash = db.folder().getFolderByType(draft.account, EntityFolder.TRASH); + EntityOperation.queue(db, draft, EntityOperation.MOVE, trash.id); - @Override - public Throwable loadInBackground() { - try { - // Get data - long id = args.getLong("id"); - int action = args.getInt("action"); - long iid = args.getLong("identity"); - String to = args.getString("to"); - String cc = args.getString("cc"); - String bcc = args.getString("bcc"); - String subject = args.getString("subject"); - String body = args.getString("body"); - - // Get draft & selected identity - DB db = DB.getInstance(getContext()); - EntityMessage draft = db.message().getMessage(id); - EntityIdentity identity = db.identity().getIdentity(iid); - - // Convert data - Address afrom[] = (identity == null ? null : new Address[]{new InternetAddress(identity.email, identity.name)}); - Address ato[] = (TextUtils.isEmpty(to) ? null : InternetAddress.parse(to)); - Address acc[] = (TextUtils.isEmpty(cc) ? null : InternetAddress.parse(cc)); - Address abcc[] = (TextUtils.isEmpty(bcc) ? null : InternetAddress.parse(bcc)); - - // Update draft - draft.identity = (identity == null ? null : identity.id); - draft.from = afrom; - draft.to = ato; - draft.cc = acc; - draft.bcc = abcc; - draft.subject = subject; - draft.body = "
" + body.replaceAll("\\r?\\n", "
") + "
"; - draft.received = new Date().getTime(); - - db.message().updateMessage(draft); - - // Check data - try { - db.beginTransaction(); + } else if (action == R.id.action_save) { + String msgid = draft.msgid; - if (action == R.id.action_trash) { - EntityFolder trash = db.folder().getFolderByType(draft.account, EntityFolder.TRASH); - - boolean move = (draft.uid != null); - if (move) - EntityOperation.queue(db, draft, EntityOperation.MOVE, trash.id, draft.uid); - - draft.folder = trash.id; - draft.uid = null; - db.message().updateMessage(draft); - - if (!move) - EntityOperation.queue(db, draft, EntityOperation.ADD); - - } else if (action == R.id.action_save) { - // Delete previous draft - draft.ui_hide = true; - db.message().updateMessage(draft); - EntityOperation.queue(db, draft, EntityOperation.DELETE); - - // Create new draft - draft.id = null; - draft.uid = null; - draft.ui_hide = false; - draft.id = db.message().insertMessage(draft); - EntityOperation.queue(db, draft, EntityOperation.ADD); - - } else if (action == R.id.action_send) { - // Check data - if (draft.identity == null) - throw new IllegalArgumentException(getContext().getString(R.string.title_from_missing)); - - if (draft.to == null && draft.cc == null && draft.bcc == null) - throw new IllegalArgumentException(getContext().getString(R.string.title_to_missing)); - - if (db.attachment().getAttachmentCountWithoutContent(draft.id) > 0) - throw new IllegalArgumentException(getContext().getString(R.string.title_attachments_missing)); - - List attachments = db.attachment().getAttachments(draft.id); - for (EntityAttachment attachment : attachments) - attachment.content = db.attachment().getContent(attachment.id); - - // Delete draft (cannot move to outbox) - draft.ui_hide = true; - db.message().updateMessage(draft); - EntityOperation.queue(db, draft, EntityOperation.DELETE); - - // Copy message to outbox - draft.id = null; - draft.folder = db.folder().getOutbox().id; - draft.uid = null; - draft.ui_hide = false; - draft.id = db.message().insertMessage(draft); - - for (EntityAttachment attachment : attachments) { - attachment.message = draft.id; - db.attachment().insertAttachment(attachment); - } + // Delete previous draft + draft.msgid = null; + draft.ui_hide = true; + db.message().updateMessage(draft); + EntityOperation.queue(db, draft, EntityOperation.DELETE); + + // Create new draft + draft.id = null; + draft.uid = null; // unique index folder/uid + draft.msgid = msgid; + draft.ui_hide = false; + draft.id = db.message().insertMessage(draft); + EntityOperation.queue(db, draft, EntityOperation.ADD); + + } else if (action == R.id.action_send) { + // Check data + if (draft.identity == null) + throw new IllegalArgumentException(getContext().getString(R.string.title_from_missing)); - EntityOperation.queue(db, draft, EntityOperation.SEND); + if (draft.to == null && draft.cc == null && draft.bcc == null) + throw new IllegalArgumentException(getContext().getString(R.string.title_to_missing)); + + if (db.attachment().getAttachmentCountWithoutContent(draft.id) > 0) + throw new IllegalArgumentException(getContext().getString(R.string.title_attachments_missing)); + + List attachments = db.attachment().getAttachments(draft.id); + for (EntityAttachment attachment : attachments) + attachment.content = db.attachment().getContent(attachment.id); + + String msgid = draft.msgid; + + // Delete draft (cannot move to outbox) + draft.msgid = null; + draft.ui_hide = true; + db.message().updateMessage(draft); + EntityOperation.queue(db, draft, EntityOperation.DELETE); + + // Copy message to outbox + draft.id = null; + draft.folder = db.folder().getOutbox().id; + draft.uid = null; + draft.msgid = msgid; + draft.ui_hide = false; + draft.id = db.message().insertMessage(draft); + + for (EntityAttachment attachment : attachments) { + attachment.message = draft.id; + db.attachment().insertAttachment(attachment); } - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); + EntityOperation.queue(db, draft, EntityOperation.SEND); } - EntityOperation.process(getContext()); - - return null; - } catch (Throwable ex) { - Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); - return ex; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); } - } - } - private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks() { - @NonNull - @Override - public Loader onCreateLoader(int id, Bundle args) { - PutLoader loader = new PutLoader(getContext()); - loader.setArgs(args); - return loader; + EntityOperation.process(getContext()); + + return draft; } @Override - public void onLoadFinished(@NonNull Loader loader, Throwable ex) { - LoaderManager.getInstance(FragmentCompose.this).destroyLoader(loader.getId()); - + public void onLoaded(Bundle args, EntityMessage draft) { + id = draft.id; bottom_navigation.getMenu().setGroupEnabled(0, true); - if (ex == null) { - Bundle args = ((PutLoader) loader).args; - int action = args.getInt("action"); + int action = args.getInt("action"); - if (action == R.id.action_trash) { - getFragmentManager().popBackStack(); - Toast.makeText(getContext(), R.string.title_draft_trashed, Toast.LENGTH_LONG).show(); - } else if (action == R.id.action_save) - Snackbar.make(view, R.string.title_draft_saved, Snackbar.LENGTH_LONG).show(); - else if (action == R.id.action_send) { - getFragmentManager().popBackStack(); - Toast.makeText(getContext(), R.string.title_queued, Toast.LENGTH_LONG).show(); - } - } else { - Log.w(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); - if (ex instanceof IllegalArgumentException) - Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); - else - Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); + if (action == R.id.action_trash) { + getFragmentManager().popBackStack(); + Toast.makeText(getContext(), R.string.title_draft_trashed, Toast.LENGTH_LONG).show(); + } else if (action == R.id.action_save) + Snackbar.make(view, R.string.title_draft_saved, Snackbar.LENGTH_LONG).show(); + else if (action == R.id.action_send) { + getFragmentManager().popBackStack(); + Toast.makeText(getContext(), R.string.title_queued, Toast.LENGTH_LONG).show(); } } @Override - public void onLoaderReset(@NonNull Loader loader) { + public void onException(Bundle args, Throwable ex) { + bottom_navigation.getMenu().setGroupEnabled(0, true); + + if (ex instanceof IllegalArgumentException) + Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); + else + Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show(); } }; } \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/FragmentMessage.java b/app/src/main/java/eu/faircode/email/FragmentMessage.java index ffb4b717..85d2db04 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessage.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessage.java @@ -522,18 +522,11 @@ public class FragmentMessage extends FragmentEx { db.beginTransaction(); EntityMessage message = db.message().getMessage(id); - EntityFolder spam = db.folder().getFolderByType(message.account, EntityFolder.JUNK); - - boolean move = (message.uid != null); - if (move) - EntityOperation.queue(db, message, EntityOperation.MOVE, spam.id, message.uid); - - message.folder = spam.id; - message.uid = null; + message.ui_hide = true; db.message().updateMessage(message); - if (!move) - EntityOperation.queue(db, message, EntityOperation.ADD); + EntityFolder spam = db.folder().getFolderByType(message.account, EntityFolder.JUNK); + EntityOperation.queue(db, message, EntityOperation.MOVE, spam.id); db.setTransactionSuccessful(); } finally { @@ -640,18 +633,11 @@ public class FragmentMessage extends FragmentEx { db.beginTransaction(); EntityMessage message = db.message().getMessage(id); - EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH); - - boolean move = (message.uid != null); - if (move) - EntityOperation.queue(db, message, EntityOperation.MOVE, trash.id, message.uid); - - message.folder = trash.id; - message.uid = null; + message.ui_hide = true; db.message().updateMessage(message); - if (!move) - EntityOperation.queue(db, message, EntityOperation.ADD); + EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH); + EntityOperation.queue(db, message, EntityOperation.MOVE, trash.id); db.setTransactionSuccessful(); } finally { @@ -705,18 +691,11 @@ public class FragmentMessage extends FragmentEx { db.beginTransaction(); EntityMessage message = db.message().getMessage(id); - EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE); - - boolean move = (message.uid != null); - if (move) - EntityOperation.queue(db, message, EntityOperation.MOVE, archive.id, message.uid); - - message.folder = archive.id; - message.uid = null; + message.ui_hide = true; db.message().updateMessage(message); - if (!move) - EntityOperation.queue(db, message, EntityOperation.ADD); + EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE); + EntityOperation.queue(db, message, EntityOperation.MOVE, archive.id); db.setTransactionSuccessful(); } finally { @@ -832,17 +811,10 @@ public class FragmentMessage extends FragmentEx { db.beginTransaction(); EntityMessage message = db.message().getMessage(id); - - boolean move = (message.uid != null); - if (move) - EntityOperation.queue(db, message, EntityOperation.MOVE, target, message.uid); - - message.folder = target; - message.uid = null; + message.ui_hide = true; db.message().updateMessage(message); - if (!move) - EntityOperation.queue(db, message, EntityOperation.ADD); + EntityOperation.queue(db, message, EntityOperation.MOVE, target); db.setTransactionSuccessful(); } finally { diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 908a7628..1c1e2b41 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -85,7 +85,7 @@ public class MessageHelper { } static MimeMessageEx from(EntityMessage message, List attachments, Session isession) throws MessagingException { - MimeMessageEx imessage = new MimeMessageEx(isession, message.id); + MimeMessageEx imessage = new MimeMessageEx(isession, message.msgid); if (message.from != null && message.from.length > 0) imessage.setFrom(message.from[0]); @@ -104,17 +104,17 @@ public class MessageHelper { // TODO: plain message? - if (attachments.size() == 0) { - if (message.body != null) - imessage.setText(message.body, Charset.defaultCharset().name(), "html"); - } else { + if (message.body == null) + throw new IllegalArgumentException("null message"); + + if (attachments.size() == 0) + imessage.setText(message.body, Charset.defaultCharset().name(), "html"); + else { Multipart multipart = new MimeMultipart(); - if (message.body != null) { - BodyPart bpMessage = new MimeBodyPart(); - bpMessage.setContent(message.body, "text/html; charset=" + Charset.defaultCharset().name()); - multipart.addBodyPart(bpMessage); - } + BodyPart bpMessage = new MimeBodyPart(); + bpMessage.setContent(message.body, "text/html; charset=" + Charset.defaultCharset().name()); + multipart.addBodyPart(bpMessage); for (EntityAttachment attachment : attachments) { BodyPart bpAttachment = new MimeBodyPart(); diff --git a/app/src/main/java/eu/faircode/email/MimeMessageEx.java b/app/src/main/java/eu/faircode/email/MimeMessageEx.java new file mode 100644 index 00000000..d928595e --- /dev/null +++ b/app/src/main/java/eu/faircode/email/MimeMessageEx.java @@ -0,0 +1,45 @@ +package eu.faircode.email; + +/* + This file is part of Safe email. + + Safe email is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + NetGuard is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with NetGuard. If not, see . + + Copyright 2018 by Marcel Bokhorst (M66B) +*/ + +import android.util.Log; + +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; + +public class MimeMessageEx extends MimeMessage { + private String msgid; + + public MimeMessageEx(Session session, String msgid) { + super(session); + this.msgid = msgid; + } + + @Override + protected void updateMessageID() throws MessagingException { + if (msgid == null) + super.updateMessageID(); + else { + setHeader("Message-ID", msgid); + Log.v(Helper.TAG, "Override Message-ID=" + msgid); + } + } +} diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index db600782..7cd448f1 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -710,7 +710,9 @@ public class ServiceSynchronize extends LifecycleService { Log.i(Helper.TAG, folder.name + " start process"); DB db = DB.getInstance(this); - for (EntityOperation op : db.operation().getOperations(folder.id)) + List ops = db.operation().getOperations(folder.id); + Log.i(Helper.TAG, folder.name + " pending operations=" + ops.size()); + for (EntityOperation op : ops) try { Log.i(Helper.TAG, folder.name + " start op=" + op.id + "/" + op.name + @@ -750,6 +752,11 @@ public class ServiceSynchronize extends LifecycleService { op.error = Helper.formatThrowable(ex); db.operation().updateOperation(op); + if (BuildConfig.DEBUG && ex instanceof NullPointerException) { + db.operation().deleteOperation(op.id); + throw ex; + } + if (ex instanceof MessageRemovedException || ex instanceof FolderNotFoundException || ex instanceof SMTPSendFailedException) { @@ -800,20 +807,27 @@ public class ServiceSynchronize extends LifecycleService { } private void doMove(EntityFolder source, IMAPStore istore, IMAPFolder ifolder, DB db, JSONArray jargs, EntityMessage message) throws JSONException, MessagingException { + if (BuildConfig.DEBUG && message.uid == null) { + Log.w(Helper.TAG, "Move local message id=" + message.id); + db.message().deleteMessage(message.id); + return; + } + long id = jargs.getLong(0); - long uid = jargs.getLong(1); EntityFolder target = db.folder().getFolder(id); if (target == null) throw new FolderNotFoundException(); // Get message - Message imessage = ifolder.getMessageByUID(uid); + Message imessage = ifolder.getMessageByUID(message.uid); if (imessage == null) throw new MessageRemovedException(); // Get folder Folder itarget = istore.getFolder(target.name); + ifolder.moveMessages(new Message[]{imessage}, itarget); +/* // Append/delete because message ID header needs to be added and not all providers support MOVE long oid = message.id; try { @@ -857,12 +871,13 @@ public class ServiceSynchronize extends LifecycleService { ifolder.expunge(); db.message().deleteMessage(oid); +*/ } private void doDelete(EntityFolder folder, IMAPFolder ifolder, DB db, EntityMessage message) throws MessagingException { // Delete message if (message.uid == null) - Log.w(Helper.TAG, folder.name + " local op delete id=" + message.id + " uid=" + message.uid); + Log.w(Helper.TAG, folder.name + " Delete local message id=" + message.id); else { Message imessage = ifolder.getMessageByUID(message.uid); if (imessage == null) @@ -1132,7 +1147,7 @@ public class ServiceSynchronize extends LifecycleService { db.endTransaction(); } } - Log.i(Helper.TAG, folder.name + " added=" + added + " updated=" + updated + " unchanged=" + unchanged); + Log.w(Helper.TAG, folder.name + " statistics added=" + added + " updated=" + updated + " unchanged=" + unchanged); } finally { Log.i(Helper.TAG, folder.name + " end sync"); } @@ -1161,45 +1176,38 @@ public class ServiceSynchronize extends LifecycleService { MessageHelper helper = new MessageHelper(imessage); boolean seen = helper.getSeen(); - EntityMessage message = null; DB db = DB.getInstance(this); - - // Find by id - long id = -1; - boolean update = false; - // Messages in archive have id of original - // Messages in inbox have id of message sent to self - if (!EntityFolder.ARCHIVE.equals(folder.type) && - !EntityFolder.INBOX.equals(folder.type)) { - id = MimeMessageEx.getId(imessage); - if (id >= 0) { - message = db.message().getMessage(id); - if (message == null) - Log.w(Helper.TAG, "By id=" + id + " uid=" + (message == null ? "n/a" : message.uid)); - else { - if (EntityFolder.SENT.equals(folder.type)) { - message.folder = folder.id; // outbox to sent - message.uid = null; + EntityMessage message = db.message().getMessageByUid(folder.id, uid); + + // Find by Message-ID + // - messages in archive have same id as original + // - messages in inbox have same id as message sent to self + if (message == null && + !EntityFolder.ARCHIVE.equals(folder.type)) { + String msgid = imessage.getMessageID(); + message = db.message().getMessageByMsgId(folder.account, msgid); + if (message != null) { + Log.i(Helper.TAG, folder.name + " found as id=" + message.id + " uid=" + message.uid + " msgid=" + msgid); + message.uid = uid; + if (EntityFolder.SENT.equals(folder.type)) { + Log.i(Helper.TAG, folder.name + " move from outbox"); + message.folder = folder.id; // outbox to sent + } + db.message().updateMessage(message); +/* + if (message.uid == null) { + // Append (move) + message.uid = uid; + if (!seen) { + seen = true; update = true; - } - - if (message.uid == null) { - // Append (move) - message.uid = uid; - if (!seen) { - seen = true; - update = true; - imessage.setFlag(Flags.Flag.SEEN, true); - } + imessage.setFlag(Flags.Flag.SEEN, true); } } +*/ } } - // Find by uid - if (message == null) - message = db.message().getMessage(folder.id, uid); - if (message == null) { FetchProfile fp1 = new FetchProfile(); fp1.add(FetchProfile.Item.ENVELOPE); @@ -1230,7 +1238,10 @@ public class ServiceSynchronize extends LifecycleService { message.ui_hide = false; message.id = db.message().insertMessage(message); - Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid); + Log.v(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid); + + if (TextUtils.isEmpty(message.msgid)) + Log.w(Helper.TAG, "No Message-ID id=" + message.id + " uid=" + message.uid); int sequence = 0; for (EntityAttachment attachment : helper.getAttachments()) { @@ -1243,11 +1254,11 @@ public class ServiceSynchronize extends LifecycleService { } return 1; - } else if (update || message.seen != seen) { + } else if (message.seen != seen) { message.seen = seen; message.ui_seen = seen; db.message().updateMessage(message); - Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid); + Log.v(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid); return -1; } else { Log.v(Helper.TAG, folder.name + " unchanged id=" + message.id + " uid=" + message.uid);