Merge pull request #4046 from ByteHamster/mark-as-read-on-delete

Mark messages read when deleting
This commit is contained in:
cketti 2020-01-08 19:00:38 +01:00 committed by GitHub
commit b336ad283f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 199 additions and 28 deletions

View file

@ -180,6 +180,7 @@ public class Account implements BaseAccount, StoreConfig {
private boolean openPgpEncryptSubject;
private boolean openPgpEncryptAllDrafts;
private boolean markMessageAsReadOnView;
private boolean markMessageAsReadOnDelete;
private boolean alwaysShowCcBcc;
private boolean allowRemoteSearch;
private boolean remoteSearchFullText;
@ -1039,6 +1040,14 @@ public class Account implements BaseAccount, StoreConfig {
markMessageAsReadOnView = value;
}
public synchronized boolean isMarkMessageAsReadOnDelete() {
return markMessageAsReadOnDelete;
}
public synchronized void setMarkMessageAsReadOnDelete(boolean value) {
markMessageAsReadOnDelete = value;
}
public synchronized boolean isAlwaysShowCcBcc() {
return alwaysShowCcBcc;
}

View file

@ -150,6 +150,7 @@ class AccountPreferenceSerializer(
isEnabled = storage.getBoolean("$accountUuid.enabled", true)
isMarkMessageAsReadOnView = storage.getBoolean("$accountUuid.markMessageAsReadOnView", true)
isMarkMessageAsReadOnDelete = storage.getBoolean("$accountUuid.markMessageAsReadOnDelete", true)
isAlwaysShowCcBcc = storage.getBoolean("$accountUuid.alwaysShowCcBcc", false)
// Use email address as account description if necessary
@ -290,6 +291,7 @@ class AccountPreferenceSerializer(
editor.putBoolean("$accountUuid.uploadSentMessages", isUploadSentMessages)
editor.putBoolean("$accountUuid.enabled", isEnabled)
editor.putBoolean("$accountUuid.markMessageAsReadOnView", isMarkMessageAsReadOnView)
editor.putBoolean("$accountUuid.markMessageAsReadOnDelete", isMarkMessageAsReadOnDelete)
editor.putBoolean("$accountUuid.alwaysShowCcBcc", isAlwaysShowCcBcc)
editor.putBoolean("$accountUuid.vibrate", notificationSetting.isVibrateEnabled)
@ -400,6 +402,7 @@ class AccountPreferenceSerializer(
editor.remove("$accountUuid.autocryptMutualMode")
editor.remove("$accountUuid.enabled")
editor.remove("$accountUuid.markMessageAsReadOnView")
editor.remove("$accountUuid.markMessageAsReadOnDelete")
editor.remove("$accountUuid.alwaysShowCcBcc")
editor.remove("$accountUuid.allowRemoteSearch")
editor.remove("$accountUuid.remoteSearchFullText")
@ -547,6 +550,7 @@ class AccountPreferenceSerializer(
isUploadSentMessages = true
isEnabled = true
isMarkMessageAsReadOnView = true
isMarkMessageAsReadOnDelete = true
isAlwaysShowCcBcc = false
setArchiveFolder(null, SpecialFolderSelection.AUTOMATIC)

View file

@ -53,6 +53,7 @@ import com.fsck.k9.controller.MessagingControllerCommands.PendingDelete;
import com.fsck.k9.controller.MessagingControllerCommands.PendingEmptyTrash;
import com.fsck.k9.controller.MessagingControllerCommands.PendingExpunge;
import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead;
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveAndMarkAsRead;
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy;
import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag;
import com.fsck.k9.controller.ProgressBodyFactory.ProgressListener;
@ -929,18 +930,44 @@ public class MessagingController {
}
}
private void queueMoveOrCopy(Account account, String srcFolder, String destFolder, boolean isCopy,
List<String> uids) {
PendingCommand command = PendingMoveOrCopy.create(srcFolder, destFolder, isCopy, uids);
private void queueMoveOrCopy(Account account, String srcFolder, String destFolder,
MoveOrCopyFlavor operation, List<String> uids) {
PendingCommand command;
switch (operation) {
case MOVE:
command = PendingMoveOrCopy.create(srcFolder, destFolder, false, uids);
break;
case COPY:
command = PendingMoveOrCopy.create(srcFolder, destFolder, true, uids);
break;
case MOVE_AND_MARK_AS_READ:
command = PendingMoveAndMarkAsRead.create(srcFolder, destFolder, uids);
break;
default:
return;
}
queuePendingCommand(account, command);
}
private void queueMoveOrCopy(Account account, String srcFolder, String destFolder,
boolean isCopy, List<String> uids, Map<String, String> uidMap) {
MoveOrCopyFlavor operation, List<String> uids, Map<String, String> uidMap) {
if (uidMap == null || uidMap.isEmpty()) {
queueMoveOrCopy(account, srcFolder, destFolder, isCopy, uids);
queueMoveOrCopy(account, srcFolder, destFolder, operation, uids);
} else {
PendingCommand command = PendingMoveOrCopy.create(srcFolder, destFolder, isCopy, uidMap);
PendingCommand command;
switch (operation) {
case MOVE:
command = PendingMoveOrCopy.create(srcFolder, destFolder, false, uidMap);
break;
case COPY:
command = PendingMoveOrCopy.create(srcFolder, destFolder, true, uidMap);
break;
case MOVE_AND_MARK_AS_READ:
command = PendingMoveAndMarkAsRead.create(srcFolder, destFolder, uidMap);
break;
default:
return;
}
queuePendingCommand(account, command);
}
}
@ -948,17 +975,28 @@ public class MessagingController {
void processPendingMoveOrCopy(PendingMoveOrCopy command, Account account) throws MessagingException {
String srcFolder = command.srcFolder;
String destFolder = command.destFolder;
boolean isCopy = command.isCopy;
MoveOrCopyFlavor operation = command.isCopy ? MoveOrCopyFlavor.COPY : MoveOrCopyFlavor.MOVE;
Map<String, String> newUidMap = command.newUidMap;
List<String> uids = newUidMap != null ? new ArrayList<>(newUidMap.keySet()) : command.uids;
processPendingMoveOrCopy(account, srcFolder, destFolder, uids, isCopy, newUidMap);
processPendingMoveOrCopy(account, srcFolder, destFolder, uids, operation, newUidMap);
}
void processPendingMoveAndRead(PendingMoveAndMarkAsRead command, Account account) throws MessagingException {
String srcFolder = command.srcFolder;
String destFolder = command.destFolder;
Map<String, String> newUidMap = command.newUidMap;
List<String> uids = newUidMap != null ? new ArrayList<>(newUidMap.keySet()) : command.uids;
processPendingMoveOrCopy(account, srcFolder, destFolder, uids,
MoveOrCopyFlavor.MOVE_AND_MARK_AS_READ, newUidMap);
}
@VisibleForTesting
void processPendingMoveOrCopy(Account account, String srcFolder, String destFolder, List<String> uids,
boolean isCopy, Map<String, String> newUidMap) throws MessagingException {
MoveOrCopyFlavor operation, Map<String, String> newUidMap) throws MessagingException {
LocalFolder localDestFolder;
LocalStore localStore = localStoreProvider.getInstance(account);
@ -967,13 +1005,22 @@ public class MessagingController {
Backend backend = getBackend(account);
Map<String, String> remoteUidMap;
if (isCopy) {
remoteUidMap = backend.copyMessages(srcFolder, destFolder, uids);
} else {
remoteUidMap = backend.moveMessages(srcFolder, destFolder, uids);
switch (operation) {
case COPY:
remoteUidMap = backend.copyMessages(srcFolder, destFolder, uids);
break;
case MOVE:
remoteUidMap = backend.moveMessages(srcFolder, destFolder, uids);
break;
case MOVE_AND_MARK_AS_READ:
remoteUidMap = backend.moveMessagesAndMarkAsRead(srcFolder, destFolder, uids);
break;
default:
throw new RuntimeException("Unsupported messaging operation");
}
if (!isCopy && backend.getSupportsExpunge() && account.getExpungePolicy() == Expunge.EXPUNGE_IMMEDIATELY) {
if (operation != MoveOrCopyFlavor.COPY && backend.getSupportsExpunge()
&& account.getExpungePolicy() == Expunge.EXPUNGE_IMMEDIATELY) {
Timber.i("processingPendingMoveOrCopy expunging folder %s:%s", account.getDescription(), srcFolder);
backend.expungeMessages(srcFolder, uids);
}
@ -1831,7 +1878,7 @@ public class MessagingController {
putBackground("moveMessages", null, new Runnable() {
@Override
public void run() {
moveOrCopyMessageSynchronous(account, srcFolder, messages, destFolder, false);
moveOrCopyMessageSynchronous(account, srcFolder, messages, destFolder, MoveOrCopyFlavor.MOVE);
}
});
}
@ -1850,7 +1897,8 @@ public class MessagingController {
public void run() {
try {
List<Message> messagesInThreads = collectMessagesInThreads(account, messages);
moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destFolder, false);
moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destFolder,
MoveOrCopyFlavor.MOVE);
} catch (MessagingException e) {
Timber.e(e, "Exception while moving messages");
}
@ -1873,7 +1921,8 @@ public class MessagingController {
putBackground("copyMessages", null, new Runnable() {
@Override
public void run() {
moveOrCopyMessageSynchronous(srcAccount, srcFolder, messages, destFolder, true);
moveOrCopyMessageSynchronous(srcAccount, srcFolder, messages, destFolder,
MoveOrCopyFlavor.COPY);
}
});
}
@ -1891,7 +1940,7 @@ public class MessagingController {
try {
List<Message> messagesInThreads = collectMessagesInThreads(account, messages);
moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destFolder,
true);
MoveOrCopyFlavor.COPY);
} catch (MessagingException e) {
Timber.e(e, "Exception while copying messages");
}
@ -1908,14 +1957,17 @@ public class MessagingController {
}
private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder,
final List<? extends Message> inMessages, final String destFolder, final boolean isCopy) {
final List<? extends Message> inMessages, final String destFolder,
MoveOrCopyFlavor operation) {
try {
LocalStore localStore = localStoreProvider.getInstance(account);
if (!isCopy && !isMoveCapable(account)) {
if ((operation == MoveOrCopyFlavor.MOVE
|| operation == MoveOrCopyFlavor.MOVE_AND_MARK_AS_READ)
&& !isMoveCapable(account)) {
return;
}
if (isCopy && !isCopyCapable(account)) {
if (operation == MoveOrCopyFlavor.COPY && !isCopyCapable(account)) {
return;
}
@ -1944,11 +1996,11 @@ public class MessagingController {
}
Timber.i("moveOrCopyMessageSynchronous: source folder = %s, %d messages, destination folder = %s, " +
"isCopy = %s", srcFolder, messages.size(), destFolder, isCopy);
"operation = %s", srcFolder, messages.size(), destFolder, operation.name());
Map<String, String> uidMap;
if (isCopy) {
if (operation == MoveOrCopyFlavor.COPY) {
FetchProfile fp = new FetchProfile();
fp.add(Item.ENVELOPE);
fp.add(Item.BODY);
@ -1972,6 +2024,10 @@ public class MessagingController {
l.messageUidChanged(account, srcFolder, origUid, message.getUid());
}
}
if (operation == MoveOrCopyFlavor.MOVE_AND_MARK_AS_READ) {
localDestFolder.setFlags(messages, Collections.singleton(Flag.SEEN), true);
unreadCountAffected = true;
}
unsuppressMessages(account, messages);
if (unreadCountAffected) {
@ -1987,7 +2043,7 @@ public class MessagingController {
}
List<String> origUidKeys = new ArrayList<>(origUidMap.keySet());
queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidKeys, uidMap);
queueMoveOrCopy(account, srcFolder, destFolder, operation, origUidKeys, uidMap);
}
processPendingCommands(account);
@ -2173,6 +2229,10 @@ public class MessagingController {
Timber.d("Deleting messages in normal folder, moving");
localTrashFolder = localStore.getFolder(account.getTrashFolder());
uidMap = localFolder.moveMessages(messages, localTrashFolder);
if (account.isMarkMessageAsReadOnDelete()) {
localTrashFolder.setFlags(messages, Collections.singleton(Flag.SEEN), true);
}
}
for (MessagingListener l : getListeners()) {
@ -2197,9 +2257,12 @@ public class MessagingController {
if (account.getDeletePolicy() == DeletePolicy.ON_DELETE) {
if (folder.equals(account.getTrashFolder()) || !backend.isDeleteMoveToTrash()) {
queueDelete(account, folder, syncedMessageUids);
} else if (account.isMarkMessageAsReadOnDelete()) {
queueMoveOrCopy(account, folder, account.getTrashFolder(),
MoveOrCopyFlavor.MOVE_AND_MARK_AS_READ, syncedMessageUids, uidMap);
} else {
queueMoveOrCopy(account, folder, account.getTrashFolder(), false,
syncedMessageUids, uidMap);
queueMoveOrCopy(account, folder, account.getTrashFolder(),
MoveOrCopyFlavor.MOVE, syncedMessageUids, uidMap);
}
processPendingCommands(account);
} else if (account.getDeletePolicy() == DeletePolicy.MARK_AS_READ) {
@ -3117,4 +3180,8 @@ public class MessagingController {
}
}
}
private enum MoveOrCopyFlavor {
MOVE, COPY, MOVE_AND_MARK_AS_READ
}
}

View file

@ -16,9 +16,9 @@ public class MessagingControllerCommands {
static final String COMMAND_DELETE = "delete";
static final String COMMAND_EXPUNGE = "expunge";
static final String COMMAND_MOVE_OR_COPY = "move_or_copy";
static final String COMMAND_MOVE_AND_MARK_AS_READ = "move_and_mark_as_read";
static final String COMMAND_EMPTY_TRASH = "empty_trash";
public abstract static class PendingCommand {
public long databaseId;
@ -66,6 +66,41 @@ public class MessagingControllerCommands {
}
}
public static class PendingMoveAndMarkAsRead extends PendingCommand {
public final String srcFolder;
public final String destFolder;
public final List<String> uids;
public final Map<String, String> newUidMap;
public static PendingMoveAndMarkAsRead create(String srcFolder, String destFolder,
Map<String, String> uidMap) {
return new PendingMoveAndMarkAsRead(srcFolder, destFolder, null, uidMap);
}
public static PendingMoveAndMarkAsRead create(String srcFolder, String destFolder, List<String> uids) {
return new PendingMoveAndMarkAsRead(srcFolder, destFolder, uids, null);
}
private PendingMoveAndMarkAsRead(String srcFolder, String destFolder, List<String> uids,
Map<String, String> newUidMap) {
this.srcFolder = srcFolder;
this.destFolder = destFolder;
this.uids = uids;
this.newUidMap = newUidMap;
}
@Override
public String getCommandName() {
return COMMAND_MOVE_AND_MARK_AS_READ;
}
@Override
public void execute(MessagingController controller, Account account) throws MessagingException {
controller.processPendingMoveAndRead(this, account);
}
}
public static class PendingEmptyTrash extends PendingCommand {
public static PendingEmptyTrash create() {
return new PendingEmptyTrash();

View file

@ -13,6 +13,7 @@ import com.fsck.k9.controller.MessagingControllerCommands.PendingDelete;
import com.fsck.k9.controller.MessagingControllerCommands.PendingEmptyTrash;
import com.fsck.k9.controller.MessagingControllerCommands.PendingExpunge;
import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead;
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveAndMarkAsRead;
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy;
import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag;
import com.squareup.moshi.JsonAdapter;
@ -31,6 +32,8 @@ public class PendingCommandSerializer {
HashMap<String, JsonAdapter<? extends PendingCommand>> adapters = new HashMap<>();
adapters.put(MessagingControllerCommands.COMMAND_MOVE_OR_COPY, moshi.adapter(PendingMoveOrCopy.class));
adapters.put(MessagingControllerCommands.COMMAND_MOVE_AND_MARK_AS_READ,
moshi.adapter(PendingMoveAndMarkAsRead.class));
adapters.put(MessagingControllerCommands.COMMAND_APPEND, moshi.adapter(PendingAppend.class));
adapters.put(MessagingControllerCommands.COMMAND_EMPTY_TRASH, moshi.adapter(PendingEmptyTrash.class));
adapters.put(MessagingControllerCommands.COMMAND_EXPUNGE, moshi.adapter(PendingExpunge.class));

View file

@ -117,6 +117,9 @@ public class AccountSettings {
s.put("markMessageAsReadOnView", Settings.versions(
new V(7, new BooleanSetting(true))
));
s.put("markMessageAsReadOnDelete", Settings.versions(
new V(63, new BooleanSetting(true))
));
s.put("maxPushFolders", Settings.versions(
new V(1, new IntegerRangeSetting(0, 100, 10))
));

View file

@ -36,7 +36,7 @@ public class Settings {
*
* @see SettingsExporter
*/
public static final int VERSION = 62;
public static final int VERSION = 63;
static Map<String, Object> validate(int version, Map<String, TreeMap<Integer, SettingsDescription>> settings,
Map<String, String> importedSettings, boolean useDefaultValues) {

View file

@ -18,6 +18,7 @@ class AccountSettingsDataStore(
return when (key) {
"account_default" -> account == preferences.defaultAccount
"mark_message_as_read_on_view" -> account.isMarkMessageAsReadOnView
"mark_message_as_read_on_delete" -> account.isMarkMessageAsReadOnDelete
"account_sync_remote_deletetions" -> account.isSyncRemoteDeletions
"push_poll_on_connect" -> account.isPushPollOnConnect
"always_show_cc_bcc" -> account.isAlwaysShowCcBcc
@ -51,6 +52,7 @@ class AccountSettingsDataStore(
return
}
"mark_message_as_read_on_view" -> account.isMarkMessageAsReadOnView = value
"mark_message_as_read_on_delete" -> account.isMarkMessageAsReadOnDelete = value
"account_sync_remote_deletetions" -> account.isSyncRemoteDeletions = value
"push_poll_on_connect" -> account.isPushPollOnConnect = value
"always_show_cc_bcc" -> account.isAlwaysShowCcBcc = value

View file

@ -513,6 +513,8 @@ Please submit bug reports, contribute new features and ask questions at
<string name="account_settings_notification_opens_unread_summary">Searches for unread messages when Notification is opened</string>
<string name="account_settings_mark_message_as_read_on_view_label">Mark as read when opened</string>
<string name="account_settings_mark_message_as_read_on_view_summary">Mark a message as read when it is opened for viewing</string>
<string name="account_settings_mark_message_as_read_on_delete_label">Mark as read when deleted</string>
<string name="account_settings_mark_message_as_read_on_delete_summary">Mark a message as read when it is deleted</string>
<string name="account_settings_notification_open_system_notifications_label">Notification settings</string>
<string name="account_settings_notification_open_system_notifications_summary">Open system notification settings</string>

View file

@ -110,6 +110,11 @@
android:summary="@string/account_settings_sync_remote_deletetions_summary"
android:title="@string/account_settings_sync_remote_deletetions_label" />
<CheckBoxPreference
android:key="mark_message_as_read_on_delete"
android:summary="@string/account_settings_mark_message_as_read_on_delete_summary"
android:title="@string/account_settings_mark_message_as_read_on_delete_label" />
<ListPreference
android:dialogTitle="@string/account_setup_incoming_delete_policy_label"
android:entries="@array/delete_policy_entries"

View file

@ -54,6 +54,13 @@ interface Backend {
messageServerIds: List<String>
): Map<String, String>?
@Throws(MessagingException::class)
fun moveMessagesAndMarkAsRead(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>?
@Throws(MessagingException::class)
fun copyMessages(
sourceFolderServerId: String,

View file

@ -1,6 +1,7 @@
package com.fsck.k9.backend.imap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -162,6 +163,18 @@ public class ImapBackend implements Backend {
return commandMoveOrCopyMessages.moveMessages(sourceFolderServerId, targetFolderServerId, messageServerIds);
}
@Nullable
@Override
public Map<String, String> moveMessagesAndMarkAsRead(@NotNull String sourceFolderServerId,
@NotNull String targetFolderServerId, @NotNull List<String> messageServerIds) throws MessagingException {
Map<String, String> uidMapping = commandMoveOrCopyMessages
.moveMessages(sourceFolderServerId, targetFolderServerId, messageServerIds);
if (uidMapping != null) {
setFlag(targetFolderServerId, new ArrayList<>(uidMapping.values()), Flag.SEEN, true);
}
return uidMapping;
}
@Nullable
@Override
public Map<String, String> copyMessages(@NotNull String sourceFolderServerId, @NotNull String targetFolderServerId,

View file

@ -88,6 +88,14 @@ class Pop3Backend(
throw UnsupportedOperationException("not supported")
}
override fun moveMessagesAndMarkAsRead(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>? {
throw UnsupportedOperationException("not supported")
}
override fun search(
folderServerId: String,
query: String?,

View file

@ -85,6 +85,19 @@ class WebDavBackend(
return commandMoveOrCopyMessages.moveMessages(sourceFolderServerId, targetFolderServerId, messageServerIds)
}
override fun moveMessagesAndMarkAsRead(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>? {
val uidMapping = commandMoveOrCopyMessages
.moveMessages(sourceFolderServerId, targetFolderServerId, messageServerIds)
if (uidMapping != null) {
setFlag(targetFolderServerId, uidMapping.values.toList(), Flag.SEEN, true)
}
return uidMapping
}
override fun copyMessages(
sourceFolderServerId: String,
targetFolderServerId: String,