diff --git a/app/src/main/java/eu/faircode/email/AdapterFolder.java b/app/src/main/java/eu/faircode/email/AdapterFolder.java index 4f27bacc..fb2d231b 100644 --- a/app/src/main/java/eu/faircode/email/AdapterFolder.java +++ b/app/src/main/java/eu/faircode/email/AdapterFolder.java @@ -103,6 +103,8 @@ public class AdapterFolder extends RecyclerView.Adapter 0 ? Typeface.BOLD : Typeface.NORMAL); tvName.setTextColor(Helper.resolveColor(context, folder.unseen > 0 ? R.attr.colorUnread : android.R.attr.textColorSecondary)); - tvMessages.setText(Integer.toString(folder.messages)); + tvMessages.setText(String.format("%d/%d", folder.content, folder.messages)); ivUnified.setVisibility(folder.unified ? View.VISIBLE : View.INVISIBLE); diff --git a/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java b/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java index 21e9fe11..0cb9c5e2 100644 --- a/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java +++ b/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java @@ -32,9 +32,11 @@ import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javax.mail.FetchProfile; import javax.mail.Folder; import javax.mail.Message; import javax.mail.Session; +import javax.mail.UIDFolder; import javax.mail.search.AndTerm; import javax.mail.search.BodyTerm; import javax.mail.search.ComparisonTerm; @@ -153,6 +155,16 @@ public class BoundaryCallbackMessages extends PagedList.BoundaryCallback= 0; i -= SYNC_BATCH_SIZE) { + int from = Math.max(0, i - SYNC_BATCH_SIZE) + 1; + Log.i(Helper.TAG, folder.name + " update " + from + " .. " + i); + + Message[] isub = Arrays.copyOfRange(imessages, from, i + 1); + ifolder.fetch(isub, fp); + + for (int j = isub.length - 1; j >= 0; j--) + try { + ids[from + j] = synchronizeMessage(this, folder, ifolder, (IMAPMessage) isub[j], false); + } catch (MessageRemovedException ex) { + Log.w(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } catch (FolderClosedException ex) { + throw ex; + } catch (FolderClosedIOException ex) { + throw ex; + } catch (Throwable ex) { + Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } finally { + ((IMAPMessage) isub[j]).invalidateHeaders(); + } + } + + db.folder().setFolderState(folder.id, "downloading"); + + //fp.add(IMAPFolder.FetchProfileItem.MESSAGE); // Download messages/attachments Log.i(Helper.TAG, folder.name + " download=" + imessages.length); - for (int i = imessages.length - 1; i >= 0; i--) - if (ids[i] != null) + for (int i = imessages.length - 1; i >= 0; i -= DOWNLOAD_BATCH_SIZE) { + int from = Math.max(0, i - DOWNLOAD_BATCH_SIZE) + 1; + Log.i(Helper.TAG, folder.name + " download " + from + " .. " + i); + + Message[] isub = Arrays.copyOfRange(imessages, from, i + 1); + ifolder.fetch(isub, fp); + + for (int j = isub.length - 1; j >= 0; j--) try { - downloadMessage(this, folder, ids[i], (IMAPMessage) imessages[i]); + Log.i(Helper.TAG, folder.name + " download index=" + (from + j) + " id=" + ids[from + j]); + if (ids[i - j] != null) + downloadMessage(this, folder, ids[i - j], (IMAPMessage) isub[j]); } catch (FolderClosedException ex) { throw ex; } catch (FolderClosedIOException ex) { throw ex; } catch (Throwable ex) { Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); + } finally { + // Free memory + ((IMAPMessage) isub[j]).invalidateHeaders(); } + } } finally { Log.v(Helper.TAG, folder.name + " end sync"); @@ -1357,151 +1412,131 @@ public class ServiceSynchronize extends LifecycleService { } static Long synchronizeMessage(Context context, EntityFolder folder, IMAPFolder ifolder, IMAPMessage imessage, boolean found) throws MessagingException, IOException { - long uid; - try { - FetchProfile fp = new FetchProfile(); - fp.add(UIDFolder.FetchProfileItem.UID); - fp.add(IMAPFolder.FetchProfileItem.FLAGS); - ifolder.fetch(new Message[]{imessage}, fp); - - uid = ifolder.getUID(imessage); - //Log.v(Helper.TAG, folder.name + " start sync uid=" + uid); + long uid = ifolder.getUID(imessage); - if (imessage.isExpunged()) { - Log.i(Helper.TAG, folder.name + " expunged uid=" + uid); - throw new MessageRemovedException(); - } - if (imessage.isSet(Flags.Flag.DELETED)) { - Log.i(Helper.TAG, folder.name + " deleted uid=" + uid); - throw new MessageRemovedException(); - } + if (imessage.isExpunged()) { + Log.i(Helper.TAG, folder.name + " expunged uid=" + uid); + throw new MessageRemovedException(); + } + if (imessage.isSet(Flags.Flag.DELETED)) { + Log.i(Helper.TAG, folder.name + " deleted uid=" + uid); + throw new MessageRemovedException(); + } - MessageHelper helper = new MessageHelper(imessage); - boolean seen = helper.getSeen(); - boolean flagged = helper.getFlagged(); + MessageHelper helper = new MessageHelper(imessage); + boolean seen = helper.getSeen(); + boolean flagged = helper.getFlagged(); - DB db = DB.getInstance(context); - try { - db.beginTransaction(); + DB db = DB.getInstance(context); + try { + db.beginTransaction(); - // Find message by uid (fast, no headers required) - EntityMessage message = db.message().getMessageByUid(folder.id, uid); - - // Find message by Message-ID (slow, headers required) - // - messages in inbox have same id as message sent to self - // - messages in archive have same id as original - if (message == null) { - // Will fetch headers within database transaction - String msgid = helper.getMessageID(); - String[] refs = helper.getReferences(); - String reference = (refs.length == 1 && refs[0].indexOf(BuildConfig.APPLICATION_ID) > 0 ? refs[0] : msgid); - Log.i(Helper.TAG, "Searching for " + msgid + " / " + reference); - for (EntityMessage dup : db.message().getMessageByMsgId(folder.account, msgid, reference)) { - EntityFolder dfolder = db.folder().getFolder(dup.folder); - boolean outbox = EntityFolder.OUTBOX.equals(dfolder.type); - Log.i(Helper.TAG, folder.name + " found as id=" + dup.id + - " folder=" + dfolder.type + ":" + dup.folder + "/" + folder.type + ":" + folder.id); - - if (dup.folder.equals(folder.id) || outbox) { - Log.i(Helper.TAG, folder.name + " found as id=" + dup.id + " uid=" + dup.uid + " msgid=" + msgid); - dup.folder = folder.id; - dup.uid = uid; - if (TextUtils.isEmpty(dup.thread)) // outbox: only now the uid is known - dup.thread = helper.getThreadId(uid); - db.message().updateMessage(dup); - message = dup; - } + // Find message by uid (fast, no headers required) + EntityMessage message = db.message().getMessageByUid(folder.id, uid); + + // Find message by Message-ID (slow, headers required) + // - messages in inbox have same id as message sent to self + // - messages in archive have same id as original + if (message == null) { + // Will fetch headers within database transaction + String msgid = helper.getMessageID(); + String[] refs = helper.getReferences(); + String reference = (refs.length == 1 && refs[0].indexOf(BuildConfig.APPLICATION_ID) > 0 ? refs[0] : msgid); + Log.i(Helper.TAG, "Searching for " + msgid + " / " + reference); + for (EntityMessage dup : db.message().getMessageByMsgId(folder.account, msgid, reference)) { + EntityFolder dfolder = db.folder().getFolder(dup.folder); + boolean outbox = EntityFolder.OUTBOX.equals(dfolder.type); + Log.i(Helper.TAG, folder.name + " found as id=" + dup.id + + " folder=" + dfolder.type + ":" + dup.folder + "/" + folder.type + ":" + folder.id); + + if (dup.folder.equals(folder.id) || outbox) { + Log.i(Helper.TAG, folder.name + " found as id=" + dup.id + " uid=" + dup.uid + " msgid=" + msgid); + dup.folder = folder.id; + dup.uid = uid; + if (TextUtils.isEmpty(dup.thread)) // outbox: only now the uid is known + dup.thread = helper.getThreadId(uid); + db.message().updateMessage(dup); + message = dup; } } + } - if (message == null) { - FetchProfile fp1 = new FetchProfile(); - fp1.add(FetchProfile.Item.ENVELOPE); - fp1.add(FetchProfile.Item.CONTENT_INFO); - fp1.add(IMAPFolder.FetchProfileItem.HEADERS); - ifolder.fetch(new Message[]{imessage}, fp1); - - message = new EntityMessage(); - message.account = folder.account; - message.folder = folder.id; - message.uid = uid; - - if (!EntityFolder.ARCHIVE.equals(folder.type)) { - message.msgid = helper.getMessageID(); - if (TextUtils.isEmpty(message.msgid)) - Log.w(Helper.TAG, "No Message-ID id=" + message.id + " uid=" + message.uid); - } - - message.references = TextUtils.join(" ", helper.getReferences()); - message.inreplyto = helper.getInReplyTo(); - message.thread = helper.getThreadId(uid); - message.from = helper.getFrom(); - message.to = helper.getTo(); - message.cc = helper.getCc(); - message.bcc = helper.getBcc(); - message.reply = helper.getReply(); - message.subject = imessage.getSubject(); - message.size = helper.getSize(); - message.content = false; - message.received = imessage.getReceivedDate().getTime(); - message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime()); - message.seen = seen; - message.ui_seen = seen; - message.flagged = false; - message.ui_flagged = false; - message.ui_hide = false; - message.ui_found = found; - - message.id = db.message().insertMessage(message); - - Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid); + if (message == null) { + message = new EntityMessage(); + message.account = folder.account; + message.folder = folder.id; + message.uid = uid; - int sequence = 1; - for (EntityAttachment attachment : helper.getAttachments()) { - 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 (!EntityFolder.ARCHIVE.equals(folder.type)) { + message.msgid = helper.getMessageID(); + if (TextUtils.isEmpty(message.msgid)) + Log.w(Helper.TAG, "No Message-ID id=" + message.id + " uid=" + message.uid); + } - if (message.size != null && attachment.size != null) - message.size -= attachment.size; - } + message.references = TextUtils.join(" ", helper.getReferences()); + message.inreplyto = helper.getInReplyTo(); + message.thread = helper.getThreadId(uid); + message.from = helper.getFrom(); + message.to = helper.getTo(); + message.cc = helper.getCc(); + message.bcc = helper.getBcc(); + message.reply = helper.getReply(); + message.subject = imessage.getSubject(); + message.size = helper.getSize(); + message.content = false; + message.received = imessage.getReceivedDate().getTime(); + message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime()); + message.seen = seen; + message.ui_seen = seen; + message.flagged = false; + message.ui_flagged = false; + message.ui_hide = false; + message.ui_found = found; + + message.id = db.message().insertMessage(message); + + Log.i(Helper.TAG, folder.name + " added id=" + message.id + " uid=" + message.uid); + + int sequence = 1; + for (EntityAttachment attachment : helper.getAttachments()) { + 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 (message.size != null && attachment.size != null) + message.size -= attachment.size; + } + db.message().updateMessage(message); + } else { + if (message.seen != seen || message.seen != message.ui_seen) { + message.seen = seen; + message.ui_seen = seen; db.message().updateMessage(message); - } else { - if (message.seen != seen || message.seen != message.ui_seen) { - message.seen = seen; - message.ui_seen = seen; - db.message().updateMessage(message); - Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen); - } + Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen); + } - if (message.flagged != flagged || message.flagged != message.ui_flagged) { - message.flagged = flagged; - message.ui_flagged = flagged; - db.message().updateMessage(message); - Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " flagged=" + flagged); - } + if (message.flagged != flagged || message.flagged != message.ui_flagged) { + message.flagged = flagged; + message.ui_flagged = flagged; + db.message().updateMessage(message); + Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " flagged=" + flagged); + } - if (message.ui_hide) { - message.ui_hide = false; - db.message().updateMessage(message); - Log.i(Helper.TAG, folder.name + " unhidden id=" + message.id + " uid=" + message.uid); - } + if (message.ui_hide) { + message.ui_hide = false; + db.message().updateMessage(message); + Log.i(Helper.TAG, folder.name + " unhidden id=" + message.id + " uid=" + message.uid); } + } - db.setTransactionSuccessful(); + db.setTransactionSuccessful(); - return message.id; - } finally { - db.endTransaction(); - } + return message.id; } finally { - //Log.v(Helper.TAG, folder.name + " end sync uid=" + uid); - - // Free memory - imessage.invalidateHeaders(); + db.endTransaction(); } } @@ -1520,15 +1555,17 @@ public class ServiceSynchronize extends LifecycleService { Log.i(Helper.TAG, folder.name + " downloaded message id=" + message.id + " size=" + message.size); } - int sequence = 1; - for (EntityAttachment a : helper.getAttachments()) { - EntityAttachment attachment = db.attachment().getAttachment(id, sequence++); - if (!attachment.available) - if (!metered || (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE)) { - attachment.part = a.part; - attachment.download(context, db); - Log.i(Helper.TAG, folder.name + " downloaded message id=" + message.id + " attachment=" + attachment.name + " size=" + message.size); - } + if (db.attachment().getAttachmentDownloadCount(id) > 0) { + int sequence = 1; + for (EntityAttachment a : helper.getAttachments()) { + EntityAttachment attachment = db.attachment().getAttachment(id, sequence++); + if (!attachment.available) + if (attachment.size != null && attachment.size < ATTACHMENT_AUTO_DOWNLOAD_SIZE) { + attachment.part = a.part; + attachment.download(context, db); + Log.i(Helper.TAG, folder.name + " downloaded message id=" + message.id + " attachment=" + attachment.name + " size=" + message.size); + } + } } } diff --git a/app/src/main/java/eu/faircode/email/TupleFolderEx.java b/app/src/main/java/eu/faircode/email/TupleFolderEx.java index 4b9a3a50..b44cc2a9 100644 --- a/app/src/main/java/eu/faircode/email/TupleFolderEx.java +++ b/app/src/main/java/eu/faircode/email/TupleFolderEx.java @@ -22,6 +22,7 @@ package eu.faircode.email; public class TupleFolderEx extends EntityFolder { public String accountName; public int messages; + public int content; public int unseen; @Override @@ -31,6 +32,7 @@ public class TupleFolderEx extends EntityFolder { return (super.equals(obj) && (this.accountName == null ? other.accountName == null : accountName.equals(other.accountName)) && this.messages == other.messages && + this.content == other.content && this.unseen == other.unseen); } else return false; diff --git a/app/src/main/res/layout/fragment_legend.xml b/app/src/main/res/layout/fragment_legend.xml index c190224b..676fb59a 100644 --- a/app/src/main/res/layout/fragment_legend.xml +++ b/app/src/main/res/layout/fragment_legend.xml @@ -248,6 +248,26 @@ app:layout_constraintStart_toEndOf="@id/ivSynchronizing" app:layout_constraintTop_toTopOf="@id/ivSynchronizing" /> + + + + + app:layout_constraintTop_toBottomOf="@id/tvDownloading" /> Connecting Connected Synchronizing + Downloading Closing Long press for options