diff --git a/app/schemas/eu.faircode.email.DB/1.json b/app/schemas/eu.faircode.email.DB/1.json index eb54211ba3..9d1e88fce5 100644 --- a/app/schemas/eu.faircode.email.DB/1.json +++ b/app/schemas/eu.faircode.email.DB/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "ff81778e939ac6480ab8ec93435db96a", + "identityHash": "959322c3e61ed9307a830e355bb2b8ab", "entities": [ { "tableName": "identity", @@ -674,6 +674,14 @@ "autoGenerate": true }, "indices": [ + { + "name": "index_operation_message", + "unique": false, + "columnNames": [ + "message" + ], + "createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)" + }, { "name": "index_operation_message", "unique": false, @@ -711,7 +719,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, \"ff81778e939ac6480ab8ec93435db96a\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"959322c3e61ed9307a830e355bb2b8ab\")" ] } } \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index 9d59391834..a3090aff9e 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -62,16 +62,17 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack private ListView drawerList; private ActionBarDrawerToggle drawerToggle; - static final int LOADER_ACCOUNT_PUT = 1; - static final int LOADER_IDENTITY_PUT = 2; - static final int LOADER_FOLDER_PUT = 3; - static final int LOADER_MESSAGE_SEEN = 4; - static final int LOADER_MESSAGE_EDIT = 5; - static final int LOADER_MESSAGE_SPAM = 6; - static final int LOADER_MESSAGE_TRASH = 7; - static final int LOADER_MESSAGE_MOVE = 8; - static final int LOADER_MESSAGE_ARCHIVE = 9; - static final int LOADER_SEEN_UNTIL = 10; + static final int LOADER_ACCOUNT_CHECK = 1; + static final int LOADER_ACCOUNT_PUT = 2; + static final int LOADER_IDENTITY_PUT = 3; + static final int LOADER_FOLDER_PUT = 4; + static final int LOADER_MESSAGE_SEEN = 5; + static final int LOADER_MESSAGE_EDIT = 6; + static final int LOADER_MESSAGE_SPAM = 7; + static final int LOADER_MESSAGE_TRASH = 8; + static final int LOADER_MESSAGE_MOVE = 9; + static final int LOADER_MESSAGE_ARCHIVE = 10; + static final int LOADER_SEEN_UNTIL = 11; static final int REQUEST_VIEW = 1; static final int REQUEST_UNSEEN = 2; diff --git a/app/src/main/java/eu/faircode/email/ApplicationEx.java b/app/src/main/java/eu/faircode/email/ApplicationEx.java index d31e19f9ff..e2bf2d6fdb 100644 --- a/app/src/main/java/eu/faircode/email/ApplicationEx.java +++ b/app/src/main/java/eu/faircode/email/ApplicationEx.java @@ -44,7 +44,7 @@ public class ApplicationEx extends Application { DB db = null; try { db = DB.getBlockingInstance(ApplicationEx.this); - EntityFolder drafts = EntityFolder.getDrafts(ApplicationEx.this, db, -1); + EntityFolder drafts = db.folder().getPrimaryDrafts(); if (drafts != null) { Address to = new InternetAddress("marcel+email@faircode.eu", "FairCode"); diff --git a/app/src/main/java/eu/faircode/email/DaoFolder.java b/app/src/main/java/eu/faircode/email/DaoFolder.java index f8f56a34db..06f42387e1 100644 --- a/app/src/main/java/eu/faircode/email/DaoFolder.java +++ b/app/src/main/java/eu/faircode/email/DaoFolder.java @@ -71,8 +71,11 @@ public interface DaoFolder { " WHERE account = :account AND type = :type") EntityFolder getFolderByType(long account, String type); - @Query("SELECT * FROM folder WHERE account IS NULL AND type = '" + EntityFolder.DRAFTS + "'") - EntityFolder getLocalDrafts(); + // For debug/crash info + @Query("SELECT folder.* FROM folder" + + " JOIN account ON account.id = folder.account" + + " WHERE `primary` AND type = '" + EntityFolder.DRAFTS + "'") + EntityFolder getPrimaryDrafts(); @Query("SELECT * FROM folder WHERE type = '" + EntityFolder.OUTBOX + "'") EntityFolder getOutbox(); @@ -85,4 +88,10 @@ public interface DaoFolder { @Query("DELETE FROM folder WHERE account= :account AND name = :name") void deleteFolder(Long account, String name); + + @Query("DELETE FROM folder" + + " WHERE account= :account" + + " AND type <> '" + EntityFolder.INBOX + "'" + + " AND type <> '" + EntityFolder.USER + "'") + int deleteSystemFolders(Long account); } diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java index 41095fd58f..afb3324faa 100644 --- a/app/src/main/java/eu/faircode/email/DaoMessage.java +++ b/app/src/main/java/eu/faircode/email/DaoMessage.java @@ -80,7 +80,7 @@ public interface DaoMessage { @Query("SELECT * FROM message WHERE folder = :folder AND uid = :uid") EntityMessage getMessage(long folder, long uid); - @Query("SELECT * FROM message" + + @Query("SELECT message.* FROM message" + " JOIN folder on folder.id = message.folder" + " WHERE thread = :thread AND folder.type= '" + EntityFolder.ARCHIVE + "'") EntityMessage getArchivedMessage(String thread); diff --git a/app/src/main/java/eu/faircode/email/EntityFolder.java b/app/src/main/java/eu/faircode/email/EntityFolder.java index ed1a5af4f7..ccefb2dc7f 100644 --- a/app/src/main/java/eu/faircode/email/EntityFolder.java +++ b/app/src/main/java/eu/faircode/email/EntityFolder.java @@ -19,8 +19,7 @@ package eu.faircode.email; Copyright 2018 by Marcel Bokhorst (M66B) */ -import android.content.Context; - +import java.io.Serializable; import java.util.Arrays; import java.util.List; @@ -44,7 +43,7 @@ import static androidx.room.ForeignKey.CASCADE; @Index(value = {"type"}) } ) -public class EntityFolder { +public class EntityFolder implements Serializable { static final String TABLE_NAME = "folder"; static final String INBOX = "Inbox"; @@ -105,21 +104,6 @@ public class EntityFolder { @NonNull public Integer after; // days - static EntityFolder getDrafts(Context context, DB db, long account) { - EntityFolder drafts = db.folder().getFolderByType(account, EntityFolder.DRAFTS); - if (drafts == null) - drafts = db.folder().getLocalDrafts(); - if (drafts == null) { - drafts = new EntityFolder(); - drafts.name = context.getString(R.string.title_folder_local_drafts); - drafts.type = EntityFolder.DRAFTS; - drafts.synchronize = false; - drafts.after = 0; - drafts.id = db.folder().insertFolder(drafts); - } - return drafts; - } - @Override public boolean equals(Object obj) { if (obj instanceof EntityFolder) { @@ -132,4 +116,9 @@ public class EntityFolder { } else return false; } + + @Override + public String toString() { + return name; + } } diff --git a/app/src/main/java/eu/faircode/email/EntityOperation.java b/app/src/main/java/eu/faircode/email/EntityOperation.java index f4afa1e2d9..91785667a2 100644 --- a/app/src/main/java/eu/faircode/email/EntityOperation.java +++ b/app/src/main/java/eu/faircode/email/EntityOperation.java @@ -44,6 +44,7 @@ import static androidx.room.ForeignKey.CASCADE; @ForeignKey(childColumns = "message", entity = EntityMessage.class, parentColumns = "id", onDelete = CASCADE) }, indices = { + @Index(value = {"folder"}), @Index(value = {"message"}) } ) diff --git a/app/src/main/java/eu/faircode/email/FragmentAbout.java b/app/src/main/java/eu/faircode/email/FragmentAbout.java index cb9eb73a5c..71fd5f5d9e 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAbout.java +++ b/app/src/main/java/eu/faircode/email/FragmentAbout.java @@ -61,7 +61,7 @@ public class FragmentAbout extends FragmentEx { public void run() { try { DB db = DB.getInstance(getContext()); - EntityFolder drafts = EntityFolder.getDrafts(getContext(), db, -1); + EntityFolder drafts = db.folder().getPrimaryDrafts(); if (drafts != null) { StringBuilder info = Helper.getDebugInfo(); info.insert(0, getString(R.string.title_debug_info_remark) + "\n\n\n\n"); diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index f017eb12a4..5951aca143 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -42,9 +42,13 @@ import com.google.android.material.textfield.TextInputLayout; import com.sun.mail.imap.IMAPFolder; import com.sun.mail.imap.IMAPStore; +import java.text.Collator; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -55,6 +59,7 @@ import javax.mail.Session; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.constraintlayout.widget.Group; import androidx.lifecycle.Observer; import androidx.loader.app.LoaderManager; import androidx.loader.content.AsyncTaskLoader; @@ -71,9 +76,17 @@ public class FragmentAccount extends FragmentEx { private TextInputLayout tilPassword; private CheckBox cbSynchronize; private CheckBox cbPrimary; - private Button btnSave; + private Button btnCheck; private ProgressBar pbCheck; + private Spinner spDrafts; + private Spinner spSent; + private Spinner spAll; + private Spinner spTrash; + private Spinner spJunk; + private Button btnSave; + private ProgressBar pbSave; private ImageButton ibDelete; + private Group grpFolders; // TODO: loading spinner private ExecutorService executor = Executors.newCachedThreadPool(); @@ -102,9 +115,17 @@ public class FragmentAccount extends FragmentEx { tilPassword = view.findViewById(R.id.tilPassword); cbSynchronize = view.findViewById(R.id.cbSynchronize); cbPrimary = view.findViewById(R.id.cbPrimary); - btnSave = view.findViewById(R.id.btnSave); + btnCheck = view.findViewById(R.id.btnCheck); pbCheck = view.findViewById(R.id.pbCheck); + spDrafts = view.findViewById(R.id.spDrafts); + spSent = view.findViewById(R.id.spSent); + spAll = view.findViewById(R.id.spAll); + spTrash = view.findViewById(R.id.spTrash); + spJunk = view.findViewById(R.id.spJunk); + btnSave = view.findViewById(R.id.btnSave); + pbSave = view.findViewById(R.id.pbSave); ibDelete = view.findViewById(R.id.ibDelete); + grpFolders = view.findViewById(R.id.grpFolders); // Wire controls @@ -135,11 +156,49 @@ public class FragmentAccount extends FragmentEx { } }); + btnCheck.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + btnCheck.setEnabled(false); + pbCheck.setVisibility(View.VISIBLE); + + Bundle args = new Bundle(); + args.putLong("id", id); + args.putString("name", etName.getText().toString()); + args.putString("host", etHost.getText().toString()); + args.putString("port", etPort.getText().toString()); + args.putString("user", etUser.getText().toString()); + args.putString("password", tilPassword.getEditText().getText().toString()); + args.putBoolean("synchronize", cbSynchronize.isChecked()); + args.putBoolean("primary", cbPrimary.isChecked()); + + LoaderManager.getInstance(FragmentAccount.this) + .restartLoader(ActivityView.LOADER_ACCOUNT_CHECK, args, checkLoaderCallbacks).forceLoad(); + } + }); + btnSave.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { btnSave.setEnabled(false); - pbCheck.setVisibility(View.VISIBLE); + pbSave.setVisibility(View.VISIBLE); + + EntityFolder drafts = (EntityFolder) spDrafts.getSelectedItem(); + EntityFolder sent = (EntityFolder) spSent.getSelectedItem(); + EntityFolder all = (EntityFolder) spAll.getSelectedItem(); + EntityFolder trash = (EntityFolder) spTrash.getSelectedItem(); + EntityFolder junk = (EntityFolder) spJunk.getSelectedItem(); + + if (drafts.type == null) + drafts = null; + if (sent.type == null) + sent = null; + if (all.type == null) + all = null; + if (trash.type == null) + trash = null; + if (junk.type == null) + junk = null; Bundle args = new Bundle(); args.putLong("id", id); @@ -150,6 +209,11 @@ public class FragmentAccount extends FragmentEx { args.putString("password", tilPassword.getEditText().getText().toString()); args.putBoolean("synchronize", cbSynchronize.isChecked()); args.putBoolean("primary", cbPrimary.isChecked()); + args.putSerializable("drafts", drafts); + args.putSerializable("sent", sent); + args.putSerializable("all", all); + args.putSerializable("trash", trash); + args.putSerializable("junk", junk); LoaderManager.getInstance(FragmentAccount.this) .restartLoader(ActivityView.LOADER_ACCOUNT_PUT, args, putLoaderCallbacks).forceLoad(); @@ -189,6 +253,9 @@ public class FragmentAccount extends FragmentEx { // Initialize tilPassword.setPasswordVisibilityToggleEnabled(id < 0); pbCheck.setVisibility(View.GONE); + btnSave.setVisibility(View.GONE); + pbSave.setVisibility(View.GONE); + grpFolders.setVisibility(View.GONE); ibDelete.setVisibility(id < 0 ? View.GONE : View.VISIBLE); return view; @@ -202,8 +269,10 @@ public class FragmentAccount extends FragmentEx { Bundle args = getArguments(); long id = (args == null ? -1 : args.getLong("id", -1)); + final DB db = DB.getInstance(getContext()); + // Observe - DB.getInstance(getContext()).account().liveAccount(id).observe(getViewLifecycleOwner(), new Observer() { + db.account().liveAccount(id).observe(getViewLifecycleOwner(), new Observer() { @Override public void onChanged(@Nullable EntityAccount account) { etName.setText(account == null ? null : account.name); @@ -218,6 +287,189 @@ public class FragmentAccount extends FragmentEx { }); } + private static class CheckData { + Throwable ex; + List folders; + } + + private static class CheckLoader extends AsyncTaskLoader { + private Bundle args; + + CheckLoader(Context context) { + super(context); + } + + void setArgs(Bundle args) { + this.args = args; + } + + @Override + public CheckData loadInBackground() { + CheckData result = new CheckData(); + try { + long id = args.getLong("id"); + String host = args.getString("host"); + String port = args.getString("port"); + String user = args.getString("user"); + String password = args.getString("password"); + + if (TextUtils.isEmpty(host)) + throw new Throwable(getContext().getString(R.string.title_no_host)); + if (TextUtils.isEmpty(port)) + throw new Throwable(getContext().getString(R.string.title_no_port)); + if (TextUtils.isEmpty(user)) + throw new Throwable(getContext().getString(R.string.title_no_user)); + if (TextUtils.isEmpty(password)) + throw new Throwable(getContext().getString(R.string.title_no_password)); + + // Check IMAP server / get folders + DB db = DB.getInstance(getContext()); + List folders = new ArrayList<>(); + Session isession = Session.getInstance(MessageHelper.getSessionProperties(), null); + IMAPStore istore = null; + try { + istore = (IMAPStore) isession.getStore("imaps"); + istore.connect(host, Integer.parseInt(port), user, password); + + if (!istore.hasCapability("IDLE")) + throw new MessagingException(getContext().getString(R.string.title_no_idle)); + + for (Folder ifolder : istore.getDefaultFolder().list("*")) { + String type = null; + + // First check folder attributes + boolean selectable = true; + String[] attrs = ((IMAPFolder) ifolder).getAttributes(); + for (String attr : attrs) { + if ("\\Noselect".equals(attr)) + selectable = false; + if (attr.startsWith("\\")) { + int index = EntityFolder.SYSTEM_FOLDER_ATTR.indexOf(attr.substring(1)); + if (index >= 0) { + type = EntityFolder.SYSTEM_FOLDER_TYPE.get(index); + break; + } + } + } + + if (selectable) { + // Next check folder full name + if (type == null) { + String fullname = ifolder.getFullName(); + for (String attr : EntityFolder.SYSTEM_FOLDER_ATTR) + if (attr.equals(fullname)) { + int index = EntityFolder.SYSTEM_FOLDER_ATTR.indexOf(attr); + type = EntityFolder.SYSTEM_FOLDER_TYPE.get(index); + break; + } + } + + // Create entry + EntityFolder folder = db.folder().getFolderByName(id, ifolder.getFullName()); + if (folder == null) { + folder = new EntityFolder(); + folder.name = ifolder.getFullName(); + folder.type = (type == null ? EntityFolder.USER : type); + folder.synchronize = (type != null && EntityFolder.SYSTEM_FOLDER_SYNC.contains(type)); + folder.after = (type == null ? EntityFolder.DEFAULT_USER_SYNC : EntityFolder.DEFAULT_SYSTEM_SYNC); + } + folders.add(folder); + + Log.i(Helper.TAG, folder.name + " id=" + folder.id + + " type=" + folder.type + " attr=" + TextUtils.join(",", attrs)); + } + } + + } finally { + if (istore != null) + istore.close(); + } + + result.folders = folders; + } catch (Throwable ex) { + Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); + result.ex = ex; + } + + return result; + } + } + + private LoaderManager.LoaderCallbacks checkLoaderCallbacks = new LoaderManager.LoaderCallbacks() { + @NonNull + @Override + public Loader onCreateLoader(int id, Bundle args) { + CheckLoader loader = new CheckLoader(getContext()); + loader.setArgs(args); + return loader; + } + + @Override + public void onLoadFinished(@NonNull Loader loader, CheckData data) { + LoaderManager.getInstance(FragmentAccount.this).destroyLoader(loader.getId()); + + btnCheck.setEnabled(true); + pbCheck.setVisibility(View.GONE); + + if (data.ex == null) { + final Collator collator = Collator.getInstance(Locale.getDefault()); + collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc + + Collections.sort(data.folders, new Comparator() { + @Override + public int compare(EntityFolder f1, EntityFolder f2) { + int s = ((Integer) EntityFolder.FOLDER_SORT_ORDER.indexOf(f1.type)) + .compareTo(EntityFolder.FOLDER_SORT_ORDER.indexOf(f2.type)); + if (s != 0) + return s; + int c = -f1.synchronize.compareTo(f2.synchronize); + if (c != 0) + return c; + return collator.compare( + f1.name == null ? "" : f1.name, + f2.name == null ? "" : f2.name); + } + }); + + EntityFolder none = new EntityFolder(); + none.name = ""; + data.folders.add(0, none); + + ArrayAdapter adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, data.folders); + adapter.setDropDownViewResource(R.layout.spinner_dropdown_item); + + spDrafts.setAdapter(adapter); + spSent.setAdapter(adapter); + spAll.setAdapter(adapter); + spTrash.setAdapter(adapter); + spJunk.setAdapter(adapter); + + for (int pos = 0; pos < data.folders.size(); pos++) { + if (EntityFolder.DRAFTS.equals(data.folders.get(pos).type)) + spDrafts.setSelection(pos); + else if (EntityFolder.SENT.equals(data.folders.get(pos).type)) + spSent.setSelection(pos); + else if (EntityFolder.ARCHIVE.equals(data.folders.get(pos).type)) + spAll.setSelection(pos); + else if (EntityFolder.TRASH.equals(data.folders.get(pos).type)) + spTrash.setSelection(pos); + else if (EntityFolder.JUNK.equals(data.folders.get(pos).type)) + spJunk.setSelection(pos); + } + + grpFolders.setVisibility(View.VISIBLE); + btnSave.setVisibility(View.VISIBLE); + } else { + Log.w(Helper.TAG, data.ex + "\n" + Log.getStackTraceString(data.ex)); + Toast.makeText(getContext(), Helper.formatThrowable(data.ex), Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onLoaderReset(@NonNull Loader loader) { + } + }; + private static class PutLoader extends AsyncTaskLoader { private Bundle args; @@ -238,6 +490,11 @@ public class FragmentAccount extends FragmentEx { String user = args.getString("user"); String password = args.getString("password"); boolean synchronize = args.getBoolean("synchronize"); + EntityFolder drafts = (EntityFolder) args.getSerializable("drafts"); + EntityFolder sent = (EntityFolder) args.getSerializable("sent"); + EntityFolder all = (EntityFolder) args.getSerializable("all"); + EntityFolder trash = (EntityFolder) args.getSerializable("trash"); + EntityFolder junk = (EntityFolder) args.getSerializable("junk"); if (TextUtils.isEmpty(host)) throw new Throwable(getContext().getString(R.string.title_no_host)); @@ -247,11 +504,28 @@ public class FragmentAccount extends FragmentEx { throw new Throwable(getContext().getString(R.string.title_no_user)); if (TextUtils.isEmpty(password)) throw new Throwable(getContext().getString(R.string.title_no_password)); + if (drafts == null) + throw new Throwable(getContext().getString(R.string.title_no_drafts)); + + // Check IMAP server + Session isession = Session.getInstance(MessageHelper.getSessionProperties(), null); + IMAPStore istore = null; + try { + istore = (IMAPStore) isession.getStore("imaps"); + istore.connect(host, Integer.parseInt(port), user, password); + + if (!istore.hasCapability("IDLE")) + throw new MessagingException(getContext().getString(R.string.title_no_idle)); + } finally { + if (istore != null) + istore.close(); + } if (TextUtils.isEmpty(name)) name = host + "/" + user; DB db = DB.getInstance(getContext()); + EntityAccount account = db.account().getAccount(args.getLong("id")); boolean update = (account != null); if (account == null) @@ -268,100 +542,61 @@ public class FragmentAccount extends FragmentEx { if (!account.synchronize && account.synchronize != synchronize) account.seen_until = new Date().getTime(); - // Check IMAP server - List folders = new ArrayList<>(); - if (account.synchronize) { - Session isession = Session.getInstance(MessageHelper.getSessionProperties(), null); - IMAPStore istore = null; - try { - istore = (IMAPStore) isession.getStore("imaps"); - istore.connect(account.host, account.port, account.user, account.password); - - if (!istore.hasCapability("IDLE")) - throw new MessagingException(getContext().getString(R.string.title_no_idle)); - - // Find system folders - boolean drafts = false; - for (Folder ifolder : istore.getDefaultFolder().list("*")) { - String type = null; - - // First check folder attributes - String[] attrs = ((IMAPFolder) ifolder).getAttributes(); - for (String attr : attrs) { - if (attr.startsWith("\\")) { - int index = EntityFolder.SYSTEM_FOLDER_ATTR.indexOf(attr.substring(1)); - if (index >= 0) { - type = EntityFolder.SYSTEM_FOLDER_TYPE.get(index); - break; - } - } - } - - // Next check folder full name - if (type == null) { - String fullname = ifolder.getFullName(); - for (String attr : EntityFolder.SYSTEM_FOLDER_ATTR) - if (attr.equals(fullname)) { - int index = EntityFolder.SYSTEM_FOLDER_ATTR.indexOf(attr); - type = EntityFolder.SYSTEM_FOLDER_TYPE.get(index); - break; - } - } - - if (type != null) { - EntityFolder folder = new EntityFolder(); - folder.name = ifolder.getFullName(); - folder.type = type; - folder.synchronize = EntityFolder.SYSTEM_FOLDER_SYNC.contains(folder.type); - folder.after = EntityFolder.DEFAULT_SYSTEM_SYNC; - folders.add(folder); - - Log.i(Helper.TAG, account.name + - " system=" + folder.name + - " type=" + folder.type + " attr=" + TextUtils.join(",", attrs)); - - if (EntityFolder.DRAFTS.equals(folder.type)) - drafts = true; - } - } - - if (!drafts) { - EntityFolder folder = new EntityFolder(); - folder.name = getContext().getString(R.string.title_folder_local_drafts); - folder.type = EntityFolder.DRAFTS; - folder.synchronize = false; - folder.after = 0; - folders.add(folder); - } - } finally { - if (istore != null) - istore.close(); - } - } - - if (account.primary) - db.account().resetPrimary(); - try { db.beginTransaction(); + + if (account.primary) + db.account().resetPrimary(); + if (update) db.account().updateAccount(account); else account.id = db.account().insertAccount(account); + List folders = new ArrayList<>(); + EntityFolder inbox = new EntityFolder(); inbox.name = "INBOX"; inbox.type = EntityFolder.INBOX; inbox.synchronize = true; inbox.after = EntityFolder.DEFAULT_INBOX_SYNC; - folders.add(0, inbox); - for (EntityFolder folder : folders) - if (db.folder().getFolderByName(account.id, folder.name) == null) { + folders.add(inbox); + if (drafts != null) { + drafts.type = EntityFolder.DRAFTS; + folders.add(drafts); + } + if (sent != null) { + sent.type = EntityFolder.SENT; + folders.add(sent); + } + if (all != null) { + all.type = EntityFolder.ARCHIVE; + folders.add(all); + } + if (trash != null) { + trash.type = EntityFolder.TRASH; + folders.add(trash); + } + if (junk != null) { + junk.type = EntityFolder.JUNK; + folders.add(junk); + } + + int count = db.folder().deleteSystemFolders(account.id); + Log.w(Helper.TAG, "Deleted system folders count=" + count); + + for (EntityFolder folder : folders) { + EntityFolder existing = db.folder().getFolderByName(account.id, folder.name); + if (existing == null) { folder.account = account.id; Log.i(Helper.TAG, "Creating folder=" + folder.name + " (" + folder.type + ")"); folder.id = db.folder().insertFolder(folder); + } else { + existing.type = folder.type; + db.folder().updateFolder(existing); } + } db.setTransactionSuccessful(); } finally { @@ -392,7 +627,7 @@ public class FragmentAccount extends FragmentEx { LoaderManager.getInstance(FragmentAccount.this).destroyLoader(loader.getId()); btnSave.setEnabled(true); - pbCheck.setVisibility(View.GONE); + btnCheck.setVisibility(View.GONE); if (ex == null) getFragmentManager().popBackStack(); diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 96a8f1f063..210fec6e21 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -517,7 +517,7 @@ public class FragmentCompose extends FragmentEx { if (ident == null) throw new IllegalArgumentException(getContext().getString(R.string.title_from_missing)); - EntityFolder drafts = EntityFolder.getDrafts(getContext(), db, ident.account); + EntityFolder drafts = db.folder().getFolderByType(ident.account, EntityFolder.DRAFTS); long rid = args.getLong("rid", -1); String thread = args.getString("thread"); @@ -623,12 +623,9 @@ public class FragmentCompose extends FragmentEx { } private LoaderManager.LoaderCallbacks putLoaderCallbacks = new LoaderManager.LoaderCallbacks() { - private Bundle args; - @NonNull @Override public Loader onCreateLoader(int id, Bundle args) { - this.args = args; PutLoader loader = new PutLoader(getContext()); loader.setArgs(args); return loader; @@ -638,6 +635,7 @@ public class FragmentCompose extends FragmentEx { public void onLoadFinished(@NonNull Loader loader, Throwable ex) { LoaderManager.getInstance(FragmentCompose.this).destroyLoader(loader.getId()); + Bundle args = ((PutLoader) loader).args; String action = args.getString("action"); Log.i(Helper.TAG, "Put finished action=" + action + " ex=" + ex); diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 62110e3833..3e23f821b6 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -366,13 +366,21 @@ public class FragmentIdentity extends FragmentEx { } } - if (identity.primary) - db.identity().resetPrimary(); + try { + db.beginTransaction(); - if (update) - db.identity().updateIdentity(identity); - else - identity.id = db.identity().insertIdentity(identity); + if (identity.primary) + db.identity().resetPrimary(); + + if (update) + db.identity().updateIdentity(identity); + else + identity.id = db.identity().insertIdentity(identity); + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } return null; } catch (Throwable ex) { diff --git a/app/src/main/java/eu/faircode/email/FragmentMessage.java b/app/src/main/java/eu/faircode/email/FragmentMessage.java index 9a5fa52403..a15c3d02af 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessage.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessage.java @@ -444,7 +444,7 @@ public class FragmentMessage extends FragmentEx { long id = args.getLong("id"); DB db = DB.getInstance(getContext()); EntityMessage draft = db.message().getMessage(id); - EntityFolder drafts = EntityFolder.getDrafts(getContext(), db, draft.account); + EntityFolder drafts = db.folder().getFolderByType(draft.account, EntityFolder.DRAFTS); draft.id = null; draft.folder = drafts.id; draft.uid = null; @@ -757,19 +757,16 @@ public class FragmentMessage extends FragmentEx { } private LoaderManager.LoaderCallbacks moveLoaderCallbacks = new LoaderManager.LoaderCallbacks>() { - Bundle args; - @NonNull @Override public Loader> onCreateLoader(int id, Bundle args) { - this.args = args; MoveLoader loader = new MoveLoader(getContext()); loader.setArgs(args); return loader; } @Override - public void onLoadFinished(@NonNull Loader> loader, List folders) { + public void onLoadFinished(@NonNull final Loader> loader, List folders) { LoaderManager.getInstance(FragmentMessage.this).destroyLoader(loader.getId()); View anchor = bottom_navigation.findViewById(R.id.action_move); @@ -788,6 +785,7 @@ public class FragmentMessage extends FragmentEx { final Drawable icon = item.getIcon(); item.setIcon(Helper.toDimmed(icon)); + Bundle args = ((MoveLoader) loader).args; args.putLong("target", target.getItemId()); new SimpleLoader() { diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 3c7f127caa..799efeaf4e 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -986,19 +986,20 @@ public class ServiceSynchronize extends LifecycleService { for (Folder ifolder : ifolders) { String[] attrs = ((IMAPFolder) ifolder).getAttributes(); - boolean candidate = true; + boolean selectable = true; for (String attr : attrs) { if ("\\Noselect".equals(attr)) { // TODO: is this attribute correct? - candidate = false; + selectable = false; break; } if (attr.startsWith("\\")) if (EntityFolder.SYSTEM_FOLDER_ATTR.contains(attr.substring(1))) { - candidate = false; + selectable = false; break; } } - if (candidate) { + + if (selectable) { Log.i(Helper.TAG, ifolder.getFullName() + " candidate attr=" + TextUtils.join(",", attrs)); EntityFolder folder = dao.getFolderByName(account.id, ifolder.getFullName()); if (folder == null) { diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index aa8593af2e..14c8b2368d 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -176,12 +176,14 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/cbSynchronize" /> + +