Use MOVE again, refactored compose message, find by message ID

pull/50/head
M66B 7 years ago
parent 884ce9870a
commit 6f06d88082

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "9fd2cb9e7b45bf1dbddced278a737dfa", "identityHash": "7814b856d44afe54b8912106df1e673b",
"entities": [ "entities": [
{ {
"tableName": "identity", "tableName": "identity",
@ -478,6 +478,14 @@
], ],
"createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)" "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", "name": "index_message_thread",
"unique": false, "unique": false,
@ -743,7 +751,7 @@
], ],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "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\")"
] ]
} }
} }

@ -78,12 +78,14 @@ public interface DaoMessage {
EntityMessage getMessage(long id); EntityMessage getMessage(long id);
@Query("SELECT * FROM message WHERE folder = :folder AND uid = :uid") @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" + @Query("SELECT message.* FROM message" +
" JOIN folder on folder.id = message.folder" + " JOIN folder ON folder.id = message.folder" +
" WHERE thread = :thread AND folder.type= '" + EntityFolder.ARCHIVE + "'") " WHERE message.account = :account" +
EntityMessage getArchivedMessage(String thread); " 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" + @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" + ", (SELECT COUNT(m.id) FROM message m WHERE m.account = message.account AND m.thread = message.thread AND NOT m.ui_hide) AS count" +

@ -45,6 +45,7 @@ import static androidx.room.ForeignKey.CASCADE;
@Index(value = {"identity"}), @Index(value = {"identity"}),
@Index(value = {"replying"}), @Index(value = {"replying"}),
@Index(value = {"folder", "uid"}, unique = true), @Index(value = {"folder", "uid"}, unique = true),
@Index(value = {"msgid"}, unique = true),
@Index(value = {"thread"}), @Index(value = {"thread"}),
@Index(value = {"received"}), @Index(value = {"received"}),
@Index(value = {"ui_seen"}), @Index(value = {"ui_seen"}),
@ -84,6 +85,17 @@ public class EntityMessage {
public Boolean ui_hide; public Boolean ui_hide;
public String error; 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 @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof EntityMessage) { if (obj instanceof EntityMessage) {

@ -20,7 +20,6 @@ package eu.faircode.email;
*/ */
import android.Manifest; import android.Manifest;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
@ -71,9 +70,6 @@ import androidx.core.content.ContextCompat;
import androidx.cursoradapter.widget.SimpleCursorAdapter; import androidx.cursoradapter.widget.SimpleCursorAdapter;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Observer; 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.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -101,6 +97,8 @@ public class FragmentCompose extends FragmentEx {
private AdapterAttachment adapter; private AdapterAttachment adapter;
private long id = -1; // draft id
private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes private static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
@Override @Override
@ -282,9 +280,7 @@ public class FragmentCompose extends FragmentEx {
@Override @Override
public void run() { public void run() {
// Get might select another identity // Get might select another identity
LoaderManager.getInstance(FragmentCompose.this) getLoader.load(FragmentCompose.this, ActivityCompose.LOADER_COMPOSE_GET, getArguments());
.restartLoader(ActivityCompose.LOADER_COMPOSE_GET, getArguments(), getLoaderCallbacks).forceLoad();
} }
}); });
} }
@ -387,7 +383,7 @@ public class FragmentCompose extends FragmentEx {
private void handleAddAttachment(Intent data) { private void handleAddAttachment(Intent data) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", getArguments().getLong("id")); args.putLong("id", id);
args.putParcelable("uri", data.getData()); args.putParcelable("uri", data.getData());
new SimpleLoader<Object>() { new SimpleLoader<Object>() {
@ -396,7 +392,7 @@ public class FragmentCompose extends FragmentEx {
Cursor cursor = null; Cursor cursor = null;
try { try {
Uri uri = args.getParcelable("uri"); 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()) if (cursor == null || !cursor.moveToFirst())
return null; return null;
@ -466,7 +462,6 @@ public class FragmentCompose extends FragmentEx {
private void onAction(int action) { private void onAction(int action) {
bottom_navigation.getMenu().setGroupEnabled(0, false); bottom_navigation.getMenu().setGroupEnabled(0, false);
long id = getArguments().getLong("id", -1);
EntityIdentity identity = (EntityIdentity) spFrom.getSelectedItem(); EntityIdentity identity = (EntityIdentity) spFrom.getSelectedItem();
Bundle args = new Bundle(); Bundle args = new Bundle();
@ -479,37 +474,29 @@ public class FragmentCompose extends FragmentEx {
args.putString("subject", etSubject.getText().toString()); args.putString("subject", etSubject.getText().toString());
args.putString("body", etBody.getText().toString()); args.putString("body", etBody.getText().toString());
LoaderManager.getInstance(this) putLoader.load(this, ActivityCompose.LOADER_COMPOSE_PUT, args);
.restartLoader(ActivityCompose.LOADER_COMPOSE_PUT, args, putLoaderCallbacks).forceLoad();
} }
private static class GetLoader extends AsyncTaskLoader<EntityMessage> { private SimpleLoader<EntityMessage> getLoader = new SimpleLoader<EntityMessage>() {
private Bundle args; @Override
public EntityMessage onLoad(Bundle args) {
GetLoader(Context context) { String action = args.getString("action");
super(context); 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) { DB db = DB.getInstance(getContext());
this.args = args;
}
@Nullable EntityMessage draft = db.message().getMessage(id);
@Override if (draft == null) {
public EntityMessage loadInBackground() { if ("edit".equals(action))
EntityMessage draft = null; throw new IllegalStateException("Message to edit not found");
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);
DB db = DB.getInstance(getContext()); try {
db.beginTransaction();
draft = db.message().getMessage(id); EntityMessage ref = db.message().getMessage(reference);
if (draft == null) {
EntityMessage ref = DB.getInstance(getContext()).message().getMessage(reference);
if (ref != null) if (ref != null)
account = ref.account; account = ref.account;
@ -562,35 +549,35 @@ public class FragmentCompose extends FragmentEx {
} }
} }
if ("new".equals(action))
draft.body = "";
draft.received = new Date().getTime(); draft.received = new Date().getTime();
draft.seen = false; draft.seen = false;
draft.ui_seen = false; draft.ui_seen = false;
draft.ui_hide = 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; return draft;
} }
}
private LoaderManager.LoaderCallbacks getLoaderCallbacks = new LoaderManager.LoaderCallbacks<EntityMessage>() {
@NonNull
@Override
public Loader<EntityMessage> onCreateLoader(int id, @Nullable Bundle args) {
GetLoader loader = new GetLoader(getContext());
loader.setArgs(args);
return loader;
}
@Override @Override
public void onLoadFinished(@NonNull Loader<EntityMessage> loader, EntityMessage msg) { public void onLoaded(Bundle args, EntityMessage draft) {
LoaderManager.getInstance(FragmentCompose.this).destroyLoader(loader.getId()); id = draft.id;
getArguments().putLong("id", msg.id);
String action = getArguments().getString("action"); String action = getArguments().getString("action");
menuAttachment.setEnabled(true); menuAttachment.setEnabled(true);
@ -602,23 +589,23 @@ public class FragmentCompose extends FragmentEx {
if (aa != null) { if (aa != null) {
for (int pos = 0; pos < aa.getCount(); pos++) { for (int pos = 0; pos < aa.getCount(); pos++) {
EntityIdentity identity = (EntityIdentity) aa.getItem(pos); EntityIdentity identity = (EntityIdentity) aa.getItem(pos);
if (msg.identity == null if (draft.identity == null
? msg.from != null && msg.from.length > 0 && ((InternetAddress) msg.from[0]).getAddress().equals(identity.email) ? draft.from != null && draft.from.length > 0 && ((InternetAddress) draft.from[0]).getAddress().equals(identity.email)
: msg.identity.equals(identity.id)) { : draft.identity.equals(identity.id)) {
spFrom.setSelection(pos); spFrom.setSelection(pos);
break; break;
} }
} }
} }
etTo.setText(msg.to == null ? null : TextUtils.join(", ", msg.to)); etTo.setText(draft.to == null ? null : TextUtils.join(", ", draft.to));
etCc.setText(msg.cc == null ? null : TextUtils.join(", ", msg.cc)); etCc.setText(draft.cc == null ? null : TextUtils.join(", ", draft.cc));
etBcc.setText(msg.bcc == null ? null : TextUtils.join(", ", msg.bcc)); etBcc.setText(draft.bcc == null ? null : TextUtils.join(", ", draft.bcc));
etSubject.setText(msg.subject); etSubject.setText(draft.subject);
DB db = DB.getInstance(getContext()); DB db = DB.getInstance(getContext());
db.attachment().liveAttachments(msg.id).removeObservers(getViewLifecycleOwner()); db.attachment().liveAttachments(draft.id).removeObservers(getViewLifecycleOwner());
db.attachment().liveAttachments(msg.id).observe(getViewLifecycleOwner(), db.attachment().liveAttachments(draft.id).observe(getViewLifecycleOwner(),
new Observer<List<TupleAttachment>>() { new Observer<List<TupleAttachment>>() {
@Override @Override
public void onChanged(@Nullable List<TupleAttachment> attachments) { public void onChanged(@Nullable List<TupleAttachment> 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)) if ("edit".equals(action))
etTo.requestFocus(); etTo.requestFocus();
@ -641,177 +628,150 @@ public class FragmentCompose extends FragmentEx {
} }
@Override @Override
public void onLoaderReset(@NonNull Loader<EntityMessage> loader) { public void onException(Bundle args, Throwable ex) {
Toast.makeText(getContext(), ex.toString(), Toast.LENGTH_LONG).show();
} }
}; };
private static class PutLoader extends AsyncTaskLoader<Throwable> { private SimpleLoader<EntityMessage> putLoader = new SimpleLoader<EntityMessage>() {
private Bundle args; @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 = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
draft.received = new Date().getTime();
db.message().updateMessage(draft);
// Check data
try {
db.beginTransaction();
PutLoader(Context context) { if (action == R.id.action_trash) {
super(context); draft.ui_hide = true;
} db.message().updateMessage(draft);
void setArgs(Bundle args) { EntityFolder trash = db.folder().getFolderByType(draft.account, EntityFolder.TRASH);
this.args = args; EntityOperation.queue(db, draft, EntityOperation.MOVE, trash.id);
}
@Override } else if (action == R.id.action_save) {
public Throwable loadInBackground() { String msgid = draft.msgid;
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 = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
draft.received = new Date().getTime();
db.message().updateMessage(draft);
// Check data
try {
db.beginTransaction();
if (action == R.id.action_trash) { // Delete previous draft
EntityFolder trash = db.folder().getFolderByType(draft.account, EntityFolder.TRASH); draft.msgid = null;
draft.ui_hide = true;
boolean move = (draft.uid != null); db.message().updateMessage(draft);
if (move) EntityOperation.queue(db, draft, EntityOperation.DELETE);
EntityOperation.queue(db, draft, EntityOperation.MOVE, trash.id, draft.uid);
// Create new draft
draft.folder = trash.id; draft.id = null;
draft.uid = null; draft.uid = null; // unique index folder/uid
db.message().updateMessage(draft); draft.msgid = msgid;
draft.ui_hide = false;
if (!move) draft.id = db.message().insertMessage(draft);
EntityOperation.queue(db, draft, EntityOperation.ADD); EntityOperation.queue(db, draft, EntityOperation.ADD);
} else if (action == R.id.action_save) { } else if (action == R.id.action_send) {
// Delete previous draft // Check data
draft.ui_hide = true; if (draft.identity == null)
db.message().updateMessage(draft); throw new IllegalArgumentException(getContext().getString(R.string.title_from_missing));
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<EntityAttachment> 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);
}
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<EntityAttachment> 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(); EntityOperation.queue(db, draft, EntityOperation.SEND);
} finally {
db.endTransaction();
} }
EntityOperation.process(getContext()); db.setTransactionSuccessful();
} finally {
return null; db.endTransaction();
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
return ex;
} }
}
}
private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks<Throwable>() { EntityOperation.process(getContext());
@NonNull
@Override return draft;
public Loader<Throwable> onCreateLoader(int id, Bundle args) {
PutLoader loader = new PutLoader(getContext());
loader.setArgs(args);
return loader;
} }
@Override @Override
public void onLoadFinished(@NonNull Loader<Throwable> loader, Throwable ex) { public void onLoaded(Bundle args, EntityMessage draft) {
LoaderManager.getInstance(FragmentCompose.this).destroyLoader(loader.getId()); id = draft.id;
bottom_navigation.getMenu().setGroupEnabled(0, true); bottom_navigation.getMenu().setGroupEnabled(0, true);
if (ex == null) { int action = args.getInt("action");
Bundle args = ((PutLoader) loader).args;
int action = args.getInt("action");
if (action == R.id.action_trash) { if (action == R.id.action_trash) {
getFragmentManager().popBackStack(); getFragmentManager().popBackStack();
Toast.makeText(getContext(), R.string.title_draft_trashed, Toast.LENGTH_LONG).show(); Toast.makeText(getContext(), R.string.title_draft_trashed, Toast.LENGTH_LONG).show();
} else if (action == R.id.action_save) } else if (action == R.id.action_save)
Snackbar.make(view, R.string.title_draft_saved, Snackbar.LENGTH_LONG).show(); Snackbar.make(view, R.string.title_draft_saved, Snackbar.LENGTH_LONG).show();
else if (action == R.id.action_send) { else if (action == R.id.action_send) {
getFragmentManager().popBackStack(); getFragmentManager().popBackStack();
Toast.makeText(getContext(), R.string.title_queued, Toast.LENGTH_LONG).show(); 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();
} }
} }
@Override @Override
public void onLoaderReset(@NonNull Loader<Throwable> 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();
} }
}; };
} }

@ -522,18 +522,11 @@ public class FragmentMessage extends FragmentEx {
db.beginTransaction(); db.beginTransaction();
EntityMessage message = db.message().getMessage(id); EntityMessage message = db.message().getMessage(id);
EntityFolder spam = db.folder().getFolderByType(message.account, EntityFolder.JUNK); message.ui_hide = true;
boolean move = (message.uid != null);
if (move)
EntityOperation.queue(db, message, EntityOperation.MOVE, spam.id, message.uid);
message.folder = spam.id;
message.uid = null;
db.message().updateMessage(message); db.message().updateMessage(message);
if (!move) EntityFolder spam = db.folder().getFolderByType(message.account, EntityFolder.JUNK);
EntityOperation.queue(db, message, EntityOperation.ADD); EntityOperation.queue(db, message, EntityOperation.MOVE, spam.id);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
@ -640,18 +633,11 @@ public class FragmentMessage extends FragmentEx {
db.beginTransaction(); db.beginTransaction();
EntityMessage message = db.message().getMessage(id); EntityMessage message = db.message().getMessage(id);
EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH); message.ui_hide = true;
boolean move = (message.uid != null);
if (move)
EntityOperation.queue(db, message, EntityOperation.MOVE, trash.id, message.uid);
message.folder = trash.id;
message.uid = null;
db.message().updateMessage(message); db.message().updateMessage(message);
if (!move) EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
EntityOperation.queue(db, message, EntityOperation.ADD); EntityOperation.queue(db, message, EntityOperation.MOVE, trash.id);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
@ -705,18 +691,11 @@ public class FragmentMessage extends FragmentEx {
db.beginTransaction(); db.beginTransaction();
EntityMessage message = db.message().getMessage(id); EntityMessage message = db.message().getMessage(id);
EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE); message.ui_hide = true;
boolean move = (message.uid != null);
if (move)
EntityOperation.queue(db, message, EntityOperation.MOVE, archive.id, message.uid);
message.folder = archive.id;
message.uid = null;
db.message().updateMessage(message); db.message().updateMessage(message);
if (!move) EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
EntityOperation.queue(db, message, EntityOperation.ADD); EntityOperation.queue(db, message, EntityOperation.MOVE, archive.id);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
@ -832,17 +811,10 @@ public class FragmentMessage extends FragmentEx {
db.beginTransaction(); db.beginTransaction();
EntityMessage message = db.message().getMessage(id); EntityMessage message = db.message().getMessage(id);
message.ui_hide = true;
boolean move = (message.uid != null);
if (move)
EntityOperation.queue(db, message, EntityOperation.MOVE, target, message.uid);
message.folder = target;
message.uid = null;
db.message().updateMessage(message); db.message().updateMessage(message);
if (!move) EntityOperation.queue(db, message, EntityOperation.MOVE, target);
EntityOperation.queue(db, message, EntityOperation.ADD);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {

@ -85,7 +85,7 @@ public class MessageHelper {
} }
static MimeMessageEx from(EntityMessage message, List<EntityAttachment> attachments, Session isession) throws MessagingException { static MimeMessageEx from(EntityMessage message, List<EntityAttachment> 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) if (message.from != null && message.from.length > 0)
imessage.setFrom(message.from[0]); imessage.setFrom(message.from[0]);
@ -104,17 +104,17 @@ public class MessageHelper {
// TODO: plain message? // TODO: plain message?
if (attachments.size() == 0) { if (message.body == null)
if (message.body != null) throw new IllegalArgumentException("null message");
imessage.setText(message.body, Charset.defaultCharset().name(), "html");
} else { if (attachments.size() == 0)
imessage.setText(message.body, Charset.defaultCharset().name(), "html");
else {
Multipart multipart = new MimeMultipart(); Multipart multipart = new MimeMultipart();
if (message.body != null) { BodyPart bpMessage = new MimeBodyPart();
BodyPart bpMessage = new MimeBodyPart(); bpMessage.setContent(message.body, "text/html; charset=" + Charset.defaultCharset().name());
bpMessage.setContent(message.body, "text/html; charset=" + Charset.defaultCharset().name()); multipart.addBodyPart(bpMessage);
multipart.addBodyPart(bpMessage);
}
for (EntityAttachment attachment : attachments) { for (EntityAttachment attachment : attachments) {
BodyPart bpAttachment = new MimeBodyPart(); BodyPart bpAttachment = new MimeBodyPart();

@ -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 <http://www.gnu.org/licenses/>.
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);
}
}
}

@ -710,7 +710,9 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, folder.name + " start process"); Log.i(Helper.TAG, folder.name + " start process");
DB db = DB.getInstance(this); DB db = DB.getInstance(this);
for (EntityOperation op : db.operation().getOperations(folder.id)) List<EntityOperation> ops = db.operation().getOperations(folder.id);
Log.i(Helper.TAG, folder.name + " pending operations=" + ops.size());
for (EntityOperation op : ops)
try { try {
Log.i(Helper.TAG, folder.name + Log.i(Helper.TAG, folder.name +
" start op=" + op.id + "/" + op.name + " start op=" + op.id + "/" + op.name +
@ -750,6 +752,11 @@ public class ServiceSynchronize extends LifecycleService {
op.error = Helper.formatThrowable(ex); op.error = Helper.formatThrowable(ex);
db.operation().updateOperation(op); db.operation().updateOperation(op);
if (BuildConfig.DEBUG && ex instanceof NullPointerException) {
db.operation().deleteOperation(op.id);
throw ex;
}
if (ex instanceof MessageRemovedException || if (ex instanceof MessageRemovedException ||
ex instanceof FolderNotFoundException || ex instanceof FolderNotFoundException ||
ex instanceof SMTPSendFailedException) { 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 { 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 id = jargs.getLong(0);
long uid = jargs.getLong(1);
EntityFolder target = db.folder().getFolder(id); EntityFolder target = db.folder().getFolder(id);
if (target == null) if (target == null)
throw new FolderNotFoundException(); throw new FolderNotFoundException();
// Get message // Get message
Message imessage = ifolder.getMessageByUID(uid); Message imessage = ifolder.getMessageByUID(message.uid);
if (imessage == null) if (imessage == null)
throw new MessageRemovedException(); throw new MessageRemovedException();
// Get folder // Get folder
Folder itarget = istore.getFolder(target.name); 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 // Append/delete because message ID header needs to be added and not all providers support MOVE
long oid = message.id; long oid = message.id;
try { try {
@ -857,12 +871,13 @@ public class ServiceSynchronize extends LifecycleService {
ifolder.expunge(); ifolder.expunge();
db.message().deleteMessage(oid); db.message().deleteMessage(oid);
*/
} }
private void doDelete(EntityFolder folder, IMAPFolder ifolder, DB db, EntityMessage message) throws MessagingException { private void doDelete(EntityFolder folder, IMAPFolder ifolder, DB db, EntityMessage message) throws MessagingException {
// Delete message // Delete message
if (message.uid == null) 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 { else {
Message imessage = ifolder.getMessageByUID(message.uid); Message imessage = ifolder.getMessageByUID(message.uid);
if (imessage == null) if (imessage == null)
@ -1132,7 +1147,7 @@ public class ServiceSynchronize extends LifecycleService {
db.endTransaction(); 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 { } finally {
Log.i(Helper.TAG, folder.name + " end sync"); Log.i(Helper.TAG, folder.name + " end sync");
} }
@ -1161,45 +1176,38 @@ public class ServiceSynchronize extends LifecycleService {
MessageHelper helper = new MessageHelper(imessage); MessageHelper helper = new MessageHelper(imessage);
boolean seen = helper.getSeen(); boolean seen = helper.getSeen();
EntityMessage message = null;
DB db = DB.getInstance(this); DB db = DB.getInstance(this);
EntityMessage message = db.message().getMessageByUid(folder.id, uid);
// Find by id
long id = -1; // Find by Message-ID
boolean update = false; // - messages in archive have same id as original
// Messages in archive have id of original // - messages in inbox have same id as message sent to self
// Messages in inbox have id of message sent to self if (message == null &&
if (!EntityFolder.ARCHIVE.equals(folder.type) && !EntityFolder.ARCHIVE.equals(folder.type)) {
!EntityFolder.INBOX.equals(folder.type)) { String msgid = imessage.getMessageID();
id = MimeMessageEx.getId(imessage); message = db.message().getMessageByMsgId(folder.account, msgid);
if (id >= 0) { if (message != null) {
message = db.message().getMessage(id); Log.i(Helper.TAG, folder.name + " found as id=" + message.id + " uid=" + message.uid + " msgid=" + msgid);
if (message == null) message.uid = uid;
Log.w(Helper.TAG, "By id=" + id + " uid=" + (message == null ? "n/a" : message.uid)); if (EntityFolder.SENT.equals(folder.type)) {
else { Log.i(Helper.TAG, folder.name + " move from outbox");
if (EntityFolder.SENT.equals(folder.type)) { message.folder = folder.id; // outbox to sent
message.folder = folder.id; // outbox to sent }
message.uid = null; db.message().updateMessage(message);
/*
if (message.uid == null) {
// Append (move)
message.uid = uid;
if (!seen) {
seen = true;
update = true; update = true;
} imessage.setFlag(Flags.Flag.SEEN, true);
if (message.uid == null) {
// Append (move)
message.uid = uid;
if (!seen) {
seen = true;
update = true;
imessage.setFlag(Flags.Flag.SEEN, true);
}
} }
} }
*/
} }
} }
// Find by uid
if (message == null)
message = db.message().getMessage(folder.id, uid);
if (message == null) { if (message == null) {
FetchProfile fp1 = new FetchProfile(); FetchProfile fp1 = new FetchProfile();
fp1.add(FetchProfile.Item.ENVELOPE); fp1.add(FetchProfile.Item.ENVELOPE);
@ -1230,7 +1238,10 @@ public class ServiceSynchronize extends LifecycleService {
message.ui_hide = false; message.ui_hide = false;
message.id = db.message().insertMessage(message); 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; int sequence = 0;
for (EntityAttachment attachment : helper.getAttachments()) { for (EntityAttachment attachment : helper.getAttachments()) {
@ -1243,11 +1254,11 @@ public class ServiceSynchronize extends LifecycleService {
} }
return 1; return 1;
} else if (update || message.seen != seen) { } else if (message.seen != seen) {
message.seen = seen; message.seen = seen;
message.ui_seen = seen; message.ui_seen = seen;
db.message().updateMessage(message); 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; return -1;
} else { } else {
Log.v(Helper.TAG, folder.name + " unchanged id=" + message.id + " uid=" + message.uid); Log.v(Helper.TAG, folder.name + " unchanged id=" + message.id + " uid=" + message.uid);

Loading…
Cancel
Save