Merge pull request #4835 from k9mail/local_folder_server_id

Continue work on referring to folders only by their database ID
This commit is contained in:
cketti 2020-06-17 01:12:43 +02:00 committed by GitHub
commit 8c759e3c5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 282 additions and 270 deletions

View file

@ -27,11 +27,6 @@ import org.jetbrains.annotations.Nullable;
* Account stores all of the settings for a single account defined by the user. Each account is defined by a UUID.
*/
public class Account implements BaseAccount {
/**
* This local folder is used to store messages to be sent.
*/
public static final String OUTBOX = "K9MAIL_INTERNAL_OUTBOX";
/**
* Fixed name of outbox - not actually displayed.
*/

View file

@ -999,8 +999,9 @@ public class MessagingController {
}
void processPendingMarkAllAsRead(PendingMarkAllAsRead command, Account account) throws MessagingException {
long folderId = command.folderId;
LocalStore localStore = localStoreProvider.getInstance(account);
LocalFolder localFolder = localStore.getFolder(command.folderId);
LocalFolder localFolder = localStore.getFolder(folderId);
localFolder.open();
String folderServerId = localFolder.getServerId();
@ -1016,7 +1017,7 @@ public class MessagingController {
}
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, folderServerId);
l.folderStatusChanged(account, folderId);
}
Backend backend = getBackend(account);
@ -1082,34 +1083,30 @@ public class MessagingController {
Timber.e(e, "Couldn't set flags in local database");
}
// Read folder name and UID of messages from the database
Map<String, List<String>> folderMap;
// Read folder ID and UID of messages from the database
Map<Long, List<String>> folderMap;
try {
folderMap = localStore.getFoldersAndUids(ids, threadedList);
folderMap = localStore.getFolderIdsAndUids(ids, threadedList);
} catch (MessagingException e) {
Timber.e(e, "Couldn't get folder name and UID of messages");
return;
}
// Loop over all folders
for (Entry<String, List<String>> entry : folderMap.entrySet()) {
String folderServerId = entry.getKey();
for (Entry<Long, List<String>> entry : folderMap.entrySet()) {
long folderId = entry.getKey();
List<String> uids = entry.getValue();
// Notify listeners of changed folder status
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, folderServerId);
l.folderStatusChanged(account, folderId);
}
// TODO: Skip the remote part for all local-only folders
// Send flag change to server
try {
long folderId = getFolderId(account, folderServerId);
queueSetFlag(account, folderId, newState, flag, entry.getValue());
processPendingCommands(account);
} catch (MessagingException e) {
Timber.e(e, "Error while trying to set flags");
}
queueSetFlag(account, folderId, newState, flag, uids);
processPendingCommands(account);
}
}
@ -1130,9 +1127,8 @@ public class MessagingController {
// Update the messages in the local store
localFolder.setFlags(messages, Collections.singleton(flag), newState);
String folderServerId = localFolder.getServerId();
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, folderServerId);
l.folderStatusChanged(account, folderId);
}
@ -1253,8 +1249,8 @@ public class MessagingController {
LocalMessage message = localFolder.getMessage(uid);
if (message == null || message.getDatabaseId() == 0) {
String folderServerId = localFolder.getServerId();
throw new IllegalArgumentException("Message not found: folder=" + folderServerId + ", uid=" + uid);
String folderName = localFolder.getName();
throw new IllegalArgumentException("Message not found: folder=" + folderName + ", uid=" + uid);
}
FetchProfile fp = new FetchProfile();
@ -1274,8 +1270,8 @@ public class MessagingController {
LocalMessage message = localFolder.getMessage(uid);
if (message == null || message.getDatabaseId() == 0) {
String folderServerId = localFolder.getServerId();
throw new IllegalArgumentException("Message not found: folder=" + folderServerId + ", uid=" + uid);
String folderName = localFolder.getName();
throw new IllegalArgumentException("Message not found: folder=" + folderName + ", uid=" + uid);
}
FetchProfile fp = new FetchProfile();
@ -1756,11 +1752,9 @@ public class MessagingController {
LocalFolder localSrcFolder = localStore.getFolder(srcFolderId);
localSrcFolder.open();
String srcFolderServerId = localSrcFolder.getServerId();
LocalFolder localDestFolder = localStore.getFolder(destFolderId);
localDestFolder.open();
String destFolderServerId = localDestFolder.getServerId();
boolean unreadCountAffected = false;
List<String> uids = new LinkedList<>();
@ -1799,7 +1793,7 @@ public class MessagingController {
// If this copy operation changes the unread count in the destination
// folder, notify the listeners.
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, destFolderServerId);
l.folderStatusChanged(account, destFolderId);
}
}
} else {
@ -1823,8 +1817,8 @@ public class MessagingController {
int unreadMessageCountSrc = localSrcFolder.getUnreadMessageCount();
int unreadMessageCountDest = localDestFolder.getUnreadMessageCount();
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, srcFolderServerId);
l.folderStatusChanged(account, destFolderServerId);
l.folderStatusChanged(account, srcFolderId);
l.folderStatusChanged(account, destFolderId);
}
}
}
@ -1863,7 +1857,7 @@ public class MessagingController {
String uid = localFolder.getMessageUidById(id);
if (uid != null) {
MessageReference messageReference = new MessageReference(account.getUuid(), folderId, uid, null);
deleteMessage(messageReference, null);
deleteMessage(messageReference);
}
} catch (MessagingException me) {
Timber.e(me, "Error deleting draft");
@ -1871,26 +1865,18 @@ public class MessagingController {
}
public void deleteThreads(final List<MessageReference> messages) {
actOnMessagesGroupedByAccountAndFolder(messages, new MessageActor() {
@Override
public void act(final Account account, final LocalFolder messageFolder,
final List<LocalMessage> accountMessages) {
suppressMessages(account, accountMessages);
putBackground("deleteThreads", null, new Runnable() {
@Override
public void run() {
deleteThreadsSynchronous(account, messageFolder.getServerId(), accountMessages);
}
});
}
actOnMessagesGroupedByAccountAndFolder(messages, (account, messageFolder, accountMessages) -> {
suppressMessages(account, accountMessages);
putBackground("deleteThreads", null, () ->
deleteThreadsSynchronous(account, messageFolder.getDatabaseId(), accountMessages)
);
});
}
private void deleteThreadsSynchronous(Account account, String folderServerId, List<LocalMessage> messages) {
private void deleteThreadsSynchronous(Account account, long folderId, List<LocalMessage> messages) {
try {
List<LocalMessage> messagesToDelete = collectMessagesInThreads(account, messages);
deleteMessagesSynchronous(account, folderServerId, messagesToDelete, null);
deleteMessagesSynchronous(account, folderId, messagesToDelete);
} catch (MessagingException e) {
Timber.e(e, "Something went wrong while deleting threads");
}
@ -1914,26 +1900,16 @@ public class MessagingController {
return messagesInThreads;
}
public void deleteMessage(MessageReference message, final MessagingListener listener) {
deleteMessages(Collections.singletonList(message), listener);
public void deleteMessage(MessageReference message) {
deleteMessages(Collections.singletonList(message));
}
public void deleteMessages(List<MessageReference> messages, final MessagingListener listener) {
actOnMessagesGroupedByAccountAndFolder(messages, new MessageActor() {
@Override
public void act(final Account account, final LocalFolder messageFolder,
final List<LocalMessage> accountMessages) {
suppressMessages(account, accountMessages);
putBackground("deleteMessages", null, new Runnable() {
@Override
public void run() {
deleteMessagesSynchronous(account, messageFolder.getServerId(), accountMessages, listener);
}
});
}
public void deleteMessages(List<MessageReference> messages) {
actOnMessagesGroupedByAccountAndFolder(messages, (account, messageFolder, accountMessages) -> {
suppressMessages(account, accountMessages);
putBackground("deleteMessages", null, () ->
deleteMessagesSynchronous(account, messageFolder.getDatabaseId(), accountMessages)
);
});
}
@ -1966,9 +1942,7 @@ public class MessagingController {
}
private void deleteMessagesSynchronous(final Account account, final String folder,
final List<LocalMessage> messages,
MessagingListener listener) {
private void deleteMessagesSynchronous(Account account, long folderId, List<LocalMessage> messages) {
try {
List<LocalMessage> localOnlyMessages = new ArrayList<>();
List<LocalMessage> syncedMessages = new ArrayList<>();
@ -1986,9 +1960,8 @@ public class MessagingController {
Backend backend = getBackend(account);
LocalStore localStore = localStoreProvider.getInstance(account);
LocalFolder localFolder = localStore.getFolder(folder);
LocalFolder localFolder = localStore.getFolder(folderId);
localFolder.open();
long folderId = localFolder.getDatabaseId();
Map<String, String> uidMap = null;
Long trashFolderId = account.getTrashFolderId();
@ -2014,9 +1987,9 @@ public class MessagingController {
}
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, folder);
l.folderStatusChanged(account, folderId);
if (localTrashFolder != null) {
l.folderStatusChanged(account, localTrashFolder.getServerId());
l.folderStatusChanged(account, trashFolderId);
}
}
@ -2107,7 +2080,6 @@ public class MessagingController {
LocalStore localStore = localStoreProvider.getInstance(account);
LocalFolder localFolder = localStore.getFolder(trashFolderId);
localFolder.open();
String trashFolderServerId = localFolder.getServerId();
boolean isTrashLocalOnly = isTrashLocalOnly(account);
if (isTrashLocalOnly) {
@ -2118,7 +2090,7 @@ public class MessagingController {
}
for (MessagingListener l : getListeners()) {
l.folderStatusChanged(account, trashFolderServerId);
l.folderStatusChanged(account, trashFolderId);
}
if (!isTrashLocalOnly) {
@ -2832,8 +2804,9 @@ public class MessagingController {
@Override
public void folderStatusChanged(@NotNull String folderServerId) {
long folderId = getFolderIdOrThrow(account, folderServerId);
for (MessagingListener messagingListener : getListeners(listener)) {
messagingListener.folderStatusChanged(account, folderServerId);
messagingListener.folderStatusChanged(account, folderId);
}
}

View file

@ -35,7 +35,7 @@ public interface MessagingListener {
void checkMailStarted(Context context, Account account);
void checkMailFinished(Context context, Account account);
void folderStatusChanged(Account account, String folderServerId);
void folderStatusChanged(Account account, long folderId);
void messageUidChanged(Account account, long folderId, String oldUid, String newUid);

View file

@ -79,7 +79,7 @@ public abstract class SimpleMessagingListener implements MessagingListener {
}
@Override
public void folderStatusChanged(Account account, String folderServerId) {
public void folderStatusChanged(Account account, long folderId) {
}
@Override

View file

@ -20,17 +20,11 @@ class FolderRepository(
.thenByDescending { it.isInTopGroup }
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.folder.name }
fun getFolders(): List<Folder> {
fun getRemoteFolders(): List<RemoteFolder> {
val folders = localStoreProvider.getInstance(account).getPersonalNamespaces(false)
return folders.map { Folder(it.databaseId, it.serverId, it.name, it.type.toFolderType(), it.isLocalOnly) }
}
fun getRemoteFolders(): List<Folder> {
val folders = localStoreProvider.getInstance(account).getPersonalNamespaces(false)
return folders
.filterNot { it.isLocalOnly }
.map { Folder(it.databaseId, it.serverId, it.name, it.type.toFolderType(), isLocalOnly = false) }
.map { RemoteFolder(it.databaseId, it.serverId, it.name, it.type.toFolderType()) }
}
fun getDisplayFolders(displayMode: FolderMode?): List<DisplayFolder> {
@ -43,12 +37,122 @@ class FolderRepository(
return displayFolders.sortedWith(sortForDisplay)
}
fun getFolderDetails(folderId: Long): FolderDetails? {
return getFolderDetails(selection = "id = ?", selectionArgs = arrayOf(folderId.toString())).firstOrNull()
fun getFolder(folderId: Long): Folder? {
val database = localStoreProvider.getInstance(account).database
return database.execute(false) { db ->
db.query(
"folders",
arrayOf(
"id",
"name",
"local_only"
),
"id = ?",
arrayOf(folderId.toString()),
null,
null,
null
).use { cursor ->
if (cursor.moveToFirst()) {
val id = cursor.getLong(0)
Folder(
id = id,
name = cursor.getString(1),
type = folderTypeOf(id),
isLocalOnly = cursor.getInt(2) == 1
)
} else {
null
}
}
}
}
fun getFolderDetails(): List<FolderDetails> {
return getFolderDetails(selection = null, selectionArgs = null)
fun getFolderDetails(folderId: Long): FolderDetails? {
val database = localStoreProvider.getInstance(account).database
return database.execute(false) { db ->
db.query(
"folders",
arrayOf(
"id",
"name",
"top_group",
"integrate",
"poll_class",
"display_class",
"notify_class",
"push_class",
"local_only"
),
"id = ?",
arrayOf(folderId.toString()),
null,
null,
null
).use { cursor ->
cursor.map {
val id = cursor.getLong(0)
FolderDetails(
folder = Folder(
id = id,
name = cursor.getString(1),
type = folderTypeOf(id),
isLocalOnly = cursor.getInt(8) == 1
),
isInTopGroup = cursor.getInt(2) == 1,
isIntegrate = cursor.getInt(3) == 1,
syncClass = cursor.getStringOrNull(4).toFolderClass(),
displayClass = cursor.getStringOrNull(5).toFolderClass(),
notifyClass = cursor.getStringOrNull(6).toFolderClass(),
pushClass = cursor.getStringOrNull(7).toFolderClass()
)
}
}
}.firstOrNull()
}
fun getRemoteFolderDetails(): List<RemoteFolderDetails> {
val database = localStoreProvider.getInstance(account).database
return database.execute(false) { db ->
db.query(
"folders",
arrayOf(
"id",
"server_id",
"name",
"type",
"top_group",
"integrate",
"poll_class",
"display_class",
"notify_class",
"push_class"
),
"local_only = 0",
null,
null,
null,
null
).use { cursor ->
cursor.map {
val id = cursor.getLong(0)
RemoteFolderDetails(
folder = RemoteFolder(
id = id,
serverId = cursor.getString(1),
name = cursor.getString(2),
type = cursor.getString(3).toFolderType().toFolderType()
),
isInTopGroup = cursor.getInt(4) == 1,
isIntegrate = cursor.getInt(5) == 1,
syncClass = cursor.getStringOrNull(6).toFolderClass(),
displayClass = cursor.getStringOrNull(7).toFolderClass(),
notifyClass = cursor.getStringOrNull(8).toFolderClass(),
pushClass = cursor.getStringOrNull(9).toFolderClass()
)
}
}
}
}
fun getFolderServerId(folderId: Long): String? {
@ -102,51 +206,6 @@ class FolderRepository(
}
}
private fun getFolderDetails(selection: String?, selectionArgs: Array<String>?): List<FolderDetails> {
val database = localStoreProvider.getInstance(account).database
return database.execute(false) { db ->
db.query(
"folders",
arrayOf(
"id",
"server_id",
"name",
"top_group",
"integrate",
"poll_class",
"display_class",
"notify_class",
"push_class",
"local_only"
),
selection,
selectionArgs,
null,
null,
null
).use { cursor ->
cursor.map {
val id = cursor.getLong(0)
FolderDetails(
folder = Folder(
id = id,
serverId = cursor.getString(1),
name = cursor.getString(2),
type = folderTypeOf(id),
isLocalOnly = cursor.getInt(9) == 1
),
isInTopGroup = cursor.getInt(3) == 1,
isIntegrate = cursor.getInt(4) == 1,
syncClass = cursor.getStringOrNull(5).toFolderClass(),
displayClass = cursor.getStringOrNull(6).toFolderClass(),
notifyClass = cursor.getStringOrNull(7).toFolderClass(),
pushClass = cursor.getStringOrNull(8).toFolderClass()
)
}
}
}
}
fun updateFolderDetails(folderDetails: FolderDetails) {
val database = localStoreProvider.getInstance(account).database
database.execute(false) { db ->
@ -164,7 +223,7 @@ class FolderRepository(
private fun getDisplayFolders(db: SQLiteDatabase, displayMode: FolderMode): List<DisplayFolder> {
val queryBuilder = StringBuilder("""
SELECT f.id, f.server_id, f.name, f.top_group, f.local_only, (
SELECT f.id, f.name, f.top_group, f.local_only, (
SELECT COUNT(m.id)
FROM messages m
WHERE m.folder_id = f.id AND m.empty = 0 AND m.deleted = 0 AND m.read = 0
@ -181,14 +240,13 @@ class FolderRepository(
while (cursor.moveToNext()) {
val id = cursor.getLong(0)
val serverId = cursor.getString(1)
val name = cursor.getString(2)
val name = cursor.getString(1)
val type = folderTypeOf(id)
val isInTopGroup = cursor.getInt(3) == 1
val isLocalOnly = cursor.getInt(4) == 1
val unreadCount = cursor.getInt(5)
val isInTopGroup = cursor.getInt(2) == 1
val isLocalOnly = cursor.getInt(3) == 1
val unreadCount = cursor.getInt(4)
val folder = Folder(id, serverId, name, type, isLocalOnly)
val folder = Folder(id, name, type, isLocalOnly)
displayFolders.add(DisplayFolder(folder, isInTopGroup, unreadCount))
}
@ -271,7 +329,9 @@ class FolderRepository(
}
}
data class Folder(val id: Long, val serverId: String, val name: String, val type: FolderType, val isLocalOnly: Boolean)
data class Folder(val id: Long, val name: String, val type: FolderType, val isLocalOnly: Boolean)
data class RemoteFolder(val id: Long, val serverId: String, val name: String, val type: FolderType)
data class FolderDetails(
val folder: Folder,
@ -283,6 +343,16 @@ data class FolderDetails(
val pushClass: FolderClass
)
data class RemoteFolderDetails(
val folder: RemoteFolder,
val isInTopGroup: Boolean,
val isIntegrate: Boolean,
val syncClass: FolderClass,
val displayClass: FolderClass,
val notifyClass: FolderClass,
val pushClass: FolderClass
)
data class DisplayFolder(
val folder: Folder,
val isInTopGroup: Boolean,

View file

@ -87,7 +87,7 @@ public class LocalFolder {
private FolderType type = FolderType.REGULAR;
private String serverId = null;
private String name;
private long databaseId = -1;
private long databaseId = -1L;
private int visibleLimit = -1;
private String prefId = null;
@ -172,10 +172,7 @@ public class LocalFolder {
}
if (cursor.moveToFirst() && !cursor.isNull(LocalStore.FOLDER_ID_INDEX)) {
int folderId = cursor.getInt(LocalStore.FOLDER_ID_INDEX);
if (folderId > 0) {
open(cursor);
}
open(cursor);
} else {
throw new MessagingException("LocalFolder.open(): Folder not found: " +
serverId + " (" + databaseId + ")", true);
@ -194,7 +191,7 @@ public class LocalFolder {
}
void open(Cursor cursor) throws MessagingException {
databaseId = cursor.getInt(LocalStore.FOLDER_ID_INDEX);
databaseId = cursor.getLong(LocalStore.FOLDER_ID_INDEX);
serverId = cursor.getString(LocalStore.FOLDER_SERVER_ID_INDEX);
visibleLimit = cursor.getInt(LocalStore.FOLDER_VISIBLE_LIMIT_INDEX);
status = cursor.getString(LocalStore.FOLDER_STATUS_INDEX);
@ -221,7 +218,7 @@ public class LocalFolder {
}
public boolean isOpen() {
return (databaseId != -1 && serverId != null);
return (databaseId != -1L && name != null);
}
public String getServerId() {
@ -352,7 +349,7 @@ public class LocalFolder {
}
public int getUnreadMessageCount() throws MessagingException {
if (databaseId == -1) {
if (databaseId == -1L) {
open();
}
@ -857,7 +854,7 @@ public class LocalFolder {
destFolder.getDatabaseId(),
message.getUid(),
message.getDatabaseId(),
getServerId());
getName());
String newUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString();
message.setUid(newUid);
@ -1643,14 +1640,15 @@ public class LocalFolder {
@Override
public boolean equals(Object o) {
if (o instanceof LocalFolder) {
return ((LocalFolder)o).serverId.equals(serverId);
return ((LocalFolder)o).databaseId == databaseId;
}
return super.equals(o);
}
@Override
public int hashCode() {
return serverId.hashCode();
long value = databaseId;
return (int) (value ^ (value >>> 32));
}
void destroyMessage(LocalMessage localMessage) throws MessagingException {

View file

@ -400,8 +400,8 @@ public class LocalStore {
if (cursor.isNull(FOLDER_ID_INDEX)) {
continue;
}
String folderServerId = cursor.getString(FOLDER_SERVER_ID_INDEX);
LocalFolder folder = new LocalFolder(LocalStore.this, folderServerId);
long folderId = cursor.getLong(FOLDER_ID_INDEX);
LocalFolder folder = new LocalFolder(LocalStore.this, folderId);
folder.open(cursor);
folders.add(folder);
@ -793,17 +793,16 @@ public class LocalStore {
@Nullable
private LocalMessage loadLocalMessageByMessageId(long messageId) throws MessagingException {
Map<String, List<String>> foldersAndUids =
getFoldersAndUids(Collections.singletonList(messageId), false);
if (foldersAndUids.isEmpty()) {
Map<Long, List<String>> folderIdsAndUids = getFolderIdsAndUids(Collections.singletonList(messageId), false);
if (folderIdsAndUids.isEmpty()) {
return null;
}
Map.Entry<String,List<String>> entry = foldersAndUids.entrySet().iterator().next();
String folderServerId = entry.getKey();
Map.Entry<Long, List<String>> entry = folderIdsAndUids.entrySet().iterator().next();
long folderId = entry.getKey();
String uid = entry.getValue().get(0);
LocalFolder folder = getFolder(folderServerId);
LocalFolder folder = getFolder(folderId);
LocalMessage localMessage = folder.getMessage(uid);
FetchProfile fp = new FetchProfile();
@ -919,17 +918,15 @@ public class LocalStore {
boolean localOnly = folder.isLocalOnly();
String databaseFolderType = FolderTypeConverter.toDatabaseFolderType(folder.getType());
if (K9.DEVELOPER_MODE) {
Cursor cursor = db.query("folders", new String[] { "id", "server_id" },
"server_id = ?", new String[] { serverId },null, null, null);
if (K9.DEVELOPER_MODE && localOnly) {
Cursor cursor = db.query("folders", new String[] { "id" },
"name = ? AND local_only = 1", new String[] { name },null, null, null);
try {
if (cursor.moveToNext()) {
long folderId = cursor.getLong(0);
String folderServerId = cursor.getString(1);
throw new AssertionError("Tried to create folder '" + serverId + "'" +
" that already exists in the database as '" + folderServerId + "'" +
" (" + folderId + ")");
throw new AssertionError("Tried to create local folder '" + name + "'" +
" that already exists in the database with ID " + folderId);
}
} finally {
cursor.close();
@ -960,14 +957,10 @@ public class LocalStore {
}
public long createLocalFolder(String folderName, FolderType type) throws MessagingException {
return createLocalFolder(folderName, folderName, type);
}
public long createLocalFolder(String folderServerId, String folderName, FolderType type) throws MessagingException {
return database.execute(true, (DbCallback<Long>) db -> {
ContentValues values = new ContentValues();
values.put("name", folderName);
values.put("server_id", folderServerId);
values.putNull("server_id");
values.put("local_only", 1);
values.put("type", FolderTypeConverter.toDatabaseFolderType(type));
values.put("visible_limit", 0);
@ -1230,7 +1223,7 @@ public class LocalStore {
}
/**
* Get folder server ID and UID for the supplied messages.
* Get folder ID and UID for the supplied messages.
*
* @param messageIds
* A list of primary keys in the "messages" table.
@ -1240,13 +1233,13 @@ public class LocalStore {
* If this is {@code false} only the UIDs for messages in {@code messageIds} are
* returned.
*
* @return The list of UIDs for the messages grouped by folder server ID.
* @return The list of UIDs for the messages grouped by folder ID.
*
*/
public Map<String, List<String>> getFoldersAndUids(final List<Long> messageIds,
public Map<Long, List<String>> getFolderIdsAndUids(final List<Long> messageIds,
final boolean threadedList) throws MessagingException {
final Map<String, List<String>> folderMap = new HashMap<>();
final Map<Long, List<String>> folderMap = new HashMap<>();
doBatchSetSelection(new BatchSetSelection() {
@ -1265,10 +1258,9 @@ public class LocalStore {
throws UnavailableStorageException {
if (threadedList) {
String sql = "SELECT m.uid, f.server_id " +
String sql = "SELECT m.uid, m.folder_id " +
"FROM threads t " +
"LEFT JOIN messages m ON (t.message_id = m.id) " +
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
"WHERE m.empty = 0 AND m.deleted = 0 " +
"AND t.root" + selectionSet;
@ -1276,10 +1268,9 @@ public class LocalStore {
} else {
String sql =
"SELECT m.uid, f.server_id " +
"FROM messages m " +
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
"WHERE m.empty = 0 AND m.id" + selectionSet;
"SELECT uid, folder_id " +
"FROM messages " +
"WHERE empty = 0 AND id" + selectionSet;
getDataFromCursor(db.rawQuery(sql, selectionArgs));
}
@ -1289,12 +1280,12 @@ public class LocalStore {
try {
while (cursor.moveToNext()) {
String uid = cursor.getString(0);
String folderServerId = cursor.getString(1);
Long folderId = cursor.getLong(1);
List<String> uidList = folderMap.get(folderServerId);
List<String> uidList = folderMap.get(folderId);
if (uidList == null) {
uidList = new ArrayList<>();
folderMap.put(folderServerId, uidList);
folderMap.put(folderId, uidList);
}
uidList.add(uid);

View file

@ -4,7 +4,7 @@ package com.fsck.k9.mailstore
* Implements the automatic special folder selection strategy.
*/
class SpecialFolderSelectionStrategy {
fun selectSpecialFolder(folders: List<Folder>, type: FolderType): Folder? {
fun selectSpecialFolder(folders: List<RemoteFolder>, type: FolderType): RemoteFolder? {
return folders.firstOrNull { folder -> folder.type == type }
}
}

View file

@ -9,6 +9,7 @@ import com.fsck.k9.mail.FolderClass
* Updates special folders in [Account] if they are marked as [SpecialFolderSelection.AUTOMATIC] or if they are marked
* as [SpecialFolderSelection.MANUAL] but have been deleted from the server.
*/
// TODO: Find a better way to deal with local-only special folders
class SpecialFolderUpdater(
private val preferences: Preferences,
private val folderRepository: FolderRepository,
@ -16,20 +17,23 @@ class SpecialFolderUpdater(
private val account: Account
) {
fun updateSpecialFolders() {
val folders = folderRepository.getFolders()
val folders = folderRepository.getRemoteFolders()
updateInbox(folders)
updateSpecialFolder(FolderType.ARCHIVE, folders)
updateSpecialFolder(FolderType.DRAFTS, folders)
updateSpecialFolder(FolderType.SENT, folders)
updateSpecialFolder(FolderType.SPAM, folders)
updateSpecialFolder(FolderType.TRASH, folders)
if (!account.isPop3()) {
updateSpecialFolder(FolderType.ARCHIVE, folders)
updateSpecialFolder(FolderType.DRAFTS, folders)
updateSpecialFolder(FolderType.SENT, folders)
updateSpecialFolder(FolderType.SPAM, folders)
updateSpecialFolder(FolderType.TRASH, folders)
}
removeImportedSpecialFoldersData()
saveAccount()
}
private fun updateInbox(folders: List<Folder>) {
private fun updateInbox(folders: List<RemoteFolder>) {
val oldInboxId = account.inboxFolderId
val newInboxId = folders.firstOrNull { it.type == FolderType.INBOX }?.id
if (newInboxId == oldInboxId) return
@ -48,7 +52,7 @@ class SpecialFolderUpdater(
}
}
private fun updateSpecialFolder(type: FolderType, folders: List<Folder>) {
private fun updateSpecialFolder(type: FolderType, folders: List<RemoteFolder>) {
val importedServerId = getImportedSpecialFolderServerId(type)
if (importedServerId != null) {
val folderId = folders.firstOrNull { it.serverId == importedServerId }?.id
@ -127,4 +131,6 @@ class SpecialFolderUpdater(
private fun saveAccount() {
preferences.saveAccount(account)
}
private fun Account.isPop3() = storeUri.startsWith("pop3")
}

View file

@ -14,7 +14,7 @@ class SpecialLocalFoldersCreator(
val localStore = localStoreProvider.getInstance(account)
account.outboxFolderId = localStore.createLocalFolder(OUTBOX_SERVER_ID, OUTBOX_FOLDER_NAME, FolderType.OUTBOX)
account.outboxFolderId = localStore.createLocalFolder(OUTBOX_FOLDER_NAME, FolderType.OUTBOX)
if (account.isPop3()) {
check(account.draftsFolderId == null) { "Drafts folder was already set up" }
@ -37,7 +37,6 @@ class SpecialLocalFoldersCreator(
private fun Account.isPop3() = storeUri.startsWith("pop3")
companion object {
private const val OUTBOX_SERVER_ID = Account.OUTBOX
private const val OUTBOX_FOLDER_NAME = Account.OUTBOX_NAME
private const val DRAFTS_FOLDER_NAME = "Drafts"
private const val SENT_FOLDER_NAME = "Sent"

View file

@ -181,7 +181,7 @@ public class NotificationActionService extends Service {
List<String> messageReferenceStrings = intent.getStringArrayListExtra(EXTRA_MESSAGE_REFERENCES);
List<MessageReference> messageReferences = toMessageReferenceList(messageReferenceStrings);
controller.deleteMessages(messageReferences, null);
controller.deleteMessages(messageReferences);
}
private void archiveMessages(Intent intent, Account account, MessagingController controller) {

View file

@ -2,18 +2,18 @@ package com.fsck.k9.preferences
import com.fsck.k9.Account
import com.fsck.k9.mail.FolderClass
import com.fsck.k9.mailstore.FolderDetails
import com.fsck.k9.mailstore.FolderRepositoryManager
import com.fsck.k9.mailstore.RemoteFolderDetails
class FolderSettingsProvider(private val folderRepositoryManager: FolderRepositoryManager) {
fun getFolderSettings(account: Account): List<FolderSettings> {
val folderRepository = folderRepositoryManager.getFolderRepository(account)
return folderRepository.getFolderDetails()
return folderRepository.getRemoteFolderDetails()
.filterNot { it.containsOnlyDefaultValues() }
.map { it.toFolderSettings() }
}
private fun FolderDetails.containsOnlyDefaultValues(): Boolean {
private fun RemoteFolderDetails.containsOnlyDefaultValues(): Boolean {
return isInTopGroup == getDefaultValue("inTopGroup") &&
isIntegrate == getDefaultValue("integrate") &&
syncClass == getDefaultValue("syncMode") &&
@ -29,7 +29,7 @@ class FolderSettingsProvider(private val folderRepositoryManager: FolderReposito
return setting.defaultValue
}
private fun FolderDetails.toFolderSettings(): FolderSettings {
private fun RemoteFolderDetails.toFolderSettings(): FolderSettings {
return FolderSettings(
folder.serverId,
isInTopGroup,

View file

@ -185,11 +185,10 @@ public class RawMessageProvider extends ContentProvider {
LocalStore localStore = DI.get(LocalStoreProvider.class).getInstance(account);
LocalFolder localFolder = localStore.getFolder(folderId);
localFolder.open();
String folderServerId = localFolder.getServerId();
LocalMessage message = localFolder.getMessage(uid);
if (message == null || message.getDatabaseId() == 0) {
Timber.w("Message not found: folder=%s, uid=%s", folderServerId, uid);
Timber.w("Message not found: folder=%s, uid=%s", folderId, uid);
return null;
}

View file

@ -52,7 +52,7 @@ class JmapAccountCreator(
private fun createOutboxFolder(account: Account) {
val localStore = localStoreProvider.getInstance(account)
account.outboxFolderId = localStore.createLocalFolder(Account.OUTBOX, Account.OUTBOX_NAME, FolderType.OUTBOX)
account.outboxFolderId = localStore.createLocalFolder(Account.OUTBOX_NAME, FolderType.OUTBOX)
}
private fun fetchFolderList(account: Account) {

View file

@ -101,7 +101,7 @@ public class MessageProvider extends ContentProvider {
MessagingController messagingController = DI.get(MessagingController.class);
messagingController.addListener(new SimpleMessagingListener() {
@Override
public void folderStatusChanged(Account account, String folderServerId) {
public void folderStatusChanged(Account account, long folderId) {
context.getContentResolver().notifyChange(CONTENT_URI, null);
}
});
@ -166,7 +166,7 @@ public class MessageProvider extends ContentProvider {
if (myAccount != null) {
MessageReference messageReference = new MessageReference(myAccount.getUuid(), folderId, msgUid, null);
MessagingController controller = MessagingController.getInstance(getContext());
controller.deleteMessage(messageReference, null);
controller.deleteMessage(messageReference);
}
// FIXME return the actual number of deleted messages

View file

@ -29,7 +29,7 @@ class MessageListWidgetUpdateListener(private val context: Context) : SimpleMess
updateMailListWidget()
}
override fun folderStatusChanged(account: Account, folderServerId: String) {
override fun folderStatusChanged(account: Account, folderId: Long) {
updateMailListWidget()
}
}

View file

@ -78,10 +78,10 @@ class UnreadWidgetDataProvider(
private fun getFolderDisplayName(account: Account, folderId: Long): String {
val folderRepository = folderRepositoryManager.getFolderRepository(account)
val folderDetails = folderRepository.getFolderDetails(folderId)
return if (folderDetails != null) {
val folder = folderRepository.getFolder(folderId)
return if (folder != null) {
val folderNameFormatter = folderNameFormatterFactory.create(context)
folderNameFormatter.displayName(folderDetails.folder)
folderNameFormatter.displayName(folder)
} else {
Timber.e("Error loading folder for account %s, folder ID: %d", account, folderId)
""

View file

@ -23,7 +23,7 @@ class UnreadWidgetUpdateListener(private val unreadWidgetUpdater: UnreadWidgetUp
updateUnreadWidget()
}
override fun folderStatusChanged(account: Account, folderServerId: String) {
override fun folderStatusChanged(account: Account, folderId: Long) {
updateUnreadWidget()
}
}

View file

@ -5,9 +5,7 @@ import com.fsck.k9.Account
import com.fsck.k9.AppRobolectricTest
import com.fsck.k9.Preferences
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.mail.FolderClass
import com.fsck.k9.mailstore.Folder
import com.fsck.k9.mailstore.FolderDetails
import com.fsck.k9.mailstore.FolderRepository
import com.fsck.k9.mailstore.FolderRepositoryManager
import com.fsck.k9.mailstore.FolderType
@ -109,15 +107,7 @@ class UnreadWidgetDataProviderTest : AppRobolectricTest() {
fun createFolderRepository(): FolderRepository {
return mock {
on { getFolderDetails(FOLDER_ID) } doReturn FolderDetails(
folder = FOLDER,
isInTopGroup = true,
isIntegrate = true,
syncClass = FolderClass.NO_CLASS,
displayClass = FolderClass.FIRST_CLASS,
notifyClass = FolderClass.NO_CLASS,
pushClass = FolderClass.NO_CLASS
)
on { getFolder(FOLDER_ID) } doReturn FOLDER
}
}
@ -142,7 +132,6 @@ class UnreadWidgetDataProviderTest : AppRobolectricTest() {
const val LOCALIZED_FOLDER_NAME = "Posteingang"
val FOLDER = Folder(
id = FOLDER_ID,
serverId = "irrelevant",
name = "INBOX",
type = FolderType.INBOX,
isLocalOnly = false

View file

@ -12,7 +12,7 @@ import timber.log.Timber;
class StoreSchemaDefinition implements SchemaDefinition {
static final int DB_VERSION = 77;
static final int DB_VERSION = 78;
private final MigrationsHelper migrationsHelper;

View file

@ -1,17 +0,0 @@
package com.fsck.k9.storage.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
/**
* Make sure local Outbox folder has correct 'server_id' value
*/
internal class MigrationTo77(private val db: SQLiteDatabase) {
fun cleanUpOutboxServerId() {
val values = ContentValues().apply {
put("server_id", "K9MAIL_INTERNAL_OUTBOX")
}
db.update("folders", values, "name = 'Outbox' AND local_only = 1", null)
}
}

View file

@ -0,0 +1,12 @@
package com.fsck.k9.storage.migrations
import android.database.sqlite.SQLiteDatabase
/**
* Set 'server_id' value to NULL for local folders
*/
internal class MigrationTo78(private val db: SQLiteDatabase) {
fun removeServerIdFromLocalFolders() {
db.execSQL("UPDATE folders SET server_id = NULL WHERE local_only = 1")
}
}

View file

@ -22,6 +22,7 @@ object Migrations {
if (oldVersion < 74) MigrationTo74(db, migrationsHelper.account).removeDeletedMessages()
if (oldVersion < 75) MigrationTo75(db, migrationsHelper).updateAccountWithSpecialFolderIds()
if (oldVersion < 76) MigrationTo76(db, migrationsHelper).cleanUpSpecialLocalFolders()
if (oldVersion < 77) MigrationTo77(db).cleanUpOutboxServerId()
// 77: No longer necessary
if (oldVersion < 78) MigrationTo78(db).removeServerIdFromLocalFolders()
}
}

View file

@ -20,7 +20,6 @@ class FolderInfoHolder(
val folderId = localFolder.databaseId
val folder = Folder(
id = folderId,
serverId = localFolder.serverId,
name = localFolder.name,
type = getFolderType(account, folderId),
isLocalOnly = localFolder.isLocalOnly

View file

@ -727,7 +727,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
if (showingThreadedList) {
messagingController.deleteThreads(messages);
} else {
messagingController.deleteMessages(messages, null);
messagingController.deleteMessages(messages);
}
}

View file

@ -3,6 +3,7 @@ package com.fsck.k9.ui.folders
import android.content.res.Resources
import com.fsck.k9.mailstore.Folder
import com.fsck.k9.mailstore.FolderType
import com.fsck.k9.mailstore.RemoteFolder
import com.fsck.k9.ui.R
class FolderNameFormatter(private val resources: Resources) {
@ -26,4 +27,9 @@ class FolderNameFormatter(private val resources: Resources) {
FolderType.INBOX -> resources.getString(R.string.special_mailbox_name_inbox)
else -> folder.name
}
fun displayName(folder: RemoteFolder) = when (folder.type) {
FolderType.INBOX -> resources.getString(R.string.special_mailbox_name_inbox)
else -> folder.name
}
}

View file

@ -25,7 +25,7 @@ class FoldersLiveData(
private val messagingListener = object : SimpleMessagingListener() {
override fun folderStatusChanged(
account: Account,
folderServerId: String
folderId: Long
) {
if (account?.uuid == accountUuid) {
loadFoldersAsync()

View file

@ -6,7 +6,6 @@ import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.activity.FolderInfoHolder
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.helper.SingleLiveEvent
import com.fsck.k9.mailstore.Folder
@ -57,7 +56,7 @@ class FolderSettingsViewModel(
this@FolderSettingsViewModel.folderId = folderId
val folderSettingsData = FolderSettingsData(
folder = createFolderObject(account, folderDetails.folder),
folder = folderDetails.folder,
dataStore = FolderSettingsDataStore(folderRepository, folderDetails)
)
emit(folderSettingsData)
@ -76,17 +75,6 @@ class FolderSettingsViewModel(
}
}
private fun createFolderObject(account: Account, folder: Folder): Folder {
val folderType = FolderInfoHolder.getFolderType(account, folder.id)
return Folder(
id = folder.id,
serverId = folder.serverId,
name = folder.name,
type = folderType,
isLocalOnly = folder.isLocalOnly
)
}
fun showClearFolderConfirmationDialog() {
sendActionEvent(Action.ShowClearFolderConfirmationDialog)
}

View file

@ -317,7 +317,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
mFragmentListener.disableDeleteAction();
LocalMessage messageToDelete = mMessage;
mFragmentListener.showNextMessageOrReturn();
mController.deleteMessage(mMessageReference, null);
mController.deleteMessage(mMessageReference);
}
}

View file

@ -20,8 +20,8 @@ import com.fsck.k9.controller.MessagingController
import com.fsck.k9.crypto.OpenPgpApiHelper
import com.fsck.k9.fragment.ConfirmationDialogFragment
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener
import com.fsck.k9.mailstore.Folder
import com.fsck.k9.mailstore.FolderType
import com.fsck.k9.mailstore.RemoteFolder
import com.fsck.k9.ui.R
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
import com.fsck.k9.ui.observe
@ -291,7 +291,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr
}
}
private fun setFolders(preferenceKey: String, folders: List<Folder>) {
private fun setFolders(preferenceKey: String, folders: List<RemoteFolder>) {
val folderListPreference = findPreference(preferenceKey) as? FolderListPreference ?: return
folderListPreference.setFolders(folders)
}

View file

@ -5,9 +5,9 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.mailstore.Folder
import com.fsck.k9.mailstore.FolderRepositoryManager
import com.fsck.k9.mailstore.FolderType
import com.fsck.k9.mailstore.RemoteFolder
import com.fsck.k9.mailstore.SpecialFolderSelectionStrategy
import com.fsck.k9.ui.account.AccountsLiveData
import kotlinx.coroutines.Dispatchers
@ -68,7 +68,7 @@ class AccountSettingsViewModel(
}
}
private fun getAutomaticSpecialFolders(folders: List<Folder>): Map<FolderType, Folder?> {
private fun getAutomaticSpecialFolders(folders: List<RemoteFolder>): Map<FolderType, RemoteFolder?> {
return mapOf(
FolderType.ARCHIVE to specialFolderSelectionStrategy.selectSpecialFolder(folders, FolderType.ARCHIVE),
FolderType.DRAFTS to specialFolderSelectionStrategy.selectSpecialFolder(folders, FolderType.DRAFTS),
@ -79,4 +79,7 @@ class AccountSettingsViewModel(
}
}
data class RemoteFolderInfo(val folders: List<Folder>, val automaticSpecialFolders: Map<FolderType, Folder?>)
data class RemoteFolderInfo(
val folders: List<RemoteFolder>,
val automaticSpecialFolders: Map<FolderType, RemoteFolder?>
)

View file

@ -8,7 +8,7 @@ import android.text.style.StyleSpan
import android.util.AttributeSet
import androidx.core.content.res.TypedArrayUtils
import androidx.preference.ListPreference
import com.fsck.k9.mailstore.Folder
import com.fsck.k9.mailstore.RemoteFolder
import com.fsck.k9.ui.R
import com.fsck.k9.ui.folders.FolderNameFormatter
import org.koin.core.KoinComponent
@ -38,13 +38,13 @@ constructor(
isEnabled = false
}
fun setFolders(folders: List<Folder>) {
fun setFolders(folders: List<RemoteFolder>) {
entries = (listOf(noFolderSelectedName) + getFolderDisplayNames(folders)).toTypedArray()
entryValues = (listOf(NO_FOLDER_SELECTED_VALUE) + getFolderValues(folders)).toTypedArray()
isEnabled = true
}
fun setFolders(folders: List<Folder>, automaticFolder: Folder?) {
fun setFolders(folders: List<RemoteFolder>, automaticFolder: RemoteFolder?) {
val automaticFolderName = if (automaticFolder != null) {
folderNameFormatter.displayName(automaticFolder)
} else {
@ -73,9 +73,9 @@ constructor(
}
}
private fun getFolderDisplayNames(folders: List<Folder>) = folders.map { folderNameFormatter.displayName(it) }
private fun getFolderDisplayNames(folders: List<RemoteFolder>) = folders.map { folderNameFormatter.displayName(it) }
private fun getFolderValues(folders: List<Folder>) = folders.map { MANUAL_PREFIX + it.id.toString() }
private fun getFolderValues(folders: List<RemoteFolder>) = folders.map { MANUAL_PREFIX + it.id.toString() }
private fun String.italicize(): CharSequence {
return SpannableString(this).apply { setSpan(StyleSpan(Typeface.ITALIC), 0, this.length, 0) }