From 9b57abd8806e50b93781f11b66176979ad2daa9c Mon Sep 17 00:00:00 2001 From: M66B Date: Fri, 20 Jan 2023 15:25:38 +0100 Subject: [PATCH] Cloud sync: insert/update accounts/identities --- .../java/eu/faircode/email/CloudSync.java | 129 +++++++++++++++--- app/src/main/java/eu/faircode/email/DB.java | 2 + .../java/eu/faircode/email/DaoAccount.java | 5 + .../res/layout/fragment_options_backup.xml | 16 ++- app/src/main/res/values/strings.xml | 1 + 5 files changed, 130 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/CloudSync.java b/app/src/main/java/eu/faircode/email/CloudSync.java index 3e9d7e8d59..d9154909d1 100644 --- a/app/src/main/java/eu/faircode/email/CloudSync.java +++ b/app/src/main/java/eu/faircode/email/CloudSync.java @@ -75,6 +75,7 @@ public class CloudSync { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String user = prefs.getString("cloud_user", null); String password = prefs.getString("cloud_password", null); + if (TextUtils.isEmpty(user) || TextUtils.isEmpty(password)) return; if (!ActivityBilling.isPro(context)) @@ -83,7 +84,7 @@ public class CloudSync { JSONObject jrequest = new JSONObject(); if ("sync".equals(command)) { - long lrevision = prefs.getLong("cloud_revision", new Date().getTime()); + long lrevision = prefs.getLong("cloud_lrevision", 0); Log.i("Cloud local revision=" + lrevision + " (" + new Date(lrevision) + ")"); Long lastUpdate = updateSyncdata(context); @@ -91,7 +92,8 @@ public class CloudSync { if (lastUpdate != null && lrevision > lastUpdate) { Log.w("Cloud invalid local revision" + " lrevision=" + lrevision + " last=" + lastUpdate); - prefs.edit().putLong("cloud_revision", lastUpdate).apply(); + lrevision = lastUpdate; + prefs.edit().putLong("cloud_lrevision", lrevision).apply(); } JSONObject jsyncstatus = new JSONObject(); @@ -108,7 +110,9 @@ public class CloudSync { if (jitems.length() == 0) { Log.i("Cloud server is empty"); - sendLocalData(context, user, password, lrevision); + sendLocalData(context, user, password, lrevision == 0 + ? (lastUpdate == null ? new Date().getTime() : lastUpdate) + : lrevision); } else if (jitems.length() == 1) { Log.i("Cloud sync check"); jsyncstatus = jitems.getJSONObject(0); @@ -134,11 +138,11 @@ public class CloudSync { if (lastUpdate >= rrevision) sendLocalData(context, user, password, lastUpdate); else - receiveRemoteData(context, user, password, lrevision, jstatus); + receiveRemoteData(context, user, password, lrevision, rrevision, jstatus); } else - receiveRemoteData(context, user, password, lrevision, jstatus); + receiveRemoteData(context, user, password, lrevision, rrevision, jstatus); else if (BuildConfig.DEBUG && false) - receiveRemoteData(context, user, password, lrevision - 1, jstatus); + receiveRemoteData(context, user, password, lrevision - 1, rrevision, jstatus); } else throw new IllegalArgumentException("Expected one status item"); } else { @@ -156,10 +160,10 @@ public class CloudSync { Long last = null; - List accounts = db.account().getSynchronizingAccounts(null); + List accounts = db.account().getAccounts(); if (accounts != null) for (EntityAccount account : accounts) - if (!TextUtils.isEmpty(account.uuid)) { + if (account.synchronize && !TextUtils.isEmpty(account.uuid)) { EntityAccount aexisting = null; File afile = new File(dir, "account." + account.uuid + ".json"); if (afile.exists()) @@ -182,7 +186,7 @@ public class CloudSync { List identities = db.identity().getIdentities(account.id); if (identities != null) for (EntityIdentity identity : identities) - if (!TextUtils.isEmpty(identity.uuid)) { + if (identity.synchronize && !TextUtils.isEmpty(identity.uuid)) { EntityIdentity iexisting = null; File ifile = new File(dir, "identity." + identity.uuid + ".json"); if (ifile.exists()) @@ -233,9 +237,12 @@ public class CloudSync { if (!TextUtils.isEmpty(identity.uuid)) { jidentitieuuids.put(identity.uuid); + JSONObject jidentity = identity.toJSON(); + jidentity.put("account_uuid", account.uuid); + JSONObject jidentitykv = new JSONObject(); jidentitykv.put("key", "identity." + identity.uuid); - jidentitykv.put("val", identity.toJSON().toString()); + jidentitykv.put("val", jidentity.toString()); jidentitykv.put("rev", lrevision); jupload.put(jidentitykv); } @@ -285,10 +292,10 @@ public class CloudSync { jrequest.put("items", jupload); call(context, user, password, "write", jrequest); - prefs.edit().putLong("cloud_revision", lrevision).apply(); + prefs.edit().putLong("cloud_lrevision", lrevision).apply(); } - private static void receiveRemoteData(Context context, String user, String password, long lrevision, JSONObject jstatus) + private static void receiveRemoteData(Context context, String user, String password, long lrevision, long rrevision, JSONObject jstatus) throws JSONException, GeneralSecurityException, IOException { DB db = DB.getInstance(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); @@ -333,23 +340,15 @@ public class CloudSync { EntityFolder left = null; if (jaccount.has("swipe_left_name") && !jaccount.isNull("swipe_left_name")) { left = new EntityFolder(); - left.account = null; left.name = jaccount.getString("swipe_left_name"); left.type = jaccount.getString("swipe_left_type"); - left.setProperties(); - //left.setSpecials(account); - //folder.id = db.folder().insertFolder(folder); } EntityFolder right = null; if (jaccount.has("swipe_right_name") && !jaccount.isNull("swipe_right_name")) { right = new EntityFolder(); - right.account = null; right.name = jaccount.getString("swipe_right_name"); right.type = jaccount.getString("swipe_right_type"); - right.setProperties(); - //right.setSpecials(account); - //folder.id = db.folder().insertFolder(folder); } Log.i("Cloud account " + raccount.uuid + "=" + @@ -362,6 +361,69 @@ public class CloudSync { " identities=" + jidentities + " size=" + value.length()); + raccount.id = null; + + try { + db.beginTransaction(); + + if (laccount == null) { + raccount.notify = false; // TODO + if (raccount.swipe_left != null && raccount.swipe_left > 0) + raccount.swipe_left = null; + if (raccount.swipe_right != null && raccount.swipe_right > 0) + raccount.swipe_right = null; + raccount.move_to = null; // TODO + raccount.id = db.account().insertAccount(raccount); + + if (raccount.protocol == EntityAccount.TYPE_POP) { + for (EntityFolder folder : EntityFolder.getPopFolders(context)) { + EntityFolder existing = db.folder().getFolderByType(raccount.id, folder.type); + if (existing == null) { + folder.account = raccount.id; + folder.id = db.folder().insertFolder(folder); + } + } + } else { + if (left != null) { + left.account = raccount.id; + left.setProperties(); + left.setSpecials(raccount); + left.id = db.folder().insertFolder(left); + } + + if (right != null) { + right.account = raccount.id; + right.setProperties(); + right.setSpecials(raccount); + right.id = db.folder().insertFolder(right); + } + + db.account().setAccountSwipes(raccount.id, + left == null ? null : left.id, + right == null ? null : right.id); + } + } else { + raccount.id = laccount.id; + raccount.notify = laccount.notify; // TODO + raccount.swipe_left = laccount.swipe_left; // TODO + raccount.swipe_right = laccount.swipe_right; // TODO + raccount.move_to = laccount.move_to; // TODO + db.account().updateAccount(raccount); + } + + if (raccount.id != null) { + if (raccount.primary) { + db.account().resetPrimary(); + db.account().setAccountPrimary(raccount.id, true); + } + db.account().setLastModified(raccount.id, rrevision); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + for (int j = 0; j < jidentities.length(); j++) { JSONObject jidentitykv = new JSONObject(); jidentitykv.put("key", "identity." + jidentities.getString(j)); @@ -393,15 +455,40 @@ public class CloudSync { ? "equal" : "update")) + " rev=" + revision + " size=" + value.length()); + + ridentity.id = null; + ridentity.primary = false; + ridentity.last_modified = rrevision; + + try { + db.beginTransaction(); + + if (lidentity == null) { + EntityAccount account = db.account().getAccountByUUID(jidentity.getString("account_uuid")); + if (account != null) { + ridentity.account = account.id; + ridentity.id = db.identity().insertIdentity(ridentity); + } + } else { + ridentity.id = lidentity.id; + db.identity().updateIdentity(ridentity); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } } } } - prefs.edit().putLong("cloud_revision", lrevision).apply(); + Log.i("Cloud set lrevision=" + rrevision); + prefs.edit().putLong("cloud_lrevision", rrevision).apply(); if (updates) ServiceSynchronize.reload(context, null, true, "sync"); } + // Lower level public static JSONObject call(Context context, String user, String password, String command, JSONObject jrequest) diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java index dcee7e352a..0537a18826 100644 --- a/app/src/main/java/eu/faircode/email/DB.java +++ b/app/src/main/java/eu/faircode/email/DB.java @@ -562,6 +562,7 @@ public abstract class DB extends RoomDatabase { " BEGIN" + " UPDATE account SET last_modified = strftime('%s') * 1000" + " WHERE id = NEW.id" + + " AND OLD.last_modified = NEW.last_modified" + " AND (NEW.auth_type = " + AUTH_TYPE_PASSWORD + " OR OLD.password = NEW.password)" + " AND OLD.keep_alive_ok IS NEW.keep_alive_ok" + " AND OLD.keep_alive_failed IS NEW.keep_alive_failed" + @@ -580,6 +581,7 @@ public abstract class DB extends RoomDatabase { " BEGIN" + " UPDATE identity SET last_modified = strftime('%s') * 1000" + " WHERE id = NEW.id" + + " AND OLD.last_modified = NEW.last_modified" + " AND OLD.state IS NEW.state" + " AND OLD.error IS NEW.error" + " AND OLD.last_connected IS NEW.last_connected" + diff --git a/app/src/main/java/eu/faircode/email/DaoAccount.java b/app/src/main/java/eu/faircode/email/DaoAccount.java index 11d1442bb2..2c20adea42 100644 --- a/app/src/main/java/eu/faircode/email/DaoAccount.java +++ b/app/src/main/java/eu/faircode/email/DaoAccount.java @@ -288,6 +288,11 @@ public interface DaoAccount { " AND (NOT (swipe_left IS :left) OR NOT (swipe_right IS :right))") int setAccountSwipes(long id, Long left, Long right); + @Query("UPDATE account" + + " SET last_modified = :last_modified" + + " WHERE id = :id") + int setLastModified(long id, Long last_modified); + @Query("UPDATE account SET `primary` = 0 WHERE NOT (`primary` IS 0)") void resetPrimary(); diff --git a/app/src/main/res/layout/fragment_options_backup.xml b/app/src/main/res/layout/fragment_options_backup.xml index 52463e301e..8ee0a33ded 100644 --- a/app/src/main/res/layout/fragment_options_backup.xml +++ b/app/src/main/res/layout/fragment_options_backup.xml @@ -303,13 +303,25 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvRegister" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cc37ac681b..6c4f2b792f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -983,6 +983,7 @@ Login Logging in for the first time will automatically create an account Invalid username or password + Only the data of enabled accounts will be synced and existing accounts on the device will never be deleted. Last sync: %1$s Logout Wipe cloud data on logout