Added folder namespaces

This commit is contained in:
M66B 2021-09-24 09:51:57 +02:00
parent aa467c4af4
commit ea1573c273
15 changed files with 2665 additions and 79 deletions

View File

@ -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

View File

@ -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

View File

@ -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));

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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");

View File

@ -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));

View File

@ -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))

View File

@ -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) &&

View File

@ -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