diff --git a/app/src/main/java/eu/faircode/email/CloudSync.java b/app/src/main/java/eu/faircode/email/CloudSync.java index 011a5d2917..dd03c0591c 100644 --- a/app/src/main/java/eu/faircode/email/CloudSync.java +++ b/app/src/main/java/eu/faircode/email/CloudSync.java @@ -19,6 +19,8 @@ package eu.faircode.email; Copyright 2018-2023 by Marcel Bokhorst (M66B) */ +import static eu.faircode.email.ServiceAuthenticator.AUTH_TYPE_PASSWORD; + import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; @@ -72,12 +74,51 @@ public class CloudSync { if ("sync".equals(command)) { DB db = DB.getInstance(context); - long lrevision = prefs.getLong("sync_status", new Date().getTime()); - Log.i("Cloud sync status=" + lrevision); - for (EntitySync s : db.sync().getSync(null, null, Long.MAX_VALUE)) + long lrevision = prefs.getLong("sync_status", new Date().getTime()); + Log.i("Cloud local revision=" + lrevision + " (" + new Date(lrevision) + ")"); + + Long lastUpdate = null; + for (EntitySync s : db.sync().getSync(null, null, Long.MAX_VALUE)) { Log.i("Cloud sync " + s.entity + ":" + s.reference + " " + s.action + " " + new Date(s.time)); - db.sync().deleteSync(Long.MAX_VALUE); + if (s.reference == null) { + Log.w("Cloud reference missing"); + db.sync().deleteSync(s.id); + continue; + } + + if ("account".equals(s.entity) && "auth".equals(s.reference)) { + EntityAccount account = db.account().getAccountByUUID(s.reference); + if (account == null || account.auth_type != AUTH_TYPE_PASSWORD) { + if (account == null) + Log.w("Cloud account missing uuid=" + s.reference); + else + Log.w("Cloud account auth uuid=" + s.reference); + db.sync().deleteSync(s.id); + continue; + } + } + + if ("identity".equals(s.entity) && "auth".equals(s.reference)) { + EntityIdentity identity = db.identity().getIdentityByUUID(s.reference); + if (identity == null || identity.auth_type != AUTH_TYPE_PASSWORD) { + if (identity == null) + Log.w("Cloud identity missing uuid=" + s.reference); + else + Log.w("Cloud identity auth uuid=" + s.reference); + db.sync().deleteSync(s.id); + continue; + } + } + + if (lastUpdate == null || s.time > lastUpdate) + lastUpdate = s.time; + } + + Log.i("Cloud last update=" + (lastUpdate == null ? null : new Date(lastUpdate))); + + if (lastUpdate != null) + db.sync().deleteSyncByTime(lastUpdate); JSONObject jsyncstatus = new JSONObject(); jsyncstatus.put("key", "sync.status"); @@ -105,6 +146,8 @@ public class CloudSync { jrequest.put("items", jitems); call(context, user, password, command, jrequest); } + + prefs.edit().putLong("cloud_last_sync", new Date().getTime()).apply(); } private static void sendLocalData(Context context, String user, String password, long lrevision) throws JSONException, GeneralSecurityException, IOException { @@ -113,15 +156,17 @@ public class CloudSync { List accounts = db.account().getSynchronizingAccounts(null); Log.i("Cloud accounts=" + (accounts == null ? null : accounts.size())); - if (accounts == null || accounts.size() == 0) + if (accounts == null || accounts.size() == 0) { + Log.i("Cloud no accounts"); return; + } JSONArray jupload = new JSONArray(); - JSONArray jaccountuuids = new JSONArray(); + JSONArray jaccountuuidlist = new JSONArray(); for (EntityAccount account : accounts) if (!TextUtils.isEmpty(account.uuid)) { - jaccountuuids.put(account.uuid); + jaccountuuidlist.put(account.uuid); JSONArray jidentitieuuids = new JSONArray(); List identities = db.identity().getIdentities(account.id); @@ -148,15 +193,16 @@ public class CloudSync { jupload.put(jaccount); } - JSONObject jaccountuuidsholder = new JSONObject(); - jaccountuuidsholder.put("uuids", jaccountuuids); + JSONObject jaccountuuids = new JSONObject(); + jaccountuuids.put("uuids", jaccountuuidlist); - JSONObject jaccountstatus = new JSONObject(); - jaccountstatus.put("accounts", jaccountuuidsholder); + JSONObject jstatus = new JSONObject(); + jstatus.put("version", 1); + jstatus.put("accounts", jaccountuuids); JSONObject jsyncstatus = new JSONObject(); jsyncstatus.put("key", "sync.status"); - jsyncstatus.put("val", jaccountstatus.toString()); + jsyncstatus.put("val", jstatus.toString()); jsyncstatus.put("rev", lrevision); jupload.put(jsyncstatus); @@ -172,28 +218,31 @@ public class CloudSync { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); long rrevision = jsyncstatus.getLong("rev"); - Log.i("Cloud revision=" + lrevision + "/" + rrevision); + JSONObject jstatus = new JSONObject(jsyncstatus.getString("val")); + int version = jstatus.optInt("version", 0); + Log.i("Cloud version=" + version + " revision=" + lrevision + "/" + rrevision); - if (BuildConfig.DEBUG) + if (BuildConfig.DEBUG && false) lrevision--; - if (rrevision <= lrevision) - return; // no changes + if (rrevision <= lrevision) { + Log.i("Cloud no changes"); + return; + } // New revision JSONArray jdownload = new JSONArray(); // Get accounts - JSONObject jstatus = new JSONObject(jsyncstatus.getString("val")); JSONObject jaccountstatus = jstatus.getJSONObject("accounts"); - JSONArray jaccountuuids = jaccountstatus.getJSONArray("uuids"); - for (int i = 0; i < jaccountuuids.length(); i++) { - String uuid = jaccountuuids.getString(i); + JSONArray jaccountuuidlist = jaccountstatus.getJSONArray("uuids"); + for (int i = 0; i < jaccountuuidlist.length(); i++) { + String uuid = jaccountuuidlist.getString(i); JSONObject jaccount = new JSONObject(); jaccount.put("key", "account." + uuid); jaccount.put("rev", lrevision); jdownload.put(jaccount); - Log.i("Cloud account " + uuid); + Log.i("Cloud account uuid=" + uuid); } if (jdownload.length() > 0) { diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java index 82e9a6fca0..2cb8d623d5 100644 --- a/app/src/main/java/eu/faircode/email/DB.java +++ b/app/src/main/java/eu/faircode/email/DB.java @@ -485,12 +485,12 @@ public abstract class DB extends RoomDatabase { db.execSQL("DROP TRIGGER IF EXISTS `account_insert`"); db.execSQL("DROP TRIGGER IF EXISTS `account_update`"); - db.execSQL("DROP TRIGGER IF EXISTS `account_password`"); + db.execSQL("DROP TRIGGER IF EXISTS `account_auth`"); db.execSQL("DROP TRIGGER IF EXISTS `account_delete`"); db.execSQL("DROP TRIGGER IF EXISTS `identity_insert`"); db.execSQL("DROP TRIGGER IF EXISTS `identity_update`"); - db.execSQL("DROP TRIGGER IF EXISTS `identity_password`"); + db.execSQL("DROP TRIGGER IF EXISTS `identity_auth`"); db.execSQL("DROP TRIGGER IF EXISTS `identity_delete`"); } @@ -563,18 +563,20 @@ public abstract class DB extends RoomDatabase { db.execSQL("CREATE TRIGGER IF NOT EXISTS account_update" + " AFTER UPDATE" + - " OF host, encryption, insecure, port, auth_type, provider, `user`, certificate_alias, realm, fingerprint" + + " OF host, encryption, insecure, port, realm, fingerprint" + " ON account" + " BEGIN" + " INSERT INTO sync ('entity', 'reference', 'action', 'time')" + " VALUES ('account', NEW.uuid, 'update', strftime('%s') * 1000);" + " END"); - db.execSQL("CREATE TRIGGER IF NOT EXISTS account_password" + - " AFTER UPDATE OF password ON account" + + db.execSQL("CREATE TRIGGER IF NOT EXISTS account_auth" + + " AFTER UPDATE" + + " OF auth_type, provider, `user`, password, certificate_alias" + + " ON account" + " BEGIN" + " INSERT INTO sync ('entity', 'reference', 'action', 'time')" + - " VALUES ('account', NEW.uuid, 'password', strftime('%s') * 1000);" + + " VALUES ('account', NEW.uuid, 'auth', strftime('%s') * 1000);" + " END"); db.execSQL("CREATE TRIGGER IF NOT EXISTS account_delete" + @@ -593,18 +595,20 @@ public abstract class DB extends RoomDatabase { db.execSQL("CREATE TRIGGER IF NOT EXISTS identity_update" + " AFTER UPDATE" + - " OF host, encryption, insecure, port, auth_type, provider, `user`, certificate_alias, realm, fingerprint" + + " OF host, encryption, insecure, port, realm, fingerprint" + " ON identity" + " BEGIN" + " INSERT INTO sync ('entity', 'reference', 'action', 'time')" + " VALUES ('identity', NEW.uuid, 'update', strftime('%s') * 1000);" + " END"); - db.execSQL("CREATE TRIGGER IF NOT EXISTS identity_password" + - " AFTER UPDATE OF password ON identity" + + db.execSQL("CREATE TRIGGER IF NOT EXISTS identity_auth" + + " AFTER UPDATE" + + " OF auth_type, provider, `user`, password, certificate_alias" + + " ON identity" + " BEGIN" + " INSERT INTO sync ('entity', 'reference', 'action', 'time')" + - " VALUES ('identity', NEW.uuid, 'password', strftime('%s') * 1000);" + + " VALUES ('identity', NEW.uuid, 'auth', strftime('%s') * 1000);" + " END"); db.execSQL("CREATE TRIGGER IF NOT EXISTS identity_delete" + diff --git a/app/src/main/java/eu/faircode/email/DaoSync.java b/app/src/main/java/eu/faircode/email/DaoSync.java index 51eb7c7f7c..bcf18516bf 100644 --- a/app/src/main/java/eu/faircode/email/DaoSync.java +++ b/app/src/main/java/eu/faircode/email/DaoSync.java @@ -29,10 +29,13 @@ public interface DaoSync { @Query("SELECT * FROM sync" + " WHERE (:entity IS NULL OR entity = :entity)" + " AND (:reference IS NULL OR reference = :reference)" + - " AND time < :time" + + " AND time <= :time" + " ORDER BY time") List getSync(String entity, String reference, long time); - @Query("DELETE FROM sync WHERE time < :time") - int deleteSync(long time); + @Query("DELETE FROM sync WHERE id= :id") + int deleteSync(long id); + + @Query("DELETE FROM sync WHERE time <= :time") + int deleteSyncByTime(long time); } diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java b/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java index 373c214f00..e9f54a1547 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java @@ -274,7 +274,8 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (key == null || "cloud_user".equals(key) || - "cloud_password".equals(key)) { + "cloud_password".equals(key) || + "cloud_last_sync".equals(key)) { String user = prefs.getString("cloud_user", null); String password = prefs.getString("cloud_password", null); boolean auth = !(TextUtils.isEmpty(user) || TextUtils.isEmpty(password));