mirror of https://github.com/M66B/FairEmail.git
Download small messages on metered connection only, refactoring, improvements
This commit is contained in:
parent
5d07791b99
commit
265e7fe88f
8
FAQ.md
8
FAQ.md
|
@ -30,15 +30,15 @@ Most, if not all, other email apps don't show a notification with the "side effe
|
|||
|
||||
The low priority status bar notification shows the number of pending operations, which can be:
|
||||
|
||||
* seen: mark message as seen/unseen in remote folder
|
||||
* add: add message to remote folder
|
||||
* move: move message to another remote folder
|
||||
* delete: delete message from remote folder
|
||||
* send: send message
|
||||
* attachment: download attachment
|
||||
* headers: download message headers
|
||||
* body: download message body
|
||||
* seen: mark message as seen/unseen in remote folder
|
||||
* flag: star/unstar remote message
|
||||
* headers: download message headers
|
||||
* body: download message text
|
||||
* attachment: download attachment
|
||||
|
||||
Operations are processed only when there is a connection to the email server or when manually synchronizing.
|
||||
See also [this FAQ](#FAQ16).
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 16,
|
||||
"identityHash": "bac90fa06592121fae18c1305a2e5b25",
|
||||
"identityHash": "95bd1e083056fa7625f21430f7d53e2d",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "identity",
|
||||
|
@ -369,7 +369,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, `avatar` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `downloaded` INTEGER NOT NULL, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` 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, `avatar` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `size` INTEGER, `content` INTEGER NOT NULL, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` 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",
|
||||
|
@ -480,8 +480,14 @@
|
|||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloaded",
|
||||
"columnName": "downloaded",
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
|
@ -964,7 +970,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, \"bac90fa06592121fae18c1305a2e5b25\")"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"95bd1e083056fa7625f21430f7d53e2d\")"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -301,6 +301,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
draft.msgid = EntityMessage.generateMessageId();
|
||||
draft.to = new Address[]{Helper.myAddress()};
|
||||
draft.subject = context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " crash log";
|
||||
draft.content = true;
|
||||
draft.received = new Date().getTime();
|
||||
draft.seen = false;
|
||||
draft.ui_seen = false;
|
||||
|
@ -757,7 +758,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
|
|||
try {
|
||||
db.beginTransaction();
|
||||
|
||||
if (!message.downloaded)
|
||||
if (!message.content)
|
||||
EntityOperation.queue(db, message, EntityOperation.BODY);
|
||||
|
||||
for (EntityMessage tmessage : db.message().getMessageByThread(message.account, message.thread)) {
|
||||
|
|
|
@ -98,7 +98,7 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
|
|||
tvName.setText(attachment.name);
|
||||
|
||||
if (attachment.size != null)
|
||||
tvSize.setText(Helper.humanReadableByteCount(attachment.size, false));
|
||||
tvSize.setText(Helper.humanReadableByteCount(attachment.size, true));
|
||||
tvSize.setVisibility(attachment.size == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
if (attachment.available) {
|
||||
|
|
|
@ -86,6 +86,7 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
|||
ImageView ivAvatar;
|
||||
ImageView ivFlagged;
|
||||
TextView tvFrom;
|
||||
TextView tvSize;
|
||||
TextView tvTime;
|
||||
ImageView ivAttachments;
|
||||
TextView tvSubject;
|
||||
|
@ -103,9 +104,10 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
|||
|
||||
this.itemView = itemView;
|
||||
vwColor = itemView.findViewById(R.id.vwColor);
|
||||
ivAvatar = itemView.findViewById(R.id.ivAvatar);
|
||||
ivFlagged = itemView.findViewById(R.id.ivFlagged);
|
||||
ivAvatar = itemView.findViewById(R.id.ivAvatar);
|
||||
tvFrom = itemView.findViewById(R.id.tvFrom);
|
||||
tvSize = itemView.findViewById(R.id.tvSize);
|
||||
tvTime = itemView.findViewById(R.id.tvTime);
|
||||
ivAttachments = itemView.findViewById(R.id.ivAttachments);
|
||||
tvSubject = itemView.findViewById(R.id.tvSubject);
|
||||
|
@ -130,8 +132,8 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
|||
ivFlagged.setVisibility(View.GONE);
|
||||
ivAvatar.setVisibility(View.GONE);
|
||||
tvFrom.setText(null);
|
||||
tvSize.setText(null);
|
||||
tvTime.setText(null);
|
||||
tvSubject.setText(null);
|
||||
ivAttachments.setVisibility(View.GONE);
|
||||
tvSubject.setText(null);
|
||||
tvFolder.setText(null);
|
||||
|
@ -201,8 +203,11 @@ public class AdapterMessage extends PagedListAdapter<TupleMessageEx, AdapterMess
|
|||
tvTime.setText(DateUtils.getRelativeTimeSpanString(context, message.received));
|
||||
}
|
||||
|
||||
tvSubject.setText(message.subject);
|
||||
tvSize.setText(message.size == null ? null : Helper.humanReadableByteCount(message.size, true));
|
||||
tvSize.setVisibility(message.size == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE);
|
||||
tvSubject.setText(message.subject);
|
||||
|
||||
if (viewType == ViewType.UNIFIED)
|
||||
tvFolder.setText(message.accountName);
|
||||
|
|
|
@ -225,7 +225,8 @@ public abstract class DB extends RoomDatabase {
|
|||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `downloaded` INTEGER NOT NULL DEFAULT 1");
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `size` INTEGER");
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `content` INTEGER NOT NULL DEFAULT 1");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
|
|
@ -200,8 +200,8 @@ public interface DaoMessage {
|
|||
@Query("UPDATE message SET ui_found = 0 WHERE folder = :folder")
|
||||
int resetFound(long folder);
|
||||
|
||||
@Query("UPDATE message SET downloaded = :downloaded WHERE id = :id")
|
||||
int setMessageDownloaded(long id, boolean downloaded);
|
||||
@Query("UPDATE message SET content = :content WHERE id = :id")
|
||||
int setMessageContent(long id, boolean content);
|
||||
|
||||
@Query("UPDATE message SET headers = :headers WHERE id = :id")
|
||||
int setMessageHeaders(long id, String headers);
|
||||
|
|
|
@ -20,10 +20,17 @@ package eu.faircode.email;
|
|||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.mail.BodyPart;
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Entity;
|
||||
|
@ -47,6 +54,7 @@ import static androidx.room.ForeignKey.CASCADE;
|
|||
)
|
||||
public class EntityAttachment {
|
||||
static final String TABLE_NAME = "attachment";
|
||||
static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
|
@ -73,6 +81,54 @@ public class EntityAttachment {
|
|||
return new File(dir, Long.toString(id));
|
||||
}
|
||||
|
||||
void download(Context context, DB db) throws MessagingException, IOException {
|
||||
// Build filename
|
||||
File file = EntityAttachment.getFile(context, this.id);
|
||||
|
||||
// Download attachment
|
||||
InputStream is = null;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
this.progress = null;
|
||||
db.attachment().updateAttachment(this);
|
||||
|
||||
is = this.part.getInputStream();
|
||||
os = new BufferedOutputStream(new FileOutputStream(file));
|
||||
|
||||
int size = 0;
|
||||
byte[] buffer = new byte[ATTACHMENT_BUFFER_SIZE];
|
||||
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
|
||||
size += len;
|
||||
os.write(buffer, 0, len);
|
||||
|
||||
// Update progress
|
||||
if (this.size != null)
|
||||
db.attachment().setProgress(this.id, size * 100 / this.size);
|
||||
}
|
||||
|
||||
// Store attachment data
|
||||
this.size = size;
|
||||
this.progress = null;
|
||||
this.available = true;
|
||||
db.attachment().updateAttachment(this);
|
||||
|
||||
Log.i(Helper.TAG, "Downloaded attachment size=" + this.size);
|
||||
} catch (IOException ex) {
|
||||
// Reset progress on failure
|
||||
this.progress = null;
|
||||
db.attachment().updateAttachment(this);
|
||||
throw ex;
|
||||
} finally {
|
||||
try {
|
||||
if (is != null)
|
||||
is.close();
|
||||
} finally {
|
||||
if (os != null)
|
||||
os.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EntityAttachment) {
|
||||
|
|
|
@ -90,8 +90,9 @@ public class EntityMessage implements Serializable {
|
|||
public Address[] reply;
|
||||
public String headers;
|
||||
public String subject;
|
||||
public Integer size;
|
||||
@NonNull
|
||||
public Boolean downloaded = false;
|
||||
public Boolean content = false;
|
||||
public Long sent; // compose = null
|
||||
@NonNull
|
||||
public Long received; // compose = stored
|
||||
|
@ -137,7 +138,6 @@ public class EntityMessage implements Serializable {
|
|||
this.body = (body == null ? "" : body);
|
||||
out = new BufferedWriter(new FileWriter(file));
|
||||
out.write(this.body);
|
||||
this.downloaded = true;
|
||||
} finally {
|
||||
if (out != null)
|
||||
try {
|
||||
|
|
|
@ -70,9 +70,9 @@ public class EntityOperation {
|
|||
public static final String MOVE = "move";
|
||||
public static final String DELETE = "delete";
|
||||
public static final String SEND = "send";
|
||||
public static final String ATTACHMENT = "attachment";
|
||||
public static final String HEADERS = "headers";
|
||||
public static final String BODY = "body";
|
||||
public static final String ATTACHMENT = "attachment";
|
||||
public static final String FLAG = "flag";
|
||||
|
||||
private static List<Intent> queue = new ArrayList<>();
|
||||
|
|
|
@ -119,6 +119,7 @@ public class FragmentAbout extends FragmentEx {
|
|||
draft.msgid = EntityMessage.generateMessageId();
|
||||
draft.to = new Address[]{Helper.myAddress()};
|
||||
draft.subject = context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " debug info";
|
||||
draft.content = true;
|
||||
draft.received = new Date().getTime();
|
||||
draft.seen = false;
|
||||
draft.ui_seen = false;
|
||||
|
|
|
@ -670,7 +670,7 @@ public class FragmentCompose extends FragmentEx {
|
|||
os = new BufferedOutputStream(new FileOutputStream(file));
|
||||
|
||||
int size = 0;
|
||||
byte[] buffer = new byte[Helper.ATTACHMENT_BUFFER_SIZE];
|
||||
byte[] buffer = new byte[EntityAttachment.ATTACHMENT_BUFFER_SIZE];
|
||||
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
|
||||
size += len;
|
||||
os.write(buffer, 0, len);
|
||||
|
@ -877,6 +877,7 @@ public class FragmentCompose extends FragmentEx {
|
|||
body = "<br />" + account.signature + body;
|
||||
}
|
||||
|
||||
draft.content = true;
|
||||
draft.received = new Date().getTime();
|
||||
draft.seen = false;
|
||||
draft.ui_seen = false;
|
||||
|
|
|
@ -483,7 +483,7 @@ public class FragmentMessage extends FragmentEx {
|
|||
|
||||
pbBody.setVisibility(View.VISIBLE);
|
||||
|
||||
if (message.downloaded)
|
||||
if (message.content)
|
||||
bodyTask.load(FragmentMessage.this, args);
|
||||
|
||||
btnImages.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -536,6 +536,7 @@ public class FragmentMessage extends FragmentEx {
|
|||
|
||||
// Observe message
|
||||
db.message().liveMessage(message.id).observe(getViewLifecycleOwner(), new Observer<TupleMessageEx>() {
|
||||
private boolean observing = false;
|
||||
|
||||
@Override
|
||||
public void onChanged(@Nullable final TupleMessageEx message) {
|
||||
|
@ -555,7 +556,7 @@ public class FragmentMessage extends FragmentEx {
|
|||
pbRawHeaders.setVisibility(!free && headers && message.headers == null ? View.VISIBLE : View.GONE);
|
||||
|
||||
// Body can be downloaded
|
||||
if (message.downloaded) {
|
||||
if (message.content) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
args.putBoolean("show_images", show_images);
|
||||
|
@ -572,7 +573,9 @@ public class FragmentMessage extends FragmentEx {
|
|||
setSubtitle(Helper.localizeFolderName(getContext(), message.folderName));
|
||||
|
||||
// Observe folders
|
||||
db.folder().liveFolders(message.account).removeObservers(getViewLifecycleOwner());
|
||||
if (observing)
|
||||
return;
|
||||
observing = true;
|
||||
db.folder().liveFolders(message.account).observe(getViewLifecycleOwner(), new Observer<List<TupleFolderEx>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<TupleFolderEx> folders) {
|
||||
|
@ -605,7 +608,7 @@ public class FragmentMessage extends FragmentEx {
|
|||
bottom_navigation.getMenu().findItem(R.id.action_delete).setVisible((message.uid != null && hasTrash) || (inOutbox && !TextUtils.isEmpty(message.error)));
|
||||
bottom_navigation.getMenu().findItem(R.id.action_move).setVisible(message.uid != null && (!inInbox || hasUser));
|
||||
bottom_navigation.getMenu().findItem(R.id.action_archive).setVisible(message.uid != null && !inArchive && hasArchive);
|
||||
bottom_navigation.getMenu().findItem(R.id.action_reply).setVisible(message.downloaded && !inOutbox);
|
||||
bottom_navigation.getMenu().findItem(R.id.action_reply).setVisible(message.content && !inOutbox);
|
||||
bottom_navigation.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
@ -623,7 +626,7 @@ public class FragmentMessage extends FragmentEx {
|
|||
adapter.set(attachments);
|
||||
grpAttachments.setVisibility(!free && attachments.size() > 0 ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (message.downloaded) {
|
||||
if (message.content) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
args.putBoolean("show_images", show_images);
|
||||
|
@ -664,12 +667,12 @@ public class FragmentMessage extends FragmentEx {
|
|||
|
||||
menu.findItem(R.id.menu_addresses).setVisible(!free);
|
||||
menu.findItem(R.id.menu_thread).setVisible(message.count > 1);
|
||||
menu.findItem(R.id.menu_forward).setVisible(message.downloaded && !inOutbox);
|
||||
menu.findItem(R.id.menu_forward).setVisible(message.content && !inOutbox);
|
||||
menu.findItem(R.id.menu_show_headers).setChecked(headers);
|
||||
menu.findItem(R.id.menu_show_headers).setEnabled(message.uid != null);
|
||||
menu.findItem(R.id.menu_show_headers).setVisible(!free);
|
||||
menu.findItem(R.id.menu_show_html).setEnabled(message.downloaded && Helper.classExists("android.webkit.WebView"));
|
||||
menu.findItem(R.id.menu_reply_all).setVisible(message.downloaded && !inOutbox);
|
||||
menu.findItem(R.id.menu_show_html).setEnabled(message.content && Helper.classExists("android.webkit.WebView"));
|
||||
menu.findItem(R.id.menu_reply_all).setVisible(message.content && !inOutbox);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -46,6 +46,7 @@ import java.io.OutputStream;
|
|||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import javax.mail.Address;
|
||||
|
@ -64,8 +65,6 @@ public class Helper {
|
|||
static final int AUTH_TYPE_PASSWORD = 1;
|
||||
static final int AUTH_TYPE_GMAIL = 2;
|
||||
|
||||
static final int ATTACHMENT_BUFFER_SIZE = 8192; // bytes
|
||||
|
||||
static ThreadFactory backgroundThreadFactory = new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(@NonNull Runnable runnable) {
|
||||
|
@ -137,7 +136,7 @@ public class Helper {
|
|||
if (bytes < unit) return bytes + " B";
|
||||
int exp = (int) (Math.log(bytes) / Math.log(unit));
|
||||
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
|
||||
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
|
||||
return new DecimalFormat("@@").format(bytes / Math.pow(unit, exp)) + " " + pre + "B";
|
||||
}
|
||||
|
||||
static boolean classExists(String className) {
|
||||
|
|
|
@ -92,6 +92,8 @@ public class MessageHelper {
|
|||
//props.put("mail.imaps.compress.strategy", "0");
|
||||
}
|
||||
|
||||
props.put("mail.imaps.fetchsize", Integer.toString(64 * 1024)); // default 16K
|
||||
|
||||
// https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#properties
|
||||
props.put("mail.smtps.ssl.checkserveridentity", "true");
|
||||
props.put("mail.smtps.ssl.trust", "*");
|
||||
|
@ -270,6 +272,11 @@ public class MessageHelper {
|
|||
return null;
|
||||
}
|
||||
|
||||
Integer getSize() throws MessagingException {
|
||||
int size = imessage.getSize();
|
||||
return (size < 0 ? null : size);
|
||||
}
|
||||
|
||||
static String getFormattedAddresses(Address[] addresses, boolean full) {
|
||||
if (addresses == null || addresses.length == 0)
|
||||
return "";
|
||||
|
|
|
@ -54,12 +54,7 @@ import com.sun.mail.util.MailConnectException;
|
|||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
|
@ -127,6 +122,7 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
private static final int CONNECT_BACKOFF_START = 8; // seconds
|
||||
private static final int CONNECT_BACKOFF_MAX = 1024; // seconds (1024 sec ~ 17 min)
|
||||
private static final long STORE_NOOP_INTERVAL = 9 * 60 * 1000L; // ms
|
||||
private static final int MESSAGE_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes
|
||||
private static final int ATTACHMENT_AUTO_DOWNLOAD_SIZE = 32 * 1024; // bytes
|
||||
|
||||
static final String ACTION_SYNCHRONIZE_FOLDER = BuildConfig.APPLICATION_ID + ".SYNCHRONIZE_FOLDER";
|
||||
|
@ -925,15 +921,15 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
else if (EntityOperation.SEND.equals(op.name))
|
||||
doSend(message, db);
|
||||
|
||||
else if (EntityOperation.ATTACHMENT.equals(op.name))
|
||||
doAttachment(folder, op, ifolder, message, jargs, db);
|
||||
|
||||
else if (EntityOperation.HEADERS.equals(op.name))
|
||||
doHeaders(folder, ifolder, message, db);
|
||||
|
||||
else if (EntityOperation.BODY.equals(op.name))
|
||||
doBody(folder, ifolder, message, db);
|
||||
|
||||
else if (EntityOperation.ATTACHMENT.equals(op.name))
|
||||
doAttachment(folder, op, ifolder, message, jargs, db);
|
||||
|
||||
else
|
||||
throw new MessagingException("Unknown operation name=" + op.name);
|
||||
|
||||
|
@ -1142,68 +1138,6 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
}
|
||||
}
|
||||
|
||||
private void doAttachment(EntityFolder folder, EntityOperation op, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException, IOException {
|
||||
// Download attachment
|
||||
int sequence = jargs.getInt(0);
|
||||
|
||||
EntityAttachment attachment = db.attachment().getAttachment(op.message, sequence);
|
||||
if (attachment == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
// Get message
|
||||
Message imessage = ifolder.getMessageByUID(message.uid);
|
||||
if (imessage == null)
|
||||
throw new MessageRemovedException();
|
||||
|
||||
// Get attachment
|
||||
MessageHelper helper = new MessageHelper((MimeMessage) imessage);
|
||||
EntityAttachment a = helper.getAttachments().get(sequence - 1);
|
||||
|
||||
// Build filename
|
||||
File file = EntityAttachment.getFile(this, attachment.id);
|
||||
|
||||
// Download attachment
|
||||
InputStream is = null;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
is = a.part.getInputStream();
|
||||
os = new BufferedOutputStream(new FileOutputStream(file));
|
||||
|
||||
int size = 0;
|
||||
byte[] buffer = new byte[Helper.ATTACHMENT_BUFFER_SIZE];
|
||||
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
|
||||
size += len;
|
||||
os.write(buffer, 0, len);
|
||||
|
||||
// Update progress
|
||||
if (attachment.size != null)
|
||||
db.attachment().setProgress(attachment.id, size * 100 / attachment.size);
|
||||
}
|
||||
|
||||
// Store attachment data
|
||||
attachment.size = size;
|
||||
attachment.progress = null;
|
||||
attachment.available = true;
|
||||
db.attachment().updateAttachment(attachment);
|
||||
} finally {
|
||||
try {
|
||||
if (is != null)
|
||||
is.close();
|
||||
} finally {
|
||||
if (os != null)
|
||||
os.close();
|
||||
}
|
||||
}
|
||||
Log.i(Helper.TAG, folder.name + " downloaded bytes=" + attachment.size);
|
||||
} catch (Throwable ex) {
|
||||
// Reset progress on failure
|
||||
attachment.progress = null;
|
||||
db.attachment().updateAttachment(attachment);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private void doHeaders(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, DB db) throws MessagingException {
|
||||
Message imessage = ifolder.getMessageByUID(message.uid);
|
||||
Enumeration<Header> headers = imessage.getAllHeaders();
|
||||
|
@ -1216,10 +1150,39 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
}
|
||||
|
||||
private void doBody(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, DB db) throws MessagingException, IOException {
|
||||
// Download message body
|
||||
if (message.content)
|
||||
return;
|
||||
|
||||
// Get message
|
||||
Message imessage = ifolder.getMessageByUID(message.uid);
|
||||
if (imessage == null)
|
||||
throw new MessageRemovedException();
|
||||
|
||||
MessageHelper helper = new MessageHelper((MimeMessage) imessage);
|
||||
message.write(this, helper.getHtml());
|
||||
db.message().setMessageDownloaded(message.id, true);
|
||||
db.message().setMessageContent(message.id, true);
|
||||
}
|
||||
|
||||
private void doAttachment(EntityFolder folder, EntityOperation op, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException, IOException {
|
||||
// Download attachment
|
||||
int sequence = jargs.getInt(0);
|
||||
|
||||
// Get attachment
|
||||
EntityAttachment attachment = db.attachment().getAttachment(op.message, sequence);
|
||||
if (attachment.available)
|
||||
return;
|
||||
|
||||
// Get message
|
||||
Message imessage = ifolder.getMessageByUID(message.uid);
|
||||
if (imessage == null)
|
||||
throw new MessageRemovedException();
|
||||
|
||||
// Download attachment
|
||||
MessageHelper helper = new MessageHelper((MimeMessage) imessage);
|
||||
EntityAttachment a = helper.getAttachments().get(sequence - 1);
|
||||
attachment.part = a.part;
|
||||
attachment.download(this, db);
|
||||
}
|
||||
|
||||
private void synchronizeFolders(EntityAccount account, IMAPStore istore, ServiceState state) throws MessagingException {
|
||||
|
@ -1501,7 +1464,8 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
message.bcc = helper.getBcc();
|
||||
message.reply = helper.getReply();
|
||||
message.subject = imessage.getSubject();
|
||||
message.downloaded = download;
|
||||
message.size = helper.getSize();
|
||||
message.content = false;
|
||||
message.received = imessage.getReceivedDate().getTime();
|
||||
message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime());
|
||||
message.seen = seen;
|
||||
|
@ -1513,22 +1477,29 @@ public class ServiceSynchronize extends LifecycleService {
|
|||
|
||||
message.id = db.message().insertMessage(message);
|
||||
|
||||
if (download)
|
||||
message.write(context, helper.getHtml());
|
||||
if (download) {
|
||||
ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
|
||||
boolean metered = cm.isActiveNetworkMetered();
|
||||
if (!metered || (message.size != null && message.size < MESSAGE_AUTO_DOWNLOAD_SIZE)) {
|
||||
message.write(context, helper.getHtml());
|
||||
db.message().setMessageContent(message.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid);
|
||||
|
||||
int sequence = 0;
|
||||
for (EntityAttachment attachment : helper.getAttachments()) {
|
||||
sequence++;
|
||||
Log.i(Helper.TAG, "attachment seq=" + sequence +
|
||||
" name=" + attachment.name + " type=" + attachment.type);
|
||||
Log.i(Helper.TAG, folder.name + " attachment" +
|
||||
" seq=" + sequence + " name=" + attachment.name + " type=" + attachment.type);
|
||||
attachment.message = message.id;
|
||||
attachment.sequence = sequence;
|
||||
attachment.id = db.attachment().insertAttachment(attachment);
|
||||
|
||||
if (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE)
|
||||
EntityOperation.queue(db, message, EntityOperation.ATTACHMENT, sequence);
|
||||
if (download)
|
||||
if (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE)
|
||||
attachment.download(context, db);
|
||||
}
|
||||
|
||||
result = 1;
|
||||
|
|
|
@ -45,10 +45,21 @@
|
|||
android:maxLines="1"
|
||||
android:text="From"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvTime"
|
||||
app:layout_constraintEnd_toStartOf="@+id/tvSize"
|
||||
app:layout_constraintStart_toEndOf="@id/ivAvatar"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:maxLines="1"
|
||||
android:text="123 KB"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintBottom_toBottomOf="@id/tvFrom"
|
||||
app:layout_constraintEnd_toStartOf="@id/tvTime" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTime"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
Loading…
Reference in New Issue