Store messages in files

pull/50/head
M66B 6 years ago
parent ee7b41f7b4
commit d884c9c7ec

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "23016f9b3ae09175ada077c837687ab6", "identityHash": "cd3cf378d6f71c13ba8beb38a8bf58cf",
"entities": [ "entities": [
{ {
"tableName": "identity", "tableName": "identity",
@ -313,7 +313,7 @@
}, },
{ {
"tableName": "message", "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": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -411,12 +411,6 @@
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
}, },
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": false
},
{ {
"fieldPath": "sent", "fieldPath": "sent",
"columnName": "sent", "columnName": "sent",
@ -599,7 +593,7 @@
}, },
{ {
"tableName": "attachment", "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": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -644,10 +638,10 @@
"notNull": false "notNull": false
}, },
{ {
"fieldPath": "filename", "fieldPath": "available",
"columnName": "filename", "columnName": "available",
"affinity": "TEXT", "affinity": "INTEGER",
"notNull": false "notNull": true
} }
], ],
"primaryKey": { "primaryKey": {
@ -782,7 +776,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, \"23016f9b3ae09175ada077c837687ab6\")" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"cd3cf378d6f71c13ba8beb38a8bf58cf\")"
] ]
} }
} }

@ -225,6 +225,8 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
file.delete(); file.delete();
String body = "<pre>" + sb.toString().replaceAll("\\r?\\n", "<br />") + "</pre>";
EntityMessage draft = null; EntityMessage draft = null;
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
@ -239,12 +241,12 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
draft.msgid = EntityMessage.generateMessageId(); draft.msgid = EntityMessage.generateMessageId();
draft.to = new Address[]{Helper.myAddress()}; draft.to = new Address[]{Helper.myAddress()};
draft.subject = context.getString(R.string.app_name) + " crash log"; draft.subject = context.getString(R.string.app_name) + " crash log";
draft.body = "<pre>" + sb.toString().replaceAll("\\r?\\n", "<br />") + "</pre>";
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.message().insertMessage(draft); draft.id = db.message().insertMessage(draft);
draft.write(context, body);
} }
EntityOperation.queue(db, draft, EntityOperation.ADD); EntityOperation.queue(db, draft, EntityOperation.ADD);

@ -64,7 +64,6 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
TextView tvSize; TextView tvSize;
ImageView ivStatus; ImageView ivStatus;
TextView tvType; TextView tvType;
TextView tvFile;
ProgressBar progressbar; ProgressBar progressbar;
ViewHolder(View itemView) { ViewHolder(View itemView) {
@ -76,7 +75,6 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
tvSize = itemView.findViewById(R.id.tvSize); tvSize = itemView.findViewById(R.id.tvSize);
ivStatus = itemView.findViewById(R.id.ivStatus); ivStatus = itemView.findViewById(R.id.ivStatus);
tvType = itemView.findViewById(R.id.tvType); tvType = itemView.findViewById(R.id.tvType);
tvFile = itemView.findViewById(R.id.tvFile);
progressbar = itemView.findViewById(R.id.progressbar); progressbar = itemView.findViewById(R.id.progressbar);
} }
@ -97,15 +95,15 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
tvSize.setText(Helper.humanReadableByteCount(attachment.size, false)); tvSize.setText(Helper.humanReadableByteCount(attachment.size, false));
tvSize.setVisibility(attachment.size == null ? View.GONE : View.VISIBLE); tvSize.setVisibility(attachment.size == null ? View.GONE : View.VISIBLE);
if (attachment.filename == null) { if (attachment.available) {
ivStatus.setImageResource(R.drawable.baseline_visibility_24);
ivStatus.setVisibility(View.VISIBLE);
} else {
if (attachment.progress == null) { if (attachment.progress == null) {
ivStatus.setImageResource(R.drawable.baseline_get_app_24); ivStatus.setImageResource(R.drawable.baseline_get_app_24);
ivStatus.setVisibility(View.VISIBLE); ivStatus.setVisibility(View.VISIBLE);
} else } else
ivStatus.setVisibility(View.GONE); ivStatus.setVisibility(View.GONE);
} else {
ivStatus.setImageResource(R.drawable.baseline_visibility_24);
ivStatus.setVisibility(View.VISIBLE);
} }
ivDelete.setVisibility(readonly ? View.GONE : View.VISIBLE); ivDelete.setVisibility(readonly ? View.GONE : View.VISIBLE);
@ -113,12 +111,10 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
if (attachment.progress != null) if (attachment.progress != null)
progressbar.setProgress(attachment.progress); progressbar.setProgress(attachment.progress);
progressbar.setVisibility( progressbar.setVisibility(
attachment.progress == null || attachment.filename != null ? View.GONE : View.VISIBLE); attachment.progress == null || attachment.available ? View.GONE : View.VISIBLE);
tvType.setText(attachment.type); tvType.setText(attachment.type);
tvFile.setText(attachment.filename);
tvType.setVisibility(debug ? View.VISIBLE : View.GONE); tvType.setVisibility(debug ? View.VISIBLE : View.GONE);
tvFile.setVisibility(debug ? View.VISIBLE : View.GONE);
} }
@Override @Override
@ -137,7 +133,7 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
protected Void onLoad(Context context, Bundle args) { protected Void onLoad(Context context, Bundle args) {
DB.getInstance(context).attachment().deleteAttachment(attachment.id); DB.getInstance(context).attachment().deleteAttachment(attachment.id);
File dir = new File(context.getFilesDir(), "attachments"); File dir = new File(context.getFilesDir(), "attachments");
File file = new File(dir, attachment.filename); File file = new File(dir, attachment.id.toString());
file.delete(); file.delete();
return null; return null;
@ -145,7 +141,39 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
}.load(context, owner, args); }.load(context, owner, args);
} else { } else {
if (attachment.filename == null) { if (attachment.available) {
// Build file name
File dir = new File(context.getFilesDir(), "attachments");
File file = new File(dir, attachment.id.toString());
// https://developer.android.com/reference/android/support/v4/content/FileProvider
Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID, file);
Log.i(Helper.TAG, "uri=" + uri);
// Build intent
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, attachment.type);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.i(Helper.TAG, "Sharing " + file + " type=" + attachment.type);
Log.i(Helper.TAG, "Intent=" + intent);
//context.startActivity(Intent.createChooser(intent, attachment.name));
// Set permissions
List<ResolveInfo> targets = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : targets) {
Log.i(Helper.TAG, "Target=" + resolveInfo);
context.grantUriPermission(resolveInfo.activityInfo.packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
// Check if viewer available
if (targets.size() == 0) {
Toast.makeText(context, R.string.title_no_viewer, Toast.LENGTH_LONG).show();
return;
}
context.startActivity(intent);
} else {
if (attachment.progress == null) { if (attachment.progress == null) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", attachment.id); args.putLong("id", attachment.id);
@ -179,38 +207,6 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
} }
}.load(context, owner, args); }.load(context, owner, args);
} }
} else {
// Build file name
File dir = new File(context.getFilesDir(), "attachments");
File file = new File(dir, attachment.filename);
// https://developer.android.com/reference/android/support/v4/content/FileProvider
Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID, file);
Log.i(Helper.TAG, "uri=" + uri);
// Build intent
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, attachment.type);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Log.i(Helper.TAG, "Sharing " + file + " type=" + attachment.type);
Log.i(Helper.TAG, "Intent=" + intent);
//context.startActivity(Intent.createChooser(intent, attachment.name));
// Set permissions
List<ResolveInfo> targets = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : targets) {
Log.i(Helper.TAG, "Target=" + resolveInfo);
context.grantUriPermission(resolveInfo.activityInfo.packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
// Check if viewer available
if (targets.size() == 0) {
Toast.makeText(context, R.string.title_no_viewer, Toast.LENGTH_LONG).show();
return;
}
context.startActivity(intent);
} }
} }
} }

@ -71,7 +71,7 @@ public abstract class DB extends RoomDatabase {
private static DB sInstance; private static DB sInstance;
private static final String DB_NAME = "fairemail"; private static final String DB_NAME = "email";
public static synchronized DB getInstance(Context context) { public static synchronized DB getInstance(Context context) {
if (sInstance == null) if (sInstance == null)

@ -54,7 +54,8 @@ public class EntityAttachment {
public String type; public String type;
public Integer size; public Integer size;
public Integer progress; public Integer progress;
public String filename; @NonNull
public Boolean available = false;
@Ignore @Ignore
BodyPart part; BodyPart part;
@ -69,7 +70,7 @@ public class EntityAttachment {
this.type.equals(other.type) && this.type.equals(other.type) &&
(this.size == null ? other.size == null : this.size.equals(other.size)) && (this.size == null ? other.size == null : this.size.equals(other.size)) &&
(this.progress == null ? other.progress == null : this.progress.equals(other.progress)) && (this.progress == null ? other.progress == null : this.progress.equals(other.progress)) &&
(this.filename == null ? other.filename == null : this.filename.equals(other.filename))); this.available.equals(other.available));
} else } else
return false; return false;
} }

@ -19,6 +19,15 @@ package eu.faircode.email;
Copyright 2018 by Marcel Bokhorst (M66B) Copyright 2018 by Marcel Bokhorst (M66B)
*/ */
import android.content.Context;
import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.Random; import java.util.Random;
@ -76,7 +85,6 @@ public class EntityMessage {
public Address[] bcc; public Address[] bcc;
public Address[] reply; public Address[] reply;
public String subject; public String subject;
public String body;
public Long sent; // compose = null public Long sent; // compose = null
@NonNull @NonNull
public Long received; // compose = stored public Long received; // compose = stored
@ -100,6 +108,52 @@ public class EntityMessage {
return sb.toString(); return sb.toString();
} }
void write(Context context, String body) throws IOException {
File dir = new File(context.getFilesDir(), "messages");
dir.mkdir();
File file = new File(dir, id.toString());
BufferedWriter out = null;
try {
out = new BufferedWriter(new FileWriter(file));
out.write(body);
} finally {
if (out != null)
try {
out.close();
} catch (IOException e) {
Log.e(Helper.TAG, e + "\n" + Log.getStackTraceString(e));
}
}
}
String read(Context context) throws IOException {
return read(context, this.id);
}
static String read(Context context, Long id) throws IOException {
File dir = new File(context.getFilesDir(), "messages");
File file = new File(dir, id.toString());
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
StringBuilder body = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
body.append(line);
body.append('\n');
}
return body.toString();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
}
}
}
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof EntityMessage) { if (obj instanceof EntityMessage) {
@ -119,7 +173,6 @@ public class EntityMessage {
equal(this.bcc, other.bcc) && equal(this.bcc, other.bcc) &&
equal(this.reply, other.reply) && equal(this.reply, other.reply) &&
(this.subject == null ? other.subject == null : this.subject.equals(other.subject)) && (this.subject == null ? other.subject == null : this.subject.equals(other.subject)) &&
(this.body == null ? other.body == null : this.body.equals(other.body)) &&
(this.sent == null ? other.sent == null : this.sent.equals(other.sent)) && (this.sent == null ? other.sent == null : this.sent.equals(other.sent)) &&
this.received.equals(other.received) && this.received.equals(other.received) &&
this.seen.equals(other.seen) && this.seen.equals(other.seen) &&

@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -30,6 +31,7 @@ import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Date; import java.util.Date;
@ -80,6 +82,8 @@ public class FragmentAbout extends FragmentEx {
sb.append(Helper.getLogcat()); sb.append(Helper.getLogcat());
String body = "<pre>" + sb.toString().replaceAll("\\r?\\n", "<br />") + "</pre>";
EntityMessage draft; EntityMessage draft;
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
@ -96,16 +100,19 @@ public class FragmentAbout extends FragmentEx {
draft.msgid = EntityMessage.generateMessageId(); draft.msgid = EntityMessage.generateMessageId();
draft.to = new Address[]{Helper.myAddress()}; draft.to = new Address[]{Helper.myAddress()};
draft.subject = context.getString(R.string.app_name) + " debug info"; draft.subject = context.getString(R.string.app_name) + " debug info";
draft.body = "<pre>" + sb.toString().replaceAll("\\r?\\n", "<br />") + "</pre>";
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.message().insertMessage(draft); draft.id = db.message().insertMessage(draft);
draft.write(context, body);
EntityOperation.queue(db, draft, EntityOperation.ADD); EntityOperation.queue(db, draft, EntityOperation.ADD);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} catch (IOException ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
return null;
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
@ -118,9 +125,10 @@ public class FragmentAbout extends FragmentEx {
@Override @Override
protected void onLoaded(Bundle args, Long id) { protected void onLoaded(Bundle args, Long id) {
btnDebugInfo.setEnabled(true); btnDebugInfo.setEnabled(true);
startActivity(new Intent(getContext(), ActivityCompose.class) if (id != null)
.putExtra("action", "edit") startActivity(new Intent(getContext(), ActivityCompose.class)
.putExtra("id", id)); .putExtra("action", "edit")
.putExtra("id", id));
} }
@Override @Override

@ -30,6 +30,7 @@ import android.os.Handler;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.text.Html; import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -516,7 +517,7 @@ public class FragmentCompose extends FragmentEx {
attachment.size = size; attachment.size = size;
attachment.progress = null; attachment.progress = null;
attachment.filename = file.getName(); attachment.available = true;
db.attachment().updateAttachment(attachment); db.attachment().updateAttachment(attachment);
} finally { } finally {
try { try {
@ -530,7 +531,6 @@ public class FragmentCompose extends FragmentEx {
} catch (Throwable ex) { } catch (Throwable ex) {
// Reset progress on failure // Reset progress on failure
attachment.progress = null; attachment.progress = null;
attachment.filename = null;
db.attachment().updateAttachment(attachment); db.attachment().updateAttachment(attachment);
throw ex; throw ex;
} }
@ -571,7 +571,7 @@ public class FragmentCompose extends FragmentEx {
private SimpleTask<EntityMessage> draftLoader = new SimpleTask<EntityMessage>() { private SimpleTask<EntityMessage> draftLoader = new SimpleTask<EntityMessage>() {
@Override @Override
protected EntityMessage onLoad(Context context, Bundle args) { protected EntityMessage onLoad(Context context, Bundle args) throws IOException {
String action = args.getString("action"); String action = args.getString("action");
long id = args.getLong("id", -1); long id = args.getLong("id", -1);
long account = args.getLong("account", -1); long account = args.getLong("account", -1);
@ -618,6 +618,8 @@ public class FragmentCompose extends FragmentEx {
if (drafts == null) if (drafts == null)
throw new IllegalArgumentException("no drafts folder"); throw new IllegalArgumentException("no drafts folder");
String body = "";
draft = new EntityMessage(); draft = new EntityMessage();
draft.account = account; draft.account = account;
draft.folder = drafts.id; draft.folder = drafts.id;
@ -640,21 +642,21 @@ public class FragmentCompose extends FragmentEx {
if ("reply".equals(action) || "reply_all".equals(action)) { if ("reply".equals(action) || "reply_all".equals(action)) {
draft.subject = context.getString(R.string.title_subject_reply, ref.subject); draft.subject = context.getString(R.string.title_subject_reply, ref.subject);
draft.body = String.format("<br><br>%s %s:<br><br>%s", body = String.format("<br><br>%s %s:<br><br>%s",
Html.escapeHtml(new Date().toString()), Html.escapeHtml(new Date().toString()),
Html.escapeHtml(TextUtils.join(", ", draft.to)), Html.escapeHtml(TextUtils.join(", ", draft.to)),
HtmlHelper.sanitize(context, ref.body, true)); HtmlHelper.sanitize(context, ref.read(context), true));
} else if ("forward".equals(action)) { } else if ("forward".equals(action)) {
draft.subject = context.getString(R.string.title_subject_forward, ref.subject); draft.subject = context.getString(R.string.title_subject_forward, ref.subject);
draft.body = String.format("<br><br>%s %s:<br><br>%s", body = String.format("<br><br>%s %s:<br><br>%s",
Html.escapeHtml(new Date().toString()), Html.escapeHtml(new Date().toString()),
Html.escapeHtml(TextUtils.join(", ", ref.from)), Html.escapeHtml(TextUtils.join(", ", ref.from)),
HtmlHelper.sanitize(context, ref.body, true)); HtmlHelper.sanitize(context, ref.read(context), true));
} }
} }
if ("new".equals(action)) if ("new".equals(action))
draft.body = ""; body = "";
draft.received = new Date().getTime(); draft.received = new Date().getTime();
draft.seen = false; draft.seen = false;
@ -662,6 +664,7 @@ public class FragmentCompose extends FragmentEx {
draft.ui_hide = false; draft.ui_hide = false;
draft.id = db.message().insertMessage(draft); draft.id = db.message().insertMessage(draft);
draft.write(context, body);
EntityOperation.queue(db, draft, EntityOperation.ADD); 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)); etBcc.setText(draft.bcc == null ? null : TextUtils.join(", ", draft.bcc));
etSubject.setText(draft.subject); 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<Spanned>() {
@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(); getActivity().invalidateOptionsMenu();
Helper.setViewsEnabled(view, true); Helper.setViewsEnabled(view, true);
@ -836,14 +854,16 @@ public class FragmentCompose extends FragmentEx {
draft.cc = acc; draft.cc = acc;
draft.bcc = abcc; draft.bcc = abcc;
draft.subject = subject; draft.subject = subject;
draft.body = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
draft.received = new Date().getTime(); draft.received = new Date().getTime();
body = "<pre>" + body.replaceAll("\\r?\\n", "<br />") + "</pre>";
// Execute action // Execute action
if (action == R.id.action_trash) { if (action == R.id.action_trash) {
draft.ui_seen = true; draft.ui_seen = true;
draft.ui_hide = true; draft.ui_hide = true;
db.message().updateMessage(draft); db.message().updateMessage(draft);
draft.write(context, body);
EntityOperation.queue(db, draft, EntityOperation.SEEN, true); EntityOperation.queue(db, draft, EntityOperation.SEEN, true);
@ -857,11 +877,13 @@ public class FragmentCompose extends FragmentEx {
return null; return null;
db.message().updateMessage(draft); db.message().updateMessage(draft);
draft.write(context, body);
EntityOperation.queue(db, draft, EntityOperation.ADD); EntityOperation.queue(db, draft, EntityOperation.ADD);
} else if (action == R.id.action_send) { } else if (action == R.id.action_send) {
db.message().updateMessage(draft); db.message().updateMessage(draft);
draft.write(context, body);
// Check data // Check data
if (draft.identity == null) if (draft.identity == null)
@ -876,7 +898,7 @@ public class FragmentCompose extends FragmentEx {
// Save attachments // Save attachments
List<EntityAttachment> attachments = db.attachment().getAttachments(draft.id); List<EntityAttachment> attachments = db.attachment().getAttachments(draft.id);
for (EntityAttachment attachment : attachments) for (EntityAttachment attachment : attachments)
if (attachment.filename == null) if (!attachment.available)
throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing)); throw new IllegalArgumentException(context.getString(R.string.title_attachments_missing));
// Delete draft (cannot move to outbox) // Delete draft (cannot move to outbox)

@ -29,6 +29,7 @@ import android.preference.PreferenceManager;
import android.text.Html; import android.text.Html;
import android.text.Layout; import android.text.Layout;
import android.text.Spannable; import android.text.Spannable;
import android.text.Spanned;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan; import android.text.style.URLSpan;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -339,9 +340,23 @@ public class FragmentMessage extends FragmentEx {
: R.drawable.baseline_visibility_24); : R.drawable.baseline_visibility_24);
actionSeen.setTitle(message.ui_seen ? R.string.title_unseen : R.string.title_seen); actionSeen.setTitle(message.ui_seen ? R.string.title_unseen : R.string.title_seen);
tvBody.setText(message.body == null tvBody.setText(null);
? null
: Html.fromHtml(HtmlHelper.sanitize(getContext(), message.body, false))); Bundle args = new Bundle();
args.putLong("id", message.id);
new SimpleTask<Spanned>() {
@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).removeObservers(getViewLifecycleOwner());
db.folder().liveFolders(message.account).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() { db.folder().liveFolders(message.account).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() {

@ -93,7 +93,7 @@ public class MessageHelper {
return props; return props;
} }
static MimeMessageEx from(Context context, EntityMessage message, List<EntityAttachment> attachments, Session isession) throws MessagingException { static MimeMessageEx from(Context context, EntityMessage message, List<EntityAttachment> attachments, Session isession) throws MessagingException, IOException {
MimeMessageEx imessage = new MimeMessageEx(isession, message.msgid); MimeMessageEx imessage = new MimeMessageEx(isession, message.msgid);
imessage.setFlag(Flags.Flag.SEEN, message.seen); imessage.setFlag(Flags.Flag.SEEN, message.seen);
@ -115,25 +115,22 @@ public class MessageHelper {
// TODO: plain message? // TODO: plain message?
if (message.body == null)
throw new IllegalArgumentException("null message");
if (attachments.size() == 0) if (attachments.size() == 0)
imessage.setText(message.body, Charset.defaultCharset().name(), "html"); imessage.setText(message.read(context), Charset.defaultCharset().name(), "html");
else { else {
Multipart multipart = new MimeMultipart(); Multipart multipart = new MimeMultipart();
BodyPart bpMessage = new MimeBodyPart(); 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); multipart.addBodyPart(bpMessage);
for (final EntityAttachment attachment : attachments) for (final EntityAttachment attachment : attachments)
if (attachment.filename != null) { if (attachment.available) {
BodyPart bpAttachment = new MimeBodyPart(); BodyPart bpAttachment = new MimeBodyPart();
bpAttachment.setFileName(attachment.name); bpAttachment.setFileName(attachment.name);
File dir = new File(context.getFilesDir(), "attachments"); 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); FileDataSource dataSource = new FileDataSource(file);
dataSource.setFileTypeMap(new FileTypeMap() { dataSource.setFileTypeMap(new FileTypeMap() {
@Override @Override
@ -159,7 +156,7 @@ public class MessageHelper {
return imessage; return imessage;
} }
static MimeMessageEx from(Context context, EntityMessage message, EntityMessage reply, List<EntityAttachment> attachments, Session isession) throws MessagingException { static MimeMessageEx from(Context context, EntityMessage message, EntityMessage reply, List<EntityAttachment> attachments, Session isession) throws MessagingException, IOException {
MimeMessageEx imessage = from(context, message, attachments, isession); MimeMessageEx imessage = from(context, message, attachments, isession);
imessage.addHeader("In-Reply-To", reply.msgid); imessage.addHeader("In-Reply-To", reply.msgid);
imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid); imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid);

@ -851,7 +851,7 @@ public class ServiceSynchronize extends LifecycleService {
db.message().setMessageSeen(message.id, seen); 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 // Append message
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id); List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
@ -871,7 +871,7 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, "Appended uid=" + message.uid); 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 // Move message
long id = jargs.getLong(0); long id = jargs.getLong(0);
EntityFolder target = db.folder().getFolder(id); EntityFolder target = db.folder().getFolder(id);
@ -914,7 +914,7 @@ public class ServiceSynchronize extends LifecycleService {
db.message().deleteMessage(message.id); 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 // Send message
EntityIdentity ident = db.identity().getIdentity(message.identity); EntityIdentity ident = db.identity().getIdentity(message.identity);
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying)); EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
@ -1033,7 +1033,7 @@ public class ServiceSynchronize extends LifecycleService {
// Store attachment data // Store attachment data
attachment.size = size; attachment.size = size;
attachment.progress = null; attachment.progress = null;
attachment.filename = file.getName(); attachment.available = true;
db.attachment().updateAttachment(attachment); db.attachment().updateAttachment(attachment);
} finally { } finally {
try { try {
@ -1048,7 +1048,6 @@ public class ServiceSynchronize extends LifecycleService {
} catch (Throwable ex) { } catch (Throwable ex) {
// Reset progress on failure // Reset progress on failure
attachment.progress = null; attachment.progress = null;
attachment.filename = null;
db.attachment().updateAttachment(attachment); db.attachment().updateAttachment(attachment);
throw ex; throw ex;
} }
@ -1296,7 +1295,6 @@ public class ServiceSynchronize extends LifecycleService {
message.bcc = helper.getBcc(); message.bcc = helper.getBcc();
message.reply = helper.getReply(); message.reply = helper.getReply();
message.subject = imessage.getSubject(); message.subject = imessage.getSubject();
message.body = helper.getHtml();
message.received = imessage.getReceivedDate().getTime(); message.received = imessage.getReceivedDate().getTime();
message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime()); message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime());
message.seen = seen; message.seen = seen;
@ -1304,6 +1302,7 @@ 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);
message.write(this, helper.getHtml());
Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid); Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid);
int sequence = 0; int sequence = 0;

@ -61,24 +61,15 @@
<TextView <TextView
android:id="@+id/tvType" android:id="@+id/tvType"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:text="text/plain" android:text="text/plain"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="@id/ivStatus"
app:layout_constraintStart_toEndOf="@id/ivAttachments" app:layout_constraintStart_toEndOf="@id/ivAttachments"
app:layout_constraintTop_toBottomOf="@id/ivAttachments" /> app:layout_constraintTop_toBottomOf="@id/ivAttachments" />
<TextView
android:id="@+id/tvFile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:text="filename"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/ivAttachments" />
<ProgressBar <ProgressBar
android:id="@+id/progressbar" android:id="@+id/progressbar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" style="@style/Widget.AppCompat.ProgressBar.Horizontal"

Loading…
Cancel
Save