From d884c9c7eca459f39734cb05dc9ddf813ee40b91 Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 19 Aug 2018 06:53:56 +0000 Subject: [PATCH] Store messages in files --- app/schemas/eu.faircode.email.DB/1.json | 22 ++--- .../java/eu/faircode/email/ActivityView.java | 4 +- .../eu/faircode/email/AdapterAttachment.java | 82 +++++++++---------- app/src/main/java/eu/faircode/email/DB.java | 2 +- .../eu/faircode/email/EntityAttachment.java | 5 +- .../java/eu/faircode/email/EntityMessage.java | 57 ++++++++++++- .../java/eu/faircode/email/FragmentAbout.java | 16 +++- .../eu/faircode/email/FragmentCompose.java | 44 +++++++--- .../eu/faircode/email/FragmentMessage.java | 21 ++++- .../java/eu/faircode/email/MessageHelper.java | 15 ++-- .../eu/faircode/email/ServiceSynchronize.java | 11 ++- app/src/main/res/layout/item_attachment.xml | 13 +-- 12 files changed, 185 insertions(+), 107 deletions(-) diff --git a/app/schemas/eu.faircode.email.DB/1.json b/app/schemas/eu.faircode.email.DB/1.json index 57b2db602d..9a486f8317 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": "23016f9b3ae09175ada077c837687ab6", + "identityHash": "cd3cf378d6f71c13ba8beb38a8bf58cf", "entities": [ { "tableName": "identity", @@ -313,7 +313,7 @@ }, { "tableName": "message", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `body` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", @@ -411,12 +411,6 @@ "affinity": "TEXT", "notNull": false }, - { - "fieldPath": "body", - "columnName": "body", - "affinity": "TEXT", - "notNull": false - }, { "fieldPath": "sent", "columnName": "sent", @@ -599,7 +593,7 @@ }, { "tableName": "attachment", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `filename` TEXT, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", @@ -644,10 +638,10 @@ "notNull": false }, { - "fieldPath": "filename", - "columnName": "filename", - "affinity": "TEXT", - "notNull": false + "fieldPath": "available", + "columnName": "available", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -782,7 +776,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, \"23016f9b3ae09175ada077c837687ab6\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"cd3cf378d6f71c13ba8beb38a8bf58cf\")" ] } } \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index 542cab7746..94c3f4f1a4 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -225,6 +225,8 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack file.delete(); + String body = "
" + sb.toString().replaceAll("\\r?\\n", "
") + "
"; + EntityMessage draft = null; DB db = DB.getInstance(context); @@ -239,12 +241,12 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack draft.msgid = EntityMessage.generateMessageId(); draft.to = new Address[]{Helper.myAddress()}; draft.subject = context.getString(R.string.app_name) + " crash log"; - draft.body = "
" + sb.toString().replaceAll("\\r?\\n", "
") + "
"; draft.received = new Date().getTime(); draft.seen = false; draft.ui_seen = false; draft.ui_hide = false; draft.id = db.message().insertMessage(draft); + draft.write(context, body); } EntityOperation.queue(db, draft, EntityOperation.ADD); diff --git a/app/src/main/java/eu/faircode/email/AdapterAttachment.java b/app/src/main/java/eu/faircode/email/AdapterAttachment.java index 394e66ec6c..5cdecd5170 100644 --- a/app/src/main/java/eu/faircode/email/AdapterAttachment.java +++ b/app/src/main/java/eu/faircode/email/AdapterAttachment.java @@ -64,7 +64,6 @@ public class AdapterAttachment extends RecyclerView.Adapter") + ""; + EntityMessage draft; DB db = DB.getInstance(context); @@ -96,16 +100,19 @@ public class FragmentAbout extends FragmentEx { draft.msgid = EntityMessage.generateMessageId(); draft.to = new Address[]{Helper.myAddress()}; draft.subject = context.getString(R.string.app_name) + " debug info"; - draft.body = "
" + sb.toString().replaceAll("\\r?\\n", "
") + "
"; draft.received = new Date().getTime(); draft.seen = false; draft.ui_seen = false; draft.ui_hide = false; draft.id = db.message().insertMessage(draft); + draft.write(context, body); EntityOperation.queue(db, draft, EntityOperation.ADD); db.setTransactionSuccessful(); + } catch (IOException ex) { + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + return null; } finally { db.endTransaction(); } @@ -118,9 +125,10 @@ public class FragmentAbout extends FragmentEx { @Override protected void onLoaded(Bundle args, Long id) { btnDebugInfo.setEnabled(true); - startActivity(new Intent(getContext(), ActivityCompose.class) - .putExtra("action", "edit") - .putExtra("id", id)); + if (id != null) + startActivity(new Intent(getContext(), ActivityCompose.class) + .putExtra("action", "edit") + .putExtra("id", id)); } @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 80da6041d7..0bb8e31d2e 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.provider.ContactsContract; import android.provider.OpenableColumns; import android.text.Html; +import android.text.Spanned; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -516,7 +517,7 @@ public class FragmentCompose extends FragmentEx { attachment.size = size; attachment.progress = null; - attachment.filename = file.getName(); + attachment.available = true; db.attachment().updateAttachment(attachment); } finally { try { @@ -530,7 +531,6 @@ public class FragmentCompose extends FragmentEx { } catch (Throwable ex) { // Reset progress on failure attachment.progress = null; - attachment.filename = null; db.attachment().updateAttachment(attachment); throw ex; } @@ -571,7 +571,7 @@ public class FragmentCompose extends FragmentEx { private SimpleTask draftLoader = new SimpleTask() { @Override - protected EntityMessage onLoad(Context context, Bundle args) { + protected EntityMessage onLoad(Context context, Bundle args) throws IOException { String action = args.getString("action"); long id = args.getLong("id", -1); long account = args.getLong("account", -1); @@ -618,6 +618,8 @@ public class FragmentCompose extends FragmentEx { if (drafts == null) throw new IllegalArgumentException("no drafts folder"); + String body = ""; + draft = new EntityMessage(); draft.account = account; draft.folder = drafts.id; @@ -640,21 +642,21 @@ public class FragmentCompose extends FragmentEx { if ("reply".equals(action) || "reply_all".equals(action)) { draft.subject = context.getString(R.string.title_subject_reply, ref.subject); - draft.body = String.format("

%s %s:

%s", + body = String.format("

%s %s:

%s", Html.escapeHtml(new Date().toString()), Html.escapeHtml(TextUtils.join(", ", draft.to)), - HtmlHelper.sanitize(context, ref.body, true)); + HtmlHelper.sanitize(context, ref.read(context), true)); } else if ("forward".equals(action)) { draft.subject = context.getString(R.string.title_subject_forward, ref.subject); - draft.body = String.format("

%s %s:

%s", + body = String.format("

%s %s:

%s", Html.escapeHtml(new Date().toString()), Html.escapeHtml(TextUtils.join(", ", ref.from)), - HtmlHelper.sanitize(context, ref.body, true)); + HtmlHelper.sanitize(context, ref.read(context), true)); } } if ("new".equals(action)) - draft.body = ""; + body = ""; draft.received = new Date().getTime(); draft.seen = false; @@ -662,6 +664,7 @@ public class FragmentCompose extends FragmentEx { draft.ui_hide = false; draft.id = db.message().insertMessage(draft); + draft.write(context, body); EntityOperation.queue(db, draft, EntityOperation.ADD); @@ -687,7 +690,22 @@ public class FragmentCompose extends FragmentEx { etBcc.setText(draft.bcc == null ? null : TextUtils.join(", ", draft.bcc)); etSubject.setText(draft.subject); - etBody.setText(TextUtils.isEmpty(draft.body) ? null : Html.fromHtml(draft.body)); + etBody.setText(null); + + Bundle a = new Bundle(); + a.putLong("id", draft.id); + + new SimpleTask() { + @Override + protected Spanned onLoad(Context context, Bundle args) throws Throwable { + return Html.fromHtml(EntityMessage.read(context, args.getLong("id"))); + } + + @Override + protected void onLoaded(Bundle args, Spanned body) { + etBody.setText(body); + } + }.load(FragmentCompose.this, a); getActivity().invalidateOptionsMenu(); Helper.setViewsEnabled(view, true); @@ -836,14 +854,16 @@ public class FragmentCompose extends FragmentEx { draft.cc = acc; draft.bcc = abcc; draft.subject = subject; - draft.body = "
" + body.replaceAll("\\r?\\n", "
") + "
"; draft.received = new Date().getTime(); + body = "
" + body.replaceAll("\\r?\\n", "
") + "
"; + // Execute action if (action == R.id.action_trash) { draft.ui_seen = true; draft.ui_hide = true; db.message().updateMessage(draft); + draft.write(context, body); EntityOperation.queue(db, draft, EntityOperation.SEEN, true); @@ -857,11 +877,13 @@ public class FragmentCompose extends FragmentEx { return null; db.message().updateMessage(draft); + draft.write(context, body); EntityOperation.queue(db, draft, EntityOperation.ADD); } else if (action == R.id.action_send) { db.message().updateMessage(draft); + draft.write(context, body); // Check data if (draft.identity == null) @@ -876,7 +898,7 @@ public class FragmentCompose extends FragmentEx { // Save attachments List attachments = db.attachment().getAttachments(draft.id); for (EntityAttachment attachment : attachments) - if (attachment.filename == null) + if (!attachment.available) throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing)); // Delete draft (cannot move to outbox) diff --git a/app/src/main/java/eu/faircode/email/FragmentMessage.java b/app/src/main/java/eu/faircode/email/FragmentMessage.java index 5be144b7a2..67abcbd48f 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessage.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessage.java @@ -29,6 +29,7 @@ import android.preference.PreferenceManager; import android.text.Html; import android.text.Layout; import android.text.Spannable; +import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.text.style.URLSpan; import android.view.LayoutInflater; @@ -339,9 +340,23 @@ public class FragmentMessage extends FragmentEx { : R.drawable.baseline_visibility_24); actionSeen.setTitle(message.ui_seen ? R.string.title_unseen : R.string.title_seen); - tvBody.setText(message.body == null - ? null - : Html.fromHtml(HtmlHelper.sanitize(getContext(), message.body, false))); + tvBody.setText(null); + + Bundle args = new Bundle(); + args.putLong("id", message.id); + + new SimpleTask() { + @Override + protected Spanned onLoad(Context context, Bundle args) throws Throwable { + String body = EntityMessage.read(context, args.getLong("id")); + return Html.fromHtml(HtmlHelper.sanitize(getContext(), body, false)); + } + + @Override + protected void onLoaded(Bundle args, Spanned body) { + tvBody.setText(body); + } + }.load(FragmentMessage.this, args); db.folder().liveFolders(message.account).removeObservers(getViewLifecycleOwner()); db.folder().liveFolders(message.account).observe(getViewLifecycleOwner(), new Observer>() { diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 1fcbddfd5b..10155ad799 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -93,7 +93,7 @@ public class MessageHelper { return props; } - static MimeMessageEx from(Context context, EntityMessage message, List attachments, Session isession) throws MessagingException { + static MimeMessageEx from(Context context, EntityMessage message, List attachments, Session isession) throws MessagingException, IOException { MimeMessageEx imessage = new MimeMessageEx(isession, message.msgid); imessage.setFlag(Flags.Flag.SEEN, message.seen); @@ -115,25 +115,22 @@ public class MessageHelper { // TODO: plain message? - if (message.body == null) - throw new IllegalArgumentException("null message"); - if (attachments.size() == 0) - imessage.setText(message.body, Charset.defaultCharset().name(), "html"); + imessage.setText(message.read(context), Charset.defaultCharset().name(), "html"); else { Multipart multipart = new MimeMultipart(); BodyPart bpMessage = new MimeBodyPart(); - bpMessage.setContent(message.body, "text/html; charset=" + Charset.defaultCharset().name()); + bpMessage.setContent(message.read(context), "text/html; charset=" + Charset.defaultCharset().name()); multipart.addBodyPart(bpMessage); for (final EntityAttachment attachment : attachments) - if (attachment.filename != null) { + if (attachment.available) { BodyPart bpAttachment = new MimeBodyPart(); bpAttachment.setFileName(attachment.name); File dir = new File(context.getFilesDir(), "attachments"); - File file = new File(dir, attachment.filename); + File file = new File(dir, attachment.id.toString()); FileDataSource dataSource = new FileDataSource(file); dataSource.setFileTypeMap(new FileTypeMap() { @Override @@ -159,7 +156,7 @@ public class MessageHelper { return imessage; } - static MimeMessageEx from(Context context, EntityMessage message, EntityMessage reply, List attachments, Session isession) throws MessagingException { + static MimeMessageEx from(Context context, EntityMessage message, EntityMessage reply, List attachments, Session isession) throws MessagingException, IOException { MimeMessageEx imessage = from(context, message, attachments, isession); imessage.addHeader("In-Reply-To", reply.msgid); imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid); diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 5a038eeb1e..3aea8a97b5 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -851,7 +851,7 @@ public class ServiceSynchronize extends LifecycleService { db.message().setMessageSeen(message.id, seen); } - private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException { + private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException { // Append message List attachments = db.attachment().getAttachments(message.id); @@ -871,7 +871,7 @@ public class ServiceSynchronize extends LifecycleService { Log.i(Helper.TAG, "Appended uid=" + message.uid); } - private void doMove(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException { + private void doMove(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException, IOException { // Move message long id = jargs.getLong(0); EntityFolder target = db.folder().getFolder(id); @@ -914,7 +914,7 @@ public class ServiceSynchronize extends LifecycleService { db.message().deleteMessage(message.id); } - private void doSend(Session isession, EntityMessage message, DB db) throws MessagingException { + private void doSend(Session isession, EntityMessage message, DB db) throws MessagingException, IOException { // Send message EntityIdentity ident = db.identity().getIdentity(message.identity); EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying)); @@ -1033,7 +1033,7 @@ public class ServiceSynchronize extends LifecycleService { // Store attachment data attachment.size = size; attachment.progress = null; - attachment.filename = file.getName(); + attachment.available = true; db.attachment().updateAttachment(attachment); } finally { try { @@ -1048,7 +1048,6 @@ public class ServiceSynchronize extends LifecycleService { } catch (Throwable ex) { // Reset progress on failure attachment.progress = null; - attachment.filename = null; db.attachment().updateAttachment(attachment); throw ex; } @@ -1296,7 +1295,6 @@ public class ServiceSynchronize extends LifecycleService { message.bcc = helper.getBcc(); message.reply = helper.getReply(); message.subject = imessage.getSubject(); - message.body = helper.getHtml(); message.received = imessage.getReceivedDate().getTime(); message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime()); message.seen = seen; @@ -1304,6 +1302,7 @@ public class ServiceSynchronize extends LifecycleService { message.ui_hide = false; message.id = db.message().insertMessage(message); + message.write(this, helper.getHtml()); Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid); int sequence = 0; diff --git a/app/src/main/res/layout/item_attachment.xml b/app/src/main/res/layout/item_attachment.xml index d7626bd749..f008aa2899 100644 --- a/app/src/main/res/layout/item_attachment.xml +++ b/app/src/main/res/layout/item_attachment.xml @@ -61,24 +61,15 @@ - -