mirror of https://github.com/M66B/FairEmail.git
Added folder namespaces
This commit is contained in:
parent
aa467c4af4
commit
ea1573c273
|
@ -4,6 +4,10 @@
|
|||
|
||||
### [Zanabazar](https://en.wikipedia.org/wiki/Zanabazar_junior)
|
||||
|
||||
### Next version
|
||||
|
||||
* Storing folder namespaces
|
||||
|
||||
### 1.1732
|
||||
|
||||
* Added collapsing of navigation menu folders
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,10 @@
|
|||
|
||||
### [Zanabazar](https://en.wikipedia.org/wiki/Zanabazar_junior)
|
||||
|
||||
### Next version
|
||||
|
||||
* Storing folder namespaces
|
||||
|
||||
### 1.1732
|
||||
|
||||
* Added collapsing of navigation menu folders
|
||||
|
|
|
@ -368,7 +368,10 @@ public class AdapterFolder extends RecyclerView.Adapter<AdapterFolder.ViewHolder
|
|||
? textColorPrimary : textColorSecondary));
|
||||
ibSync.setEnabled(folder.last_sync != null);
|
||||
|
||||
tvKeywords.setText(BuildConfig.DEBUG ? TextUtils.join(" ", folder.keywords) : null);
|
||||
tvKeywords.setText(BuildConfig.DEBUG ?
|
||||
(folder.separator == null ? "" : folder.separator + " ") +
|
||||
(folder.namespace == null ? "" : folder.namespace + " ") +
|
||||
TextUtils.join(" ", folder.keywords) : null);
|
||||
tvKeywords.setVisibility(show_flagged ? View.VISIBLE : View.GONE);
|
||||
|
||||
tvFlagged.setText(NF.format(folder.flagged));
|
||||
|
|
|
@ -67,6 +67,7 @@ import com.sun.mail.imap.IMAPStore;
|
|||
import com.sun.mail.imap.protocol.FLAGS;
|
||||
import com.sun.mail.imap.protocol.FetchResponse;
|
||||
import com.sun.mail.imap.protocol.IMAPProtocol;
|
||||
import com.sun.mail.imap.protocol.Namespaces;
|
||||
import com.sun.mail.imap.protocol.UID;
|
||||
import com.sun.mail.pop3.POP3Folder;
|
||||
import com.sun.mail.pop3.POP3Message;
|
||||
|
@ -2022,13 +2023,10 @@ class Core {
|
|||
|
||||
// Get default folder
|
||||
Folder defaultFolder = istore.getDefaultFolder();
|
||||
char separator = defaultFolder.getSeparator();
|
||||
EntityLog.log(context, account.name + " folder separator=" + separator);
|
||||
db.account().setFolderSeparator(account.id, separator);
|
||||
|
||||
// Get remote folders
|
||||
long start = new Date().getTime();
|
||||
List<Folder> ifolders = new ArrayList<>();
|
||||
List<Pair<Folder, Folder>> ifolders = new ArrayList<>();
|
||||
List<String> subscription = new ArrayList<>();
|
||||
|
||||
Folder[] personal;
|
||||
|
@ -2043,22 +2041,23 @@ class Core {
|
|||
|
||||
for (Folder namespace : personal) {
|
||||
EntityLog.log(context, "Personal namespace=" + namespace.getFullName());
|
||||
if (namespace.getSeparator() == separator) {
|
||||
String pattern = namespace.getFullName() + "*";
|
||||
ifolders.addAll(Arrays.asList(defaultFolder.list(pattern)));
|
||||
|
||||
try {
|
||||
Folder[] isubscribed = defaultFolder.listSubscribed(pattern);
|
||||
for (Folder ifolder : isubscribed) {
|
||||
String fullName = ifolder.getFullName();
|
||||
if (TextUtils.isEmpty(fullName)) {
|
||||
Log.w("Subscribed folder name empty namespace=" + defaultFolder.getFullName());
|
||||
continue;
|
||||
}
|
||||
subscription.add(fullName);
|
||||
Log.i("Subscribed " + defaultFolder.getFullName() + ":" + fullName);
|
||||
String pattern = namespace.getFullName() + "*";
|
||||
for (Folder ifolder : defaultFolder.list(pattern))
|
||||
ifolders.add(new Pair<>(namespace, ifolder));
|
||||
|
||||
try {
|
||||
Folder[] isubscribed = defaultFolder.listSubscribed(pattern);
|
||||
for (Folder ifolder : isubscribed) {
|
||||
String fullName = ifolder.getFullName();
|
||||
if (TextUtils.isEmpty(fullName)) {
|
||||
Log.w("Subscribed folder name empty namespace=" + defaultFolder.getFullName());
|
||||
continue;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
subscription.add(fullName);
|
||||
Log.i("Subscribed " + defaultFolder.getFullName() + ":" + fullName);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
/*
|
||||
06-21 10:02:38.035 9927 10024 E fairemail: java.lang.NullPointerException: Folder name is null
|
||||
06-21 10:02:38.035 9927 10024 E fairemail: at com.sun.mail.imap.IMAPFolder.<init>(SourceFile:372)
|
||||
|
@ -2066,41 +2065,40 @@ class Core {
|
|||
06-21 10:02:38.035 9927 10024 E fairemail: at com.sun.mail.imap.IMAPStore.newIMAPFolder(SourceFile:1809)
|
||||
06-21 10:02:38.035 9927 10024 E fairemail: at com.sun.mail.imap.DefaultFolder.listSubscribed(SourceFile:89)
|
||||
*/
|
||||
Log.e(account.name, ex);
|
||||
}
|
||||
} else
|
||||
Log.e("Personal namespace separator=" + namespace.getSeparator() + " default=" + separator);
|
||||
Log.e(account.name, ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (sync_shared_folders) {
|
||||
// https://tools.ietf.org/html/rfc2342
|
||||
Folder[] shared = istore.getSharedNamespaces();
|
||||
EntityLog.log(context, "Shared namespaces=" + shared.length);
|
||||
|
||||
for (Folder namespace : shared) {
|
||||
EntityLog.log(context, "Shared namespace=" + namespace.getFullName());
|
||||
if (namespace.getSeparator() == separator) {
|
||||
try {
|
||||
ifolders.addAll(Arrays.asList(namespace.list("*")));
|
||||
} catch (FolderNotFoundException ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
|
||||
try {
|
||||
Folder[] isubscribed = namespace.listSubscribed("*");
|
||||
for (Folder ifolder : isubscribed) {
|
||||
String fullName = ifolder.getFullName();
|
||||
if (TextUtils.isEmpty(fullName)) {
|
||||
Log.e("Subscribed folder name empty namespace=" + namespace.getFullName());
|
||||
continue;
|
||||
}
|
||||
subscription.add(fullName);
|
||||
Log.i("Subscribed " + namespace.getFullName() + ":" + fullName);
|
||||
String pattern = namespace.getFullName() + "*";
|
||||
try {
|
||||
for (Folder ifolder : defaultFolder.list(pattern))
|
||||
ifolders.add(new Pair<>(namespace, ifolder));
|
||||
} catch (FolderNotFoundException ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
|
||||
try {
|
||||
Folder[] isubscribed = namespace.listSubscribed(pattern);
|
||||
for (Folder ifolder : isubscribed) {
|
||||
String fullName = ifolder.getFullName();
|
||||
if (TextUtils.isEmpty(fullName)) {
|
||||
Log.e("Subscribed folder name empty namespace=" + namespace.getFullName());
|
||||
continue;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(account.name, ex);
|
||||
subscription.add(fullName);
|
||||
Log.i("Subscribed " + namespace.getFullName() + ":" + fullName);
|
||||
}
|
||||
} else
|
||||
Log.e("Shared namespace separator=" + namespace.getSeparator() + " default=" + separator);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(account.name, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2108,17 +2106,16 @@ class Core {
|
|||
|
||||
Log.i("Remote folder count=" + ifolders.size() +
|
||||
" subscriptions=" + subscription.size() +
|
||||
" separator=" + separator +
|
||||
" fetched in " + duration + " ms");
|
||||
|
||||
// Check if system folders were renamed
|
||||
try {
|
||||
for (Folder ifolder : ifolders) {
|
||||
String fullName = ifolder.getFullName();
|
||||
for (Pair<Folder, Folder> ifolder : ifolders) {
|
||||
String fullName = ifolder.second.getFullName();
|
||||
if (TextUtils.isEmpty(fullName))
|
||||
continue;
|
||||
|
||||
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
|
||||
String[] attrs = ((IMAPFolder) ifolder.second).getAttributes();
|
||||
String type = EntityFolder.getType(attrs, fullName, false);
|
||||
if (type != null &&
|
||||
!EntityFolder.USER.equals(type) &&
|
||||
|
@ -2161,14 +2158,14 @@ class Core {
|
|||
|
||||
Map<String, EntityFolder> nameFolder = new HashMap<>();
|
||||
Map<String, List<EntityFolder>> parentFolders = new HashMap<>();
|
||||
for (Folder ifolder : ifolders) {
|
||||
String fullName = ifolder.getFullName();
|
||||
for (Pair<Folder, Folder> ifolder : ifolders) {
|
||||
String fullName = ifolder.second.getFullName();
|
||||
if (TextUtils.isEmpty(fullName)) {
|
||||
Log.e("Folder name empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] attrs = ((IMAPFolder) ifolder).getAttributes();
|
||||
String[] attrs = ((IMAPFolder) ifolder.second).getAttributes();
|
||||
String type = EntityFolder.getType(attrs, fullName, false);
|
||||
boolean subscribed = subscription.contains(fullName);
|
||||
|
||||
|
@ -2180,8 +2177,8 @@ class Core {
|
|||
if (attr.equalsIgnoreCase("\\NoInferiors"))
|
||||
inferiors = false;
|
||||
}
|
||||
selectable = selectable && ((ifolder.getType() & IMAPFolder.HOLDS_MESSAGES) != 0);
|
||||
inferiors = inferiors && ((ifolder.getType() & IMAPFolder.HOLDS_FOLDERS) != 0);
|
||||
selectable = selectable && ((ifolder.second.getType() & IMAPFolder.HOLDS_MESSAGES) != 0);
|
||||
inferiors = inferiors && ((ifolder.second.getType() & IMAPFolder.HOLDS_FOLDERS) != 0);
|
||||
|
||||
if (EntityFolder.INBOX.equals(type))
|
||||
selectable = true;
|
||||
|
@ -2202,12 +2199,15 @@ class Core {
|
|||
folder = db.folder().getFolderByName(account.id, fullName);
|
||||
if (folder == null) {
|
||||
EntityFolder parent = null;
|
||||
char separator = ifolder.first.getSeparator();
|
||||
int sep = fullName.lastIndexOf(separator);
|
||||
if (sep > 0)
|
||||
parent = db.folder().getFolderByName(account.id, fullName.substring(0, sep));
|
||||
|
||||
folder = new EntityFolder();
|
||||
folder.account = account.id;
|
||||
folder.namespace = ifolder.first.getFullName();
|
||||
folder.separator = separator;
|
||||
folder.name = fullName;
|
||||
folder.type = (EntityFolder.SYSTEM.equals(type) ? type : EntityFolder.USER);
|
||||
folder.subscribed = subscribed;
|
||||
|
@ -2235,6 +2235,10 @@ class Core {
|
|||
} else {
|
||||
Log.i(folder.name + " exists type=" + folder.type);
|
||||
|
||||
folder.namespace = ifolder.first.getFullName();
|
||||
folder.separator = ifolder.first.getSeparator();
|
||||
db.folder().setFolderNamespace(folder.id, folder.namespace, folder.separator);
|
||||
|
||||
if (folder.subscribed == null || !folder.subscribed.equals(subscribed))
|
||||
db.folder().setFolderSubscribed(folder.id, subscribed);
|
||||
|
||||
|
@ -2262,7 +2266,7 @@ class Core {
|
|||
}
|
||||
|
||||
nameFolder.put(folder.name, folder);
|
||||
String parentName = folder.getParentName(separator);
|
||||
String parentName = folder.getParentName();
|
||||
if (!parentFolders.containsKey(parentName))
|
||||
parentFolders.put(parentName, new ArrayList<EntityFolder>());
|
||||
parentFolders.get(parentName).add(folder);
|
||||
|
|
|
@ -68,7 +68,7 @@ import io.requery.android.database.sqlite.SQLiteDatabase;
|
|||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 209,
|
||||
version = 210,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
|
@ -2142,6 +2142,15 @@ public abstract class DB extends RoomDatabase {
|
|||
db.execSQL("ALTER TABLE `log` ADD COLUMN `folder` INTEGER");
|
||||
db.execSQL("ALTER TABLE `log` ADD COLUMN `message` INTEGER");
|
||||
}
|
||||
}).addMigrations(new Migration(209, 210) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase db) {
|
||||
Log.i("DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `folder` ADD COLUMN `namespace` TEXT");
|
||||
db.execSQL("ALTER TABLE `folder` ADD COLUMN `separator` INTEGER");
|
||||
db.execSQL("UPDATE folder SET separator =" +
|
||||
" (SELECT separator FROM account WHERE account.id = folder.account)");
|
||||
}
|
||||
}).addMigrations(new Migration(998, 999) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase db) {
|
||||
|
|
|
@ -78,7 +78,8 @@ public interface DaoAccount {
|
|||
LiveData<List<TupleAccountEx>> liveAccountsEx(boolean all);
|
||||
|
||||
@Query("SELECT account.*" +
|
||||
", NULL AS folderId, NULL AS folderType, -1 AS folderOrder" +
|
||||
", NULL AS folderId, NULL AS folderSeparator" +
|
||||
", NULL AS folderType, -1 AS folderOrder" +
|
||||
", NULL AS folderName, NULL AS folderDisplay, NULL AS folderColor" +
|
||||
", 0 AS folderSync, NULL AS folderState, NULL AS folderSyncState" +
|
||||
", 0 AS executing" +
|
||||
|
@ -103,7 +104,8 @@ public interface DaoAccount {
|
|||
" UNION " +
|
||||
|
||||
" SELECT account.*" +
|
||||
", folder.id AS folderId, folder.type AS folderType, folder.`order` AS folderOrder" +
|
||||
", folder.id AS folderId, folder.separator AS folderSeparator" +
|
||||
", folder.type AS folderType, folder.`order` AS folderOrder" +
|
||||
", folder.name AS folderName, folder.display AS folderDisplay, folder.color AS folderColor" +
|
||||
", folder.synchronize AS folderSync, folder.state AS foldeState, folder.sync_state AS folderSyncState" +
|
||||
", (SELECT COUNT(operation.id) FROM operation" +
|
||||
|
@ -177,9 +179,6 @@ public interface DaoAccount {
|
|||
@Update
|
||||
void updateAccount(EntityAccount account);
|
||||
|
||||
@Query("UPDATE account SET separator = :separator WHERE id = :id AND NOT (separator IS :separator)")
|
||||
int setFolderSeparator(long id, Character separator);
|
||||
|
||||
@Query("UPDATE account SET synchronize = :synchronize WHERE id = :id AND NOT (synchronize IS :synchronize)")
|
||||
int setAccountSynchronize(long id, boolean synchronize);
|
||||
|
||||
|
|
|
@ -233,6 +233,11 @@ public interface DaoFolder {
|
|||
@Insert
|
||||
long insertFolder(EntityFolder folder);
|
||||
|
||||
@Query("UPDATE folder" +
|
||||
" SET namespace = :namespace, separator = :separator" +
|
||||
" WHERE id = :id AND NOT (namespace IS :namespace AND separator IS :separator)")
|
||||
int setFolderNamespace(long id, String namespace, Character separator);
|
||||
|
||||
@Query("UPDATE folder SET unified = :unified WHERE id = :id AND NOT (unified IS :unified)")
|
||||
int setFolderUnified(long id, boolean unified);
|
||||
|
||||
|
|
|
@ -713,7 +713,7 @@ public class EmailService implements AutoCloseable {
|
|||
folders.add(new EntityFolder(fullName, type));
|
||||
}
|
||||
|
||||
EntityFolder.guessTypes(folders, getStore().getDefaultFolder().getSeparator());
|
||||
EntityFolder.guessTypes(folders);
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
|
|
@ -116,7 +116,8 @@ public class EntityAccount extends EntityOrder implements Serializable {
|
|||
public Integer max_messages; // POP3
|
||||
@NonNull
|
||||
public Boolean auto_seen = true;
|
||||
public Character separator;
|
||||
@ColumnInfo(name = "separator")
|
||||
public Character _separator; // obsolete
|
||||
public Long swipe_left;
|
||||
public Long swipe_right;
|
||||
public Long move_to;
|
||||
|
|
|
@ -71,6 +71,8 @@ public class EntityFolder extends EntityOrder implements Serializable {
|
|||
public Long parent;
|
||||
public Long uidv; // UIDValidity
|
||||
public Long modseq;
|
||||
public String namespace;
|
||||
public Character separator;
|
||||
@NonNull
|
||||
public String name;
|
||||
@NonNull
|
||||
|
@ -441,7 +443,7 @@ public class EntityFolder extends EntityOrder implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
static void guessTypes(List<EntityFolder> folders, final Character separator) {
|
||||
static void guessTypes(List<EntityFolder> folders) {
|
||||
List<String> types = new ArrayList<>();
|
||||
Map<String, List<FolderScore>> typeFolderScore = new HashMap<>();
|
||||
|
||||
|
@ -471,16 +473,11 @@ public class EntityFolder extends EntityOrder implements Serializable {
|
|||
|
||||
int s = Integer.compare(fs1.score, fs2.score);
|
||||
if (s == 0) {
|
||||
if (separator == null)
|
||||
return Integer.compare(
|
||||
fs1.folder.name.length(),
|
||||
fs2.folder.name.length());
|
||||
else {
|
||||
String sep = Pattern.quote(String.valueOf(separator));
|
||||
return Integer.compare(
|
||||
fs1.folder.name.split(sep).length,
|
||||
fs2.folder.name.split(sep).length);
|
||||
}
|
||||
int len1 = (fs1.folder.separator == null ? 1
|
||||
: fs1.folder.name.split(Pattern.quote(String.valueOf(fs1.folder.separator))).length);
|
||||
int len2 = (fs2.folder.separator == null ? 1
|
||||
: fs2.folder.name.split(Pattern.quote(String.valueOf(fs2.folder.separator))).length);
|
||||
return Integer.compare(len1, len2);
|
||||
} else
|
||||
return s;
|
||||
}
|
||||
|
@ -494,7 +491,7 @@ public class EntityFolder extends EntityOrder implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
String getParentName(Character separator) {
|
||||
String getParentName() {
|
||||
if (separator == null)
|
||||
return null;
|
||||
else {
|
||||
|
@ -531,6 +528,8 @@ public class EntityFolder extends EntityOrder implements Serializable {
|
|||
Objects.equals(this.account, other.account) &&
|
||||
Objects.equals(this.parent, other.parent) &&
|
||||
Objects.equals(this.uidv, other.uidv) &&
|
||||
Objects.equals(this.namespace, other.namespace) &&
|
||||
Objects.equals(this.separator, other.separator) &&
|
||||
this.name.equals(other.name) &&
|
||||
this.type.equals(other.type) &&
|
||||
this.level.equals(other.level) &&
|
||||
|
@ -577,6 +576,9 @@ public class EntityFolder extends EntityOrder implements Serializable {
|
|||
JSONObject json = new JSONObject();
|
||||
json.put("id", id);
|
||||
json.put("order", order);
|
||||
json.put("namespace", namespace);
|
||||
if (separator != null)
|
||||
json.put("separator", (int) separator);
|
||||
json.put("name", name);
|
||||
json.put("type", type);
|
||||
json.put("synchronize", synchronize);
|
||||
|
@ -606,6 +608,11 @@ public class EntityFolder extends EntityOrder implements Serializable {
|
|||
if (json.has("order"))
|
||||
folder.order = json.getInt("order");
|
||||
|
||||
if (json.has("namespace"))
|
||||
folder.namespace = json.getString("namespace");
|
||||
if (json.has("separator"))
|
||||
folder.separator = (char) json.getInt("separator");
|
||||
|
||||
folder.name = json.getString("name");
|
||||
folder.type = json.getString("type");
|
||||
|
||||
|
|
|
@ -744,7 +744,7 @@ public class FragmentAccount extends FragmentBase {
|
|||
}
|
||||
}
|
||||
|
||||
EntityFolder.guessTypes(result.folders, iservice.getStore().getDefaultFolder().getSeparator());
|
||||
EntityFolder.guessTypes(result.folders);
|
||||
|
||||
if (result.folders.size() > 0)
|
||||
Collections.sort(result.folders, result.folders.get(0).getComparator(null));
|
||||
|
|
|
@ -574,11 +574,11 @@ public class FragmentFolder extends FragmentBase {
|
|||
Log.i("Creating folder=" + name + " parent=" + parent);
|
||||
|
||||
if (parent != null) {
|
||||
EntityAccount account = db.account().getAccount(aid);
|
||||
if (account == null)
|
||||
EntityFolder p = db.folder().getFolderByName(aid, parent);
|
||||
if (p == null || p.separator == null)
|
||||
return false;
|
||||
|
||||
name = parent + account.separator + name;
|
||||
name = parent + p.separator + name;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(name))
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.Objects;
|
|||
|
||||
public class TupleAccountFolder extends EntityAccount {
|
||||
public Long folderId;
|
||||
public Character folderSeparator;
|
||||
public String folderType;
|
||||
public Integer folderOrder;
|
||||
public String folderName;
|
||||
|
@ -47,10 +48,10 @@ public class TupleAccountFolder extends EntityAccount {
|
|||
if (EntityFolder.INBOX.equals(folderType))
|
||||
return EntityFolder.localizeName(context, folderName);
|
||||
|
||||
if (separator == null)
|
||||
if (folderSeparator == null)
|
||||
return folderName;
|
||||
|
||||
int s = folderName.lastIndexOf(separator);
|
||||
int s = folderName.lastIndexOf(folderSeparator);
|
||||
if (s < 0)
|
||||
return folderName;
|
||||
|
||||
|
@ -63,6 +64,7 @@ public class TupleAccountFolder extends EntityAccount {
|
|||
TupleAccountFolder other = (TupleAccountFolder) obj;
|
||||
return (super.equals(obj) &&
|
||||
Objects.equals(this.folderId, other.folderId) &&
|
||||
Objects.equals(this.folderSeparator, other.folderSeparator) &&
|
||||
Objects.equals(this.folderType, other.folderType) &&
|
||||
Objects.equals(this.folderOrder, other.folderOrder) &&
|
||||
Objects.equals(this.folderName, other.folderName) &&
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
### [Zanabazar](https://en.wikipedia.org/wiki/Zanabazar_junior)
|
||||
|
||||
### Next version
|
||||
|
||||
* Storing folder namespaces
|
||||
|
||||
### 1.1732
|
||||
|
||||
* Added collapsing of navigation menu folders
|
||||
|
|
Loading…
Reference in New Issue