From f3b079d76e921c93f831eb7ebfb4042f2d7fa665 Mon Sep 17 00:00:00 2001 From: Yuku on yuku8 Date: Wed, 8 Jul 2015 16:17:20 +0800 Subject: [PATCH 01/17] Generalize sync ClientState class --- Alkitab/build.gradle | 4 ++-- .../alkitab/base/ac/SecretSyncDebugActivity.java | 8 ++++---- .../main/java/yuku/alkitab/base/sync/Sync.java | 10 ++++++++++ .../java/yuku/alkitab/base/sync/SyncAdapter.java | 16 ++++++++-------- .../yuku/alkitab/base/sync/Sync_History.java | 14 ++------------ .../java/yuku/alkitab/base/sync/Sync_Mabel.java | 14 ++------------ .../java/yuku/alkitab/base/sync/Sync_Pins.java | 14 ++------------ .../java/yuku/alkitab/base/sync/Sync_Rp.java | 14 ++------------ 8 files changed, 32 insertions(+), 62 deletions(-) diff --git a/Alkitab/build.gradle b/Alkitab/build.gradle index 1e53f0341..21f167d06 100644 --- a/Alkitab/build.gradle +++ b/Alkitab/build.gradle @@ -17,8 +17,8 @@ android { applicationId 'yuku.alkitab.debug' minSdkVersion 14 targetSdkVersion 22 - versionCode 14000231 - versionName '4.2.1' + versionCode 14000240 + versionName '4.3.0-beta0' } buildTypes { release { diff --git a/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java b/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java index 7d394c738..25168e575 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java @@ -94,8 +94,8 @@ protected void onCreate(Bundle savedInstanceState) { View.OnClickListener bMabelClientState_click = v -> { final StringBuilder sb = new StringBuilder(); - final Pair>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); - final Sync_Mabel.ClientState clientState = pair.first; + final Pair, List>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.first; sb.append("Base revno: ").append(clientState.base_revno).append('\n'); sb.append("Delta operations (size " + clientState.delta.operations.size() + "):\n"); @@ -205,8 +205,8 @@ void displayUser() { return; } - final Pair>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); - final Sync_Mabel.ClientState clientState = pair.first; + final Pair, List>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.first; final List> entitiesBeforeSync = pair.second; final RequestBody requestBody = new FormEncodingBuilder() diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java index 16653f31b..8bfb598fb 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java @@ -129,6 +129,16 @@ public int hashCode() { //endregion } + public static class ClientState { + public final int base_revno; + @NonNull public final Sync.Delta delta; + + public ClientState(final int base_revno, @NonNull final Sync.Delta delta) { + this.base_revno = base_revno; + this.delta = delta; + } + } + /** * Ignoring order, check if all the entities are the same. */ diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java index c9990b499..1120c4b0b 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java @@ -153,8 +153,8 @@ void syncMabel(final SyncResult sr) { } Log.d(TAG, "@@syncMabel step 10: gathering client state"); - final Pair>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); - final Sync_Mabel.ClientState clientState = pair.first; + final Pair, List>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.first; final List> entitiesBeforeSync = pair.second; SyncRecorder.log(SyncRecorder.EventKind.current_entities_gathered, syncSetName, "base_revno", clientState.base_revno, "client_delta_operations_size", clientState.delta.operations.size(), "client_entities_size", entitiesBeforeSync.size()); @@ -251,8 +251,8 @@ void syncHistory(final SyncResult sr) { } Log.d(TAG, "@@syncHistory step 10: gathering client state"); - final Pair>> pair = Sync_History.getClientStateAndCurrentEntities(); - final Sync_History.ClientState clientState = pair.first; + final Pair, List>> pair = Sync_History.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.first; final List> entitiesBeforeSync = pair.second; SyncRecorder.log(SyncRecorder.EventKind.current_entities_gathered, syncSetName, "base_revno", clientState.base_revno, "client_delta_operations_size", clientState.delta.operations.size(), "client_entities_size", entitiesBeforeSync.size()); @@ -344,8 +344,8 @@ void syncPins(final SyncResult sr) { } Log.d(TAG, "@@syncPins step 10: gathering client state"); - final Pair>> pair = Sync_Pins.getClientStateAndCurrentEntities(); - final Sync_Pins.ClientState clientState = pair.first; + final Pair, List>> pair = Sync_Pins.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.first; final List> entitiesBeforeSync = pair.second; SyncRecorder.log(SyncRecorder.EventKind.current_entities_gathered, syncSetName, "base_revno", clientState.base_revno, "client_delta_operations_size", clientState.delta.operations.size(), "client_entities_size", entitiesBeforeSync.size()); @@ -439,8 +439,8 @@ void syncRp(final SyncResult sr) { } Log.d(TAG, "@@syncRp step 10: gathering client state"); - final Pair>> pair = Sync_Rp.getClientStateAndCurrentEntities(); - final Sync_Rp.ClientState clientState = pair.first; + final Pair, List>> pair = Sync_Rp.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.first; final List> entitiesBeforeSync = pair.second; SyncRecorder.log(SyncRecorder.EventKind.current_entities_gathered, syncSetName, "base_revno", clientState.base_revno, "client_delta_operations_size", clientState.delta.operations.size(), "client_entities_size", entitiesBeforeSync.size()); diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java index fc964e7c0..2c31970c2 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java @@ -16,7 +16,7 @@ public class Sync_History { /** * @return base revno, delta of shadow -> current. */ - public static Pair>> getClientStateAndCurrentEntities() { + public static Pair, List>> getClientStateAndCurrentEntities() { final SyncShadow ss = S.getDb().getSyncShadowBySyncSetName(SyncShadow.SYNC_SET_HISTORY); final List> srcs = ss == null? Literals.List(): entitiesFromShadow(ss); @@ -45,7 +45,7 @@ public static Pair>> getClientStateAndCur } } - return Pair.create(new ClientState(ss == null ? 0 : ss.revno, delta), dsts); + return Pair.create(new Sync.ClientState<>(ss == null ? 0 : ss.revno, delta), dsts); } private static boolean isSameContent(final Sync.Entity a, final Sync.Entity b) { @@ -138,16 +138,6 @@ public static class SyncShadowDataJson { public List> entities; } - public static class ClientState { - public final int base_revno; - @NonNull public final Sync.Delta delta; - - public ClientState(final int base_revno, @NonNull final Sync.Delta delta) { - this.base_revno = base_revno; - this.delta = delta; - } - } - public static class SyncResponseJson extends Sync.ResponseJson { public int final_revno; public Sync.Delta append_delta; diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java index 9b0dae44e..3500293db 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java @@ -20,7 +20,7 @@ public class Sync_Mabel { /** * @return base revno, delta of shadow -> current. */ - public static Pair>> getClientStateAndCurrentEntities() { + public static Pair, List>> getClientStateAndCurrentEntities() { final SyncShadow ss = S.getDb().getSyncShadowBySyncSetName(SyncShadow.SYNC_SET_MABEL); final List> srcs = ss == null? Literals.List(): entitiesFromShadow(ss); @@ -49,7 +49,7 @@ public static Pair>> getClientStateAndCur } } - return Pair.create(new ClientState(ss == null ? 0 : ss.revno, delta), dsts); + return Pair.create(new Sync.ClientState<>(ss == null ? 0 : ss.revno, delta), dsts); } private static boolean isSameContent(final Sync.Entity a, final Sync.Entity b) { @@ -279,16 +279,6 @@ public static class SyncShadowDataJson { public List> entities; } - public static class ClientState { - public final int base_revno; - @NonNull public final Sync.Delta delta; - - public ClientState(final int base_revno, @NonNull final Sync.Delta delta) { - this.base_revno = base_revno; - this.delta = delta; - } - } - public static class SyncResponseJson extends Sync.ResponseJson { public int final_revno; public Sync.Delta append_delta; diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java index fcae6444b..503c9eec1 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java @@ -25,7 +25,7 @@ public class Sync_Pins { /** * @return base revno, delta of shadow -> current. */ - public static Pair>> getClientStateAndCurrentEntities() { + public static Pair, List>> getClientStateAndCurrentEntities() { final SyncShadow ss = S.getDb().getSyncShadowBySyncSetName(SyncShadow.SYNC_SET_PINS); final List> srcs = ss == null? Literals.List(): entitiesFromShadow(ss); @@ -54,7 +54,7 @@ public static Pair>> getClientStateAndCur } } - return Pair.create(new ClientState(ss == null ? 0 : ss.revno, delta), dsts); + return Pair.create(new Sync.ClientState<>(ss == null ? 0 : ss.revno, delta), dsts); } private static boolean isSameContent(final Sync.Entity a, final Sync.Entity b) { @@ -210,16 +210,6 @@ public static class SyncShadowDataJson { public List> entities; } - public static class ClientState { - public final int base_revno; - @NonNull public final Sync.Delta delta; - - public ClientState(final int base_revno, @NonNull final Sync.Delta delta) { - this.base_revno = base_revno; - this.delta = delta; - } - } - public static class SyncResponseJson extends Sync.ResponseJson { public int final_revno; public Sync.Delta append_delta; diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java index 2838b4e88..08990f23c 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java @@ -24,7 +24,7 @@ public class Sync_Rp { /** * @return base revno, delta of shadow -> current. */ - public static Pair>> getClientStateAndCurrentEntities() { + public static Pair, List>> getClientStateAndCurrentEntities() { final SyncShadow ss = S.getDb().getSyncShadowBySyncSetName(SyncShadow.SYNC_SET_RP); final List> srcs = ss == null? Literals.List(): entitiesFromShadow(ss); @@ -53,7 +53,7 @@ public static Pair>> getClientStateAndCur } } - return Pair.create(new ClientState(ss == null ? 0 : ss.revno, delta), dsts); + return Pair.create(new Sync.ClientState<>(ss == null ? 0 : ss.revno, delta), dsts); } private static boolean isSameContent(final Sync.Entity a, final Sync.Entity b) { @@ -179,16 +179,6 @@ public static class SyncShadowDataJson { public List> entities; } - public static class ClientState { - public final int base_revno; - @NonNull public final Sync.Delta delta; - - public ClientState(final int base_revno, @NonNull final Sync.Delta delta) { - this.base_revno = base_revno; - this.delta = delta; - } - } - public static class SyncResponseJson extends Sync.ResponseJson { public int final_revno; public Sync.Delta append_delta; From 05fd59c9fb9280a9bd98acab9a64215617d1fabc Mon Sep 17 00:00:00 2001 From: Yuku on yuku8 Date: Wed, 8 Jul 2015 16:35:17 +0800 Subject: [PATCH 02/17] Classes SyncShadowDataJson and SyncResponseJson generified --- .../alkitab/base/ac/SecretSyncDebugActivity.java | 3 ++- .../main/java/yuku/alkitab/base/sync/Sync.java | 13 +++++++++++-- .../java/yuku/alkitab/base/sync/SyncAdapter.java | 9 +++++---- .../yuku/alkitab/base/sync/Sync_History.java | 16 ++++------------ .../java/yuku/alkitab/base/sync/Sync_Mabel.java | 16 ++++------------ .../java/yuku/alkitab/base/sync/Sync_Pins.java | 16 ++++------------ .../java/yuku/alkitab/base/sync/Sync_Rp.java | 16 ++++------------ 7 files changed, 34 insertions(+), 55 deletions(-) diff --git a/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java b/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java index 25168e575..bd62ad2a3 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java @@ -8,6 +8,7 @@ import android.widget.EditText; import android.widget.TextView; import com.afollestad.materialdialogs.AlertDialogWrapper; +import com.google.gson.reflect.TypeToken; import com.squareup.okhttp.Call; import com.squareup.okhttp.Callback; import com.squareup.okhttp.FormEncodingBuilder; @@ -257,7 +258,7 @@ public void onFailure(final Request request, final IOException e) { @Override public void onResponse(final Response response) throws IOException { - final Sync_Mabel.SyncResponseJson debugSyncResponse = App.getDefaultGson().fromJson(response.body().charStream(), Sync_Mabel.SyncResponseJson.class); + final Sync.SyncResponseJson debugSyncResponse = App.getDefaultGson().fromJson(response.body().charStream(), new TypeToken>() {}.getType()); runOnUiThread(() -> { if (debugSyncResponse.success) { final int final_revno = debugSyncResponse.final_revno; diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java index 8bfb598fb..dba3678a9 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java @@ -131,14 +131,23 @@ public int hashCode() { public static class ClientState { public final int base_revno; - @NonNull public final Sync.Delta delta; + @NonNull public final Delta delta; - public ClientState(final int base_revno, @NonNull final Sync.Delta delta) { + public ClientState(final int base_revno, @NonNull final Delta delta) { this.base_revno = base_revno; this.delta = delta; } } + public static class SyncShadowDataJson { + public List> entities; + } + + public static class SyncResponseJson extends ResponseJson { + public int final_revno; + public Delta append_delta; + } + /** * Ignoring order, check if all the entities are the same. */ diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java index 1120c4b0b..74241ab23 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java @@ -11,6 +11,7 @@ import android.util.Pair; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; import com.squareup.okhttp.Call; import com.squareup.okhttp.FormEncodingBuilder; import com.squareup.okhttp.MultipartBuilder; @@ -184,7 +185,7 @@ void syncMabel(final SyncResult sr) { final long startTime = System.currentTimeMillis(); final String response_s = U.inputStreamUtf8ToString(call.execute().body().byteStream()); Log.d(TAG, "@@syncMabel server response string: " + response_s); - final Sync_Mabel.SyncResponseJson response = App.getDefaultGson().fromJson(response_s, Sync_Mabel.SyncResponseJson.class); + final Sync.SyncResponseJson response = App.getDefaultGson().fromJson(response_s, new TypeToken>() {}.getType()); SyncRecorder.log(SyncRecorder.EventKind.sync_to_server_post_response_ok, syncSetName, "duration_ms", System.currentTimeMillis() - startTime); if (!response.success) { @@ -281,7 +282,7 @@ void syncHistory(final SyncResult sr) { final long startTime = System.currentTimeMillis(); final String response_s = U.inputStreamUtf8ToString(call.execute().body().byteStream()); Log.d(TAG, "@@syncHistory server response string: " + response_s); - final Sync_History.SyncResponseJson response = App.getDefaultGson().fromJson(response_s, Sync_History.SyncResponseJson.class); + final Sync.SyncResponseJson response = App.getDefaultGson().fromJson(response_s, new TypeToken>() {}.getType()); SyncRecorder.log(SyncRecorder.EventKind.sync_to_server_post_response_ok, syncSetName, "duration_ms", System.currentTimeMillis() - startTime); if (!response.success) { @@ -374,7 +375,7 @@ void syncPins(final SyncResult sr) { final long startTime = System.currentTimeMillis(); final String response_s = U.inputStreamUtf8ToString(call.execute().body().byteStream()); Log.d(TAG, "@@syncPins server response string: " + response_s); - final Sync_Pins.SyncResponseJson response = App.getDefaultGson().fromJson(response_s, Sync_Pins.SyncResponseJson.class); + final Sync.SyncResponseJson response = App.getDefaultGson().fromJson(response_s, new TypeToken>() {}.getType()); SyncRecorder.log(SyncRecorder.EventKind.sync_to_server_post_response_ok, syncSetName, "duration_ms", System.currentTimeMillis() - startTime); if (!response.success) { @@ -469,7 +470,7 @@ void syncRp(final SyncResult sr) { final long startTime = System.currentTimeMillis(); final String response_s = U.inputStreamUtf8ToString(call.execute().body().byteStream()); Log.d(TAG, "@@syncRp server response string: " + response_s); - final Sync_Rp.SyncResponseJson response = App.getDefaultGson().fromJson(response_s, Sync_Rp.SyncResponseJson.class); + final Sync.SyncResponseJson response = App.getDefaultGson().fromJson(response_s, new TypeToken>() {}.getType()); SyncRecorder.log(SyncRecorder.EventKind.sync_to_server_post_response_ok, syncSetName, "duration_ms", System.currentTimeMillis() - startTime); if (!response.success) { diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java index 2c31970c2..3f7cd8ef8 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java @@ -2,6 +2,7 @@ import android.support.annotation.NonNull; import android.util.Pair; +import com.google.gson.reflect.TypeToken; import yuku.alkitab.base.App; import yuku.alkitab.base.S; import yuku.alkitab.base.U; @@ -65,14 +66,14 @@ private static Sync.Entity findEntity(final List> } private static List> entitiesFromShadow(@NonNull final SyncShadow ss) { - final SyncShadowDataJson data = App.getDefaultGson().fromJson(U.utf8BytesToString(ss.data), SyncShadowDataJson.class); + final Sync.SyncShadowDataJson data = App.getDefaultGson().fromJson(U.utf8BytesToString(ss.data), new TypeToken>() {}.getType()); return data.entities; } @NonNull public static SyncShadow shadowFromEntities(@NonNull final List> entities, final int revno) { - final SyncShadowDataJson data = new SyncShadowDataJson(); + final Sync.SyncShadowDataJson data = new Sync.SyncShadowDataJson<>(); data.entities = entities; - final String s = App.getDefaultGson().toJson(data); + final String s = App.getDefaultGson().toJson(data, new TypeToken>() {}.getType()); final SyncShadow res = new SyncShadow(); res.data = U.stringToUtf8Bytes(s); res.syncSetName = SyncShadow.SYNC_SET_HISTORY; @@ -133,13 +134,4 @@ public String toString() { '}'; } } - - public static class SyncShadowDataJson { - public List> entities; - } - - public static class SyncResponseJson extends Sync.ResponseJson { - public int final_revno; - public Sync.Delta append_delta; - } } diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java index 3500293db..03fe1224b 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java @@ -3,6 +3,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Pair; +import com.google.gson.reflect.TypeToken; import yuku.alkitab.base.App; import yuku.alkitab.base.S; import yuku.alkitab.base.U; @@ -69,14 +70,14 @@ private static Sync.Entity findEntity(final List> } private static List> entitiesFromShadow(@NonNull final SyncShadow ss) { - final SyncShadowDataJson data = App.getDefaultGson().fromJson(U.utf8BytesToString(ss.data), SyncShadowDataJson.class); + final Sync.SyncShadowDataJson data = App.getDefaultGson().fromJson(U.utf8BytesToString(ss.data), new TypeToken>() {}.getType()); return data.entities; } @NonNull public static SyncShadow shadowFromEntities(@NonNull final List> entities, final int revno) { - final SyncShadowDataJson data = new SyncShadowDataJson(); + final Sync.SyncShadowDataJson data = new Sync.SyncShadowDataJson<>(); data.entities = entities; - final String s = App.getDefaultGson().toJson(data); + final String s = App.getDefaultGson().toJson(data, new TypeToken>() {}.getType()); final SyncShadow res = new SyncShadow(); res.data = U.stringToUtf8Bytes(s); res.syncSetName = SyncShadow.SYNC_SET_MABEL; @@ -274,13 +275,4 @@ static String q(@NonNull String s) { return "'" + c + "'"; } } - - public static class SyncShadowDataJson { - public List> entities; - } - - public static class SyncResponseJson extends Sync.ResponseJson { - public int final_revno; - public Sync.Delta append_delta; - } } diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java index 503c9eec1..ef2788dee 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java @@ -2,6 +2,7 @@ import android.support.annotation.NonNull; import android.util.Pair; +import com.google.gson.reflect.TypeToken; import yuku.alkitab.base.App; import yuku.alkitab.base.S; import yuku.alkitab.base.U; @@ -74,14 +75,14 @@ private static Sync.Entity findEntity(final List> } private static List> entitiesFromShadow(@NonNull final SyncShadow ss) { - final SyncShadowDataJson data = App.getDefaultGson().fromJson(U.utf8BytesToString(ss.data), SyncShadowDataJson.class); + final Sync.SyncShadowDataJson data = App.getDefaultGson().fromJson(U.utf8BytesToString(ss.data), new TypeToken>() {}.getType()); return data.entities; } @NonNull public static SyncShadow shadowFromEntities(@NonNull final List> entities, final int revno) { - final SyncShadowDataJson data = new SyncShadowDataJson(); + final Sync.SyncShadowDataJson data = new Sync.SyncShadowDataJson<>(); data.entities = entities; - final String s = App.getDefaultGson().toJson(data); + final String s = App.getDefaultGson().toJson(data, new TypeToken>() {}.getType()); final SyncShadow res = new SyncShadow(); res.data = U.stringToUtf8Bytes(s); res.syncSetName = SyncShadow.SYNC_SET_PINS; @@ -205,13 +206,4 @@ public String toString() { } } } - - public static class SyncShadowDataJson { - public List> entities; - } - - public static class SyncResponseJson extends Sync.ResponseJson { - public int final_revno; - public Sync.Delta append_delta; - } } diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java index 08990f23c..1e7cf9d3f 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java @@ -2,6 +2,7 @@ import android.support.annotation.NonNull; import android.util.Pair; +import com.google.gson.reflect.TypeToken; import gnu.trove.map.hash.TObjectLongHashMap; import gnu.trove.set.TIntSet; import yuku.alkitab.base.App; @@ -73,14 +74,14 @@ private static Sync.Entity findEntity(final List> } private static List> entitiesFromShadow(@NonNull final SyncShadow ss) { - final SyncShadowDataJson data = App.getDefaultGson().fromJson(U.utf8BytesToString(ss.data), SyncShadowDataJson.class); + final Sync.SyncShadowDataJson data = App.getDefaultGson().fromJson(U.utf8BytesToString(ss.data), new TypeToken>() {}.getType()); return data.entities; } @NonNull public static SyncShadow shadowFromEntities(@NonNull final List> entities, final int revno) { - final SyncShadowDataJson data = new SyncShadowDataJson(); + final Sync.SyncShadowDataJson data = new Sync.SyncShadowDataJson<>(); data.entities = entities; - final String s = App.getDefaultGson().toJson(data); + final String s = App.getDefaultGson().toJson(data, new TypeToken>() {}.getType()); final SyncShadow res = new SyncShadow(); res.data = U.stringToUtf8Bytes(s); res.syncSetName = SyncShadow.SYNC_SET_RP; @@ -174,13 +175,4 @@ public String toString() { '}'; } } - - public static class SyncShadowDataJson { - public List> entities; - } - - public static class SyncResponseJson extends Sync.ResponseJson { - public int final_revno; - public Sync.Delta append_delta; - } } From b7af46bdb2c1254ac1f6d7f089b977b5b5603dda Mon Sep 17 00:00:00 2001 From: Yuku on yuku8 Date: Thu, 9 Jul 2015 13:32:47 +0800 Subject: [PATCH 03/17] make Sync.Entity a immutable class --- .../java/yuku/alkitab/base/sync/Sync.java | 12 ++++++++--- .../yuku/alkitab/base/sync/Sync_History.java | 7 +++---- .../yuku/alkitab/base/sync/Sync_Mabel.java | 21 ++++++++----------- .../yuku/alkitab/base/sync/Sync_Pins.java | 6 ++---- .../java/yuku/alkitab/base/sync/Sync_Rp.java | 17 +++++++-------- 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java index dba3678a9..2e24a4b3d 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java @@ -100,9 +100,15 @@ public static class Entity { /** * Kind of this entity. One of the KIND_ constants on {@link yuku.alkitab.base.sync.Sync.Entity}. */ - public String kind; - public String gid; - public C content; + public final String kind; + public final String gid; + public final C content; + + public Entity(final String kind, final String gid, final C content) { + this.kind = kind; + this.gid = gid; + this.content = content; + } //region Boilerplate equals and hashCode @Override diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java index 3f7cd8ef8..4df72157f 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_History.java @@ -85,12 +85,11 @@ private static List> entitiesFromShadow(@NonNull final Sync final List> res = new ArrayList<>(); for (final History.HistoryEntry entry: History.getInstance().listAllEntries()) { - final Sync.Entity entity = new Sync.Entity<>(); - entity.kind = Sync.Entity.KIND_HISTORY_ENTRY; - entity.gid = entry.gid; - final Content content = entity.content = new Content(); + final Content content = new Content(); content.ari = entry.ari; content.timestamp = entry.timestamp; + + final Sync.Entity entity = new Sync.Entity<>(Sync.Entity.KIND_HISTORY_ENTRY, entry.gid, content); res.add(entity); } diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java index 03fe1224b..fa95c771b 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java @@ -90,41 +90,38 @@ private static List> entitiesFromShadow(@NonNull final Sync { // markers for (final Marker marker : S.getDb().listAllMarkers()) { - final Sync.Entity entity = new Sync.Entity<>(); - entity.kind = Sync.Entity.KIND_MARKER; - entity.gid = marker.gid; - final Content content = entity.content = new Content(); + final Content content = new Content(); content.ari = marker.ari; content.caption = marker.caption; content.kind = marker.kind.code; content.verseCount = marker.verseCount; content.createTime = Sqlitil.toInt(marker.createTime); content.modifyTime = Sqlitil.toInt(marker.modifyTime); + + final Sync.Entity entity = new Sync.Entity<>(Sync.Entity.KIND_MARKER, marker.gid, content); res.add(entity); } } { // labels for (final Label label : S.getDb().listAllLabels()) { - final Sync.Entity entity = new Sync.Entity<>(); - entity.kind = Sync.Entity.KIND_LABEL; - entity.gid = label.gid; - final Content content = entity.content = new Content(); + final Content content = new Content(); content.title = label.title; content.backgroundColor = label.backgroundColor; content.ordering = label.ordering; + + final Sync.Entity entity = new Sync.Entity<>(Sync.Entity.KIND_LABEL, label.gid, content); res.add(entity); } } { // marker_labels for (final Marker_Label marker_label : S.getDb().listAllMarker_Labels()) { - final Sync.Entity entity = new Sync.Entity<>(); - entity.kind = Sync.Entity.KIND_MARKER_LABEL; - entity.gid = marker_label.gid; - final Content content = entity.content = new Content(); + final Content content = new Content(); content.marker_gid = marker_label.marker_gid; content.label_gid = marker_label.label_gid; + + final Sync.Entity entity = new Sync.Entity<>(Sync.Entity.KIND_MARKER_LABEL, marker_label.gid, content); res.add(entity); } } diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java index ef2788dee..01758b144 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Pins.java @@ -93,10 +93,7 @@ private static List> entitiesFromShadow(@NonNull final Sync @NonNull public static List> getEntitiesFromCurrent() { final List> res = new ArrayList<>(); - final Sync.Entity entity = new Sync.Entity<>(); - entity.kind = Sync.Entity.KIND_PINS; - entity.gid = GID_SPECIAL_PINS; - final Content content = entity.content = new Content(); + final Content content = new Content(); final List pins = content.pins = new ArrayList<>(); for (int preset_id = 0; preset_id < AttributeView.PROGRESS_MARK_TOTAL_COUNT; preset_id++) { @@ -111,6 +108,7 @@ private static List> entitiesFromShadow(@NonNull final Sync pins.add(pin); } + final Sync.Entity entity = new Sync.Entity<>(Sync.Entity.KIND_PINS, GID_SPECIAL_PINS, content); res.add(entity); return res; diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java index 1e7cf9d3f..3cd4b277b 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Rp.java @@ -106,31 +106,28 @@ private static List> entitiesFromShadow(@NonNull final Sync for (final Map.Entry e : map.entrySet()) { final String gid = e.getKey(); - final Sync.Entity entity = new Sync.Entity<>(); - entity.kind = Sync.Entity.KIND_RP_PROGRESS; - entity.gid = gid; - - final Content content = entity.content = new Content(); + final Content content = new Content(); content.startTime = startTimes.containsKey(gid)? startTimes.get(gid): null; + final TIntSet set = e.getValue(); final Set done = content.done = new LinkedHashSet<>(set.size()); set.forEach(value -> { done.add(value); return true; }); + + final Sync.Entity entity = new Sync.Entity<>(Sync.Entity.KIND_RP_PROGRESS, gid, content); res.add(entity); } // add remaining reading plans without any done startTimes.forEachEntry((gid, startTime) -> { if (!map.containsKey(gid)) { - final Sync.Entity entity = new Sync.Entity<>(); - entity.kind = Sync.Entity.KIND_RP_PROGRESS; - entity.gid = gid; - - final Content content = entity.content = new Content(); + final Content content = new Content(); content.startTime = startTime; content.done = new LinkedHashSet<>(); + + final Sync.Entity entity = new Sync.Entity<>(Sync.Entity.KIND_RP_PROGRESS, gid, content); res.add(entity); } return true; From 1fe6428522583a136f4c39f8d5c4ab36415057d8 Mon Sep 17 00:00:00 2001 From: Yuku on yuku8 Date: Thu, 9 Jul 2015 13:33:09 +0800 Subject: [PATCH 04/17] Remove irrelevant comment --- Alkitab/src/main/java/yuku/alkitab/base/util/History.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Alkitab/src/main/java/yuku/alkitab/base/util/History.java b/Alkitab/src/main/java/yuku/alkitab/base/util/History.java index c5f1399c9..877d1d92a 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/util/History.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/util/History.java @@ -123,7 +123,6 @@ public List listAllEntries() { /** * Makes the current history updated with patches (append delta) from server. * Also updates the shadow (both data and the revno). - * TODO merge with {@link yuku.alkitab.base.storage.InternalDb#applyMabelAppendDelta(int, yuku.alkitab.base.sync.Sync.Delta, java.util.List, String)} * @return {@link yuku.alkitab.base.sync.Sync.ApplyAppendDeltaResult#ok} if history and sync shadow are updated. Otherwise else. */ @NonNull public Sync.ApplyAppendDeltaResult applyHistoryAppendDelta(final int final_revno, @NonNull final Sync.Delta append_delta, @NonNull final List> entitiesBeforeSync, @NonNull final String simpleTokenBeforeSync) { From 86df7088699b6053beead44cd1c94c5b27cd5223 Mon Sep 17 00:00:00 2001 From: Yuku on yuku8 Date: Thu, 9 Jul 2015 15:55:02 +0800 Subject: [PATCH 05/17] Implement partial sync for syncset mabel --- .../base/ac/SecretSyncDebugActivity.java | 13 +- .../yuku/alkitab/base/storage/InternalDb.java | 13 +- .../java/yuku/alkitab/base/sync/Sync.java | 15 +++ .../yuku/alkitab/base/sync/SyncAdapter.java | 114 +++++++++++++++++- .../yuku/alkitab/base/sync/SyncRecorder.java | 1 + .../yuku/alkitab/base/sync/Sync_Mabel.java | 8 +- 6 files changed, 144 insertions(+), 20 deletions(-) diff --git a/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java b/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java index bd62ad2a3..4429771da 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java @@ -2,7 +2,6 @@ import android.content.Intent; import android.os.Bundle; -import android.util.Pair; import android.view.View; import android.widget.CheckBox; import android.widget.EditText; @@ -95,8 +94,8 @@ protected void onCreate(Bundle savedInstanceState) { View.OnClickListener bMabelClientState_click = v -> { final StringBuilder sb = new StringBuilder(); - final Pair, List>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); - final Sync.ClientState clientState = pair.first; + final Sync.GetClientStateResult pair = Sync_Mabel.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.clientState; sb.append("Base revno: ").append(clientState.base_revno).append('\n'); sb.append("Delta operations (size " + clientState.delta.operations.size() + "):\n"); @@ -206,9 +205,9 @@ void displayUser() { return; } - final Pair, List>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); - final Sync.ClientState clientState = pair.first; - final List> entitiesBeforeSync = pair.second; + final Sync.GetClientStateResult pair = Sync_Mabel.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.clientState; + final List> entitiesBeforeSync = pair.currentEntities; final RequestBody requestBody = new FormEncodingBuilder() .add("simpleToken", simpleToken) @@ -264,7 +263,7 @@ public void onResponse(final Response response) throws IOException { final int final_revno = debugSyncResponse.final_revno; final Sync.Delta append_delta = debugSyncResponse.append_delta; - final Sync.ApplyAppendDeltaResult applyResult = S.getDb().applyMabelAppendDelta(final_revno, append_delta, entitiesBeforeSync, simpleToken); + final Sync.ApplyAppendDeltaResult applyResult = S.getDb().applyMabelAppendDelta(final_revno, pair.shadowEntities, clientState, append_delta, entitiesBeforeSync, simpleToken); new AlertDialogWrapper.Builder(SecretSyncDebugActivity.this) .setMessage("Final revno: " + final_revno + "\nApply result: " + applyResult + "\nAppend delta: " + append_delta) .setPositiveButton(R.string.ok, null) diff --git a/Alkitab/src/main/java/yuku/alkitab/base/storage/InternalDb.java b/Alkitab/src/main/java/yuku/alkitab/base/storage/InternalDb.java index 6b6671710..32e8a7f10 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/storage/InternalDb.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/storage/InternalDb.java @@ -31,6 +31,7 @@ import yuku.alkitab.base.model.SyncLog; import yuku.alkitab.base.model.SyncShadow; import yuku.alkitab.base.sync.Sync; +import yuku.alkitab.base.sync.SyncAdapter; import yuku.alkitab.base.sync.SyncRecorder; import yuku.alkitab.base.sync.Sync_Mabel; import yuku.alkitab.base.sync.Sync_Pins; @@ -1464,7 +1465,7 @@ public void deleteSyncShadowBySyncSetName(final String syncSetName) { * Also updates the shadow (both data and the revno). * @return {@link yuku.alkitab.base.sync.Sync.ApplyAppendDeltaResult#ok} if database and sync shadow are updated. Otherwise else. */ - @NonNull public Sync.ApplyAppendDeltaResult applyMabelAppendDelta(final int final_revno, @NonNull final Sync.Delta append_delta, @NonNull final List> entitiesBeforeSync, @NonNull final String simpleTokenBeforeSync) { + @NonNull public Sync.ApplyAppendDeltaResult applyMabelAppendDelta(final int final_revno, final List> shadowEntities, final Sync.ClientState clientState, @NonNull final Sync.Delta append_delta, @NonNull final List> entitiesBeforeSync, @NonNull final String simpleTokenBeforeSync) { final SQLiteDatabase db = helper.getWritableDatabase(); db.beginTransaction(); Sync.notifySyncUpdatesOngoing(SyncShadow.SYNC_SET_MABEL, true); @@ -1483,6 +1484,7 @@ public void deleteSyncShadowBySyncSetName(final String syncSetName) { } } + // apply changes, which is server append delta, to current entities for (final Sync.Operation o : append_delta.operations) { switch (o.opkind) { case del: @@ -1525,8 +1527,13 @@ public void deleteSyncShadowBySyncSetName(final String syncSetName) { } } - // if we reach here, the local database has been updated with the append delta. - final SyncShadow ss = Sync_Mabel.shadowFromEntities(Sync_Mabel.getEntitiesFromCurrent(), final_revno); + // if we reach here, the current entities has been updated with the append delta. + + // apply changes, which are client delta, and server append delta, to shadow entities + final List> shadowEntitiesPatched1 = SyncAdapter.patchNoConflict(shadowEntities, clientState.delta.operations); + final List> shadowEntitiesPatched2 = SyncAdapter.patchNoConflict(shadowEntitiesPatched1, append_delta.operations); + + final SyncShadow ss = Sync_Mabel.shadowFromEntities(shadowEntitiesPatched2, final_revno); insertOrUpdateSyncShadowBySyncSetName(ss); db.setTransactionSuccessful(); diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java index 2e24a4b3d..e13d94e78 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java @@ -7,6 +7,7 @@ import android.support.annotation.NonNull; import android.support.v4.util.ArrayMap; import android.util.Log; +import android.util.Pair; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.squareup.okhttp.Call; @@ -25,7 +26,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -154,6 +157,18 @@ public static class SyncResponseJson extends ResponseJson { public Delta append_delta; } + public static class GetClientStateResult { + public final ClientState clientState; + public final List> shadowEntities; + public final List> currentEntities; + + public GetClientStateResult(final ClientState clientState, final List> shadowEntities, final List> currentEntities) { + this.clientState = clientState; + this.shadowEntities = shadowEntities; + this.currentEntities = currentEntities; + } + } + /** * Ignoring order, check if all the entities are the same. */ diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java index 74241ab23..a44a59bf3 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java @@ -31,8 +31,11 @@ import yuku.alkitab.base.util.Sqlitil; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.Stack; @@ -43,6 +46,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter { static final String TAG = SyncAdapter.class.getSimpleName(); public static final String EXTRA_SYNC_SET_NAME = "syncSetName"; + private static final int PARTIAL_SYNC_THRESHOLD = 100; final static Stack syncSetsRunning = new Stack<>(); // need guard when accessing this @@ -62,6 +66,55 @@ public SyncAdapter(Context context, boolean autoInitialize, boolean allowParalle super(context, autoInitialize, allowParallelSyncs); } + /** + * Patches entities with operations, returning new set of entities. + * Add and mod overwrites without merge, del deletes immediately + * @param entities before patch + * @param operations patch in temporal order (old first then new) + * @param type of content + * @return after patch + */ + public static List> patchNoConflict(final List> entities, final List> operations) { + /* + Ported from server code: + + def patchNoConflict(entities, operations): + # convert to map for faster lookup + entities_map = { + (e.gid, e.kind): e + for e in entities + } + + for o in operations: + if o.opkind == 'del': + del entities_map[(o.gid, o.kind)] + else: # add and mod are treated the same: just overwrite! + entities_map[(o.gid, o.kind)] = Entity(o.kind, o.gid, content=o.content, creator_id=o.creator_id) + + return entities_map.values() + + */ + + final Map, Sync.Entity> entities_map = new HashMap<>(); + for (final Sync.Entity entity : entities) { + entities_map.put(Pair.create(entity.gid, entity.kind), entity); + } + + for (final Sync.Operation o : operations) { + switch (o.opkind) { + case del: + entities_map.remove(Pair.create(o.gid, o.kind)); + break; + case add: + case mod: + entities_map.put(Pair.create(o.gid, o.kind), new Sync.Entity(o.kind, o.gid, o.content)); + break; + } + } + + return new ArrayList<>(entities_map.values()); + } + /* * Specify the code you want to run in the sync adapter. The entire * sync adapter runs in a background thread, so you don't have to set @@ -143,6 +196,56 @@ static void fillInStatsFromAppendDelta(final Sync.Delta append_delta, fin } } + /** + * If the client state's delta is too big, we remove some of the changes, so only some changes are transmitted to the server, + * and the server will not time out anymore. + * + * The selection of the changes follow these rules: + * - all operations with opkind {@link yuku.alkitab.base.sync.Sync.Opkind#mod} and {@link yuku.alkitab.base.sync.Sync.Opkind#del} + * must be included in the selection + * - if the number of operations are still less than a certain threshold, operations with opkind + * {@link yuku.alkitab.base.sync.Sync.Opkind#add} are also included until the certain threshold is reached. + * + * WARNING: If you are using partial sync by this method, do not create sync shadow from the current state, but you must + * create it from an existing sync shadow by applying the client delta AND the append delta (given from the server). + * Also, the current state must be updated using the append delta from the server. + * + * @param clientState will be modified + */ + static void chopClientState(final Sync.ClientState clientState, final String syncSetName) { + final List> src = clientState.delta.operations; + + // fast path if the operation count is below threshold + if (src.size() <= PARTIAL_SYNC_THRESHOLD) { + return; + } + + final List> dst = new ArrayList<>(); + + // insert all mod and del operations + for (final Sync.Operation o : src) { + if (o.opkind == Sync.Opkind.mod || o.opkind == Sync.Opkind.del) { + dst.add(o); + } + } + + // if we are still ok, add operations too + for (final Sync.Operation o : src) { + if (dst.size() >= PARTIAL_SYNC_THRESHOLD) { + break; + } + + if (o.opkind == Sync.Opkind.add) { + dst.add(o); + } + } + + SyncRecorder.log(SyncRecorder.EventKind.partial_sync_info, syncSetName, "client_delta_operations_size_original", src.size(), "client_delta_operations_size_chopped", dst.size()); + + src.clear(); + src.addAll(dst); + } + void syncMabel(final SyncResult sr) { final String syncSetName = SyncShadow.SYNC_SET_MABEL; @@ -154,12 +257,15 @@ void syncMabel(final SyncResult sr) { } Log.d(TAG, "@@syncMabel step 10: gathering client state"); - final Pair, List>> pair = Sync_Mabel.getClientStateAndCurrentEntities(); - final Sync.ClientState clientState = pair.first; - final List> entitiesBeforeSync = pair.second; + final Sync.GetClientStateResult pair = Sync_Mabel.getClientStateAndCurrentEntities(); + final Sync.ClientState clientState = pair.clientState; + final List> shadowEntities = pair.shadowEntities; + final List> entitiesBeforeSync = pair.currentEntities; SyncRecorder.log(SyncRecorder.EventKind.current_entities_gathered, syncSetName, "base_revno", clientState.base_revno, "client_delta_operations_size", clientState.delta.operations.size(), "client_entities_size", entitiesBeforeSync.size()); + chopClientState(clientState, syncSetName); + final String serverPrefix = Sync.getEffectiveServerPrefix(); Log.d(TAG, "@@syncMabel step 20: building http request. Server prefix: " + serverPrefix); final RequestBody requestBody = new MultipartBuilder() @@ -207,7 +313,7 @@ void syncMabel(final SyncResult sr) { SyncRecorder.log(SyncRecorder.EventKind.sync_to_server_got_success_data, syncSetName, "final_revno", final_revno, "append_delta_operations_size", append_delta.operations.size()); - final Sync.ApplyAppendDeltaResult applyResult = S.getDb().applyMabelAppendDelta(final_revno, append_delta, entitiesBeforeSync, simpleToken); + final Sync.ApplyAppendDeltaResult applyResult = S.getDb().applyMabelAppendDelta(final_revno, shadowEntities, clientState, append_delta, entitiesBeforeSync, simpleToken); SyncRecorder.log(SyncRecorder.EventKind.apply_result, syncSetName, "apply_result", applyResult.name()); diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncRecorder.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncRecorder.java index 0ee089808..894194740 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncRecorder.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncRecorder.java @@ -40,6 +40,7 @@ public enum EventKind { sync_needed_notified(81, INFO), sync_adapter_on_perform(82, INFO), sync_adapter_set_not_enabled(83, INFO), + partial_sync_info(84, INFO), error_no_simple_token(100, ERROR), current_entities_gathered(101, INFO), sync_to_server_pre(102, INFO), diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java index fa95c771b..4e0d002c2 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync_Mabel.java @@ -2,7 +2,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Pair; import com.google.gson.reflect.TypeToken; import yuku.alkitab.base.App; import yuku.alkitab.base.S; @@ -18,10 +17,7 @@ import java.util.List; public class Sync_Mabel { - /** - * @return base revno, delta of shadow -> current. - */ - public static Pair, List>> getClientStateAndCurrentEntities() { + public static Sync.GetClientStateResult getClientStateAndCurrentEntities() { final SyncShadow ss = S.getDb().getSyncShadowBySyncSetName(SyncShadow.SYNC_SET_MABEL); final List> srcs = ss == null? Literals.List(): entitiesFromShadow(ss); @@ -50,7 +46,7 @@ public static Pair, List>> getCli } } - return Pair.create(new Sync.ClientState<>(ss == null ? 0 : ss.revno, delta), dsts); + return new Sync.GetClientStateResult<>(new Sync.ClientState<>(ss == null ? 0 : ss.revno, delta), srcs, dsts); } private static boolean isSameContent(final Sync.Entity a, final Sync.Entity b) { From 90730b81121cc8fb7a8f612da9b77dbc277e1e94 Mon Sep 17 00:00:00 2001 From: Yuku on yuku8 Date: Thu, 9 Jul 2015 17:59:16 +0800 Subject: [PATCH 06/17] notify sync now can specify multiple sync set names --- .../java/yuku/alkitab/base/IsiActivity.java | 4 +- .../alkitab/base/sync/GcmIntentService.java | 4 +- .../java/yuku/alkitab/base/sync/Sync.java | 71 ++++++++++--------- .../yuku/alkitab/base/sync/SyncAdapter.java | 13 ++-- 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/Alkitab/src/main/java/yuku/alkitab/base/IsiActivity.java b/Alkitab/src/main/java/yuku/alkitab/base/IsiActivity.java index 771394972..27263c625 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/IsiActivity.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/IsiActivity.java @@ -558,9 +558,7 @@ public void onReceive(final Context context, final Intent intent) { // sync on app start, if we are logged in if (Preferences.contains(Prefkey.sync_simpleToken)) { - for (final String syncSetName : SyncShadow.ALL_SYNC_SET_NAMES) { - Sync.notifySyncNeeded(syncSetName); - } + Sync.notifySyncNeeded(SyncShadow.ALL_SYNC_SET_NAMES); } if (!U.equals(getPackageName(), "yuku.alkitab") /* prevent self-import */ diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/GcmIntentService.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/GcmIntentService.java index 916e9c5b6..b373c4f09 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/GcmIntentService.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/GcmIntentService.java @@ -58,9 +58,7 @@ private void handle(final Intent intent) { final GcmMessageEncodedDataJson data = App.getDefaultGson().fromJson(encoded_data, GcmMessageEncodedDataJson.class); if ("sync".equals(data.kind)) { if (data.syncSetNames != null) { - for (final String syncSetName : data.syncSetNames) { - Sync.notifySyncNeeded(syncSetName); - } + Sync.notifySyncNeeded(data.syncSetNames.toArray(new String[data.syncSetNames.size()])); } } } diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java index e13d94e78..cf1e832ae 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/Sync.java @@ -7,7 +7,6 @@ import android.support.annotation.NonNull; import android.support.v4.util.ArrayMap; import android.util.Log; -import android.util.Pair; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.squareup.okhttp.Call; @@ -26,9 +25,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -191,45 +188,52 @@ public static boolean entitiesEqual(@NonNull final List> a_, @NonN /** * Notify that we need to sync with server. - * @param syncSetName The name of the sync set that needs sync with server. Should be {@link yuku.alkitab.base.model.SyncShadow#SYNC_SET_MABEL} or others. + * @param syncSetNames The names of the sync set that needs sync with server. Should be list of {@link yuku.alkitab.base.model.SyncShadow#SYNC_SET_MABEL} or others. */ - public static synchronized void notifySyncNeeded(final String syncSetName) { - AtomicInteger counter = syncUpdatesOngoingCounters.get(syncSetName); - if (counter != null && counter.get() != 0) { - Log.d(TAG, "@@notifySyncNeeded " + syncSetName + " ignored: ongoing counter != 0"); - return; - } - + public static synchronized void notifySyncNeeded(final String... syncSetNames) { // if not logged in, do nothing if (Preferences.getString(Prefkey.sync_simpleToken) == null) { return; } - { // check if preferences prevent syncing - if (!Preferences.getBoolean(prefkeyForSyncSetEnabled(syncSetName), true)) { + for (final String syncSetName : syncSetNames) { + final AtomicInteger counter = syncUpdatesOngoingCounters.get(syncSetName); + if (counter != null && counter.get() != 0) { + Log.d(TAG, "@@notifySyncNeeded " + syncSetName + " ignored: ongoing counter != 0"); return; } - } - SyncRecorder.log(SyncRecorder.EventKind.sync_needed_notified, syncSetName); + { // check if preferences prevent syncing + if (!Preferences.getBoolean(prefkeyForSyncSetEnabled(syncSetName), true)) { + continue; + } + } - // check if we can omit queueing sync request for this sync set name. - synchronized (syncSetNameQueue) { - if (syncSetNameQueue.contains(syncSetName)) { - Log.d(TAG, "@@notifySyncNeeded " + syncSetName + " ignored: sync queue already contains it"); - return; + SyncRecorder.log(SyncRecorder.EventKind.sync_needed_notified, syncSetName); + + // check if we can omit queueing sync request for this sync set name. + synchronized (syncSetNameQueue) { + if (syncSetNameQueue.contains(syncSetName)) { + Log.d(TAG, "@@notifySyncNeeded " + syncSetName + " ignored: sync queue already contains it"); + continue; + } + syncSetNameQueue.add(syncSetName); } - syncSetNameQueue.add(syncSetName); } syncExecutor.schedule(() -> { while (true) { - final String extraSyncSetName; + final List extraSyncSetNames = new ArrayList<>(); synchronized (syncSetNameQueue) { - extraSyncSetName = syncSetNameQueue.poll(); + final String extraSyncSetName = syncSetNameQueue.poll(); if (extraSyncSetName == null) { - return; + break; } + extraSyncSetNames.add(extraSyncSetName); + } + + if (extraSyncSetNames.size() == 0) { + return; } final Account account = SyncUtils.getOrCreateSyncAccount(); @@ -243,7 +247,7 @@ public static synchronized void notifySyncNeeded(final String syncSetName) { // request sync. final Bundle extras = new Bundle(); - extras.putString(SyncAdapter.EXTRA_SYNC_SET_NAME, extraSyncSetName); + extras.putString(SyncAdapter.EXTRA_SYNC_SET_NAMES, App.getDefaultGson().toJson(extraSyncSetNames)); ContentResolver.requestSync(account, authority, extras); } }, 5, TimeUnit.SECONDS); @@ -251,7 +255,7 @@ public static synchronized void notifySyncNeeded(final String syncSetName) { /** * Call this method(true) when updating local storage because of sync. Call this method(false) when finished. - * Calls to {@link #notifySyncNeeded(String)} will be a no-op when sync updates are ongoing (marked by this method being called). + * Calls to {@link #notifySyncNeeded(String...)} will be a no-op when sync updates are ongoing (marked by this method being called). * @param isRunning true to start, false to stop. */ public static synchronized void notifySyncUpdatesOngoing(final String syncSetName, final boolean isRunning) { @@ -519,12 +523,13 @@ public static void forceSyncNow() { } // request sync. - for (final String syncSetName : SyncShadow.ALL_SYNC_SET_NAMES) { - final Bundle extras = new Bundle(); - extras.putString(SyncAdapter.EXTRA_SYNC_SET_NAME, syncSetName); - extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); - ContentResolver.requestSync(account, authority, extras); - } + final List syncSetNames = new ArrayList<>(); + Collections.addAll(syncSetNames, SyncShadow.ALL_SYNC_SET_NAMES); + + final Bundle extras = new Bundle(); + extras.putString(SyncAdapter.EXTRA_SYNC_SET_NAMES, App.getDefaultGson().toJson(syncSetNames)); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + ContentResolver.requestSync(account, authority, extras); } } diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java index a44a59bf3..fe11dd71d 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java @@ -45,7 +45,7 @@ */ public class SyncAdapter extends AbstractThreadedSyncAdapter { static final String TAG = SyncAdapter.class.getSimpleName(); - public static final String EXTRA_SYNC_SET_NAME = "syncSetName"; + public static final String EXTRA_SYNC_SET_NAMES = "syncSetNames"; private static final int PARTIAL_SYNC_THRESHOLD = 100; final static Stack syncSetsRunning = new Stack<>(); // need guard when accessing this @@ -123,11 +123,13 @@ def patchNoConflict(entities, operations): @Override public void onPerformSync(final Account account, final Bundle extras, final String authority, final ContentProviderClient provider, final SyncResult syncResult) { Log.d(TAG, "@@onPerformSync account:" + account + " extras:" + extras + " authority:" + authority); - final String syncSetName = extras.getString(EXTRA_SYNC_SET_NAME); - if (syncSetName == null) { + + final String[] syncSetNames = App.getDefaultGson().fromJson(extras.getString(EXTRA_SYNC_SET_NAMES), String[].class); + if (syncSetNames == null || syncSetNames.length == 0) { return; } + for (final String syncSetName : syncSetNames) { synchronized (syncSetsRunning) { syncSetsRunning.add(syncSetName); } @@ -158,8 +160,6 @@ public void onPerformSync(final Account account, final Bundle extras, final Stri break; } } finally { - Log.d(TAG, "Sync result: " + syncResult); - synchronized (syncSetsRunning) { final String popped = syncSetsRunning.pop(); if (!popped.equals(syncSetName)) { @@ -171,6 +171,9 @@ public void onPerformSync(final Account account, final Bundle extras, final Stri } } + Log.d(TAG, "Sync result: " + syncResult + " hasSoftError=" + syncResult.hasSoftError() + " hasHardError=" + syncResult.hasHardError() + " ioex=" + syncResult.stats.numIoExceptions); + } + public static Set getRunningSyncs() { final Set res = new LinkedHashSet<>(); synchronized (syncSetsRunning) { From 0c8636c5f3a6d00fb6c01b1c6b8332b631c3269e Mon Sep 17 00:00:00 2001 From: Yuku on yuku8 Date: Thu, 9 Jul 2015 17:59:38 +0800 Subject: [PATCH 07/17] Request sync again if the last sync is partial --- .../yuku/alkitab/base/sync/SyncAdapter.java | 79 +++++++++++-------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java index fe11dd71d..eb596348b 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/sync/SyncAdapter.java @@ -130,46 +130,46 @@ public void onPerformSync(final Account account, final Bundle extras, final Stri } for (final String syncSetName : syncSetNames) { - synchronized (syncSetsRunning) { - syncSetsRunning.add(syncSetName); - } + synchronized (syncSetsRunning) { + syncSetsRunning.add(syncSetName); + } - try { - App.getLbm().sendBroadcast(new Intent(SyncSettingsActivity.ACTION_RELOAD)); + try { + App.getLbm().sendBroadcast(new Intent(SyncSettingsActivity.ACTION_RELOAD)); - SyncRecorder.log(SyncRecorder.EventKind.sync_adapter_on_perform, syncSetName); + SyncRecorder.log(SyncRecorder.EventKind.sync_adapter_on_perform, syncSetName); - if (!Preferences.getBoolean(Sync.prefkeyForSyncSetEnabled(syncSetName), true)) { - SyncRecorder.log(SyncRecorder.EventKind.sync_adapter_set_not_enabled, syncSetName); + if (!Preferences.getBoolean(Sync.prefkeyForSyncSetEnabled(syncSetName), true)) { + SyncRecorder.log(SyncRecorder.EventKind.sync_adapter_set_not_enabled, syncSetName); - return; - } + return; + } - switch (syncSetName) { - case SyncShadow.SYNC_SET_MABEL: - syncMabel(syncResult); - break; - case SyncShadow.SYNC_SET_HISTORY: - syncHistory(syncResult); - break; - case SyncShadow.SYNC_SET_PINS: - syncPins(syncResult); - break; - case SyncShadow.SYNC_SET_RP: - syncRp(syncResult); - break; - } - } finally { - synchronized (syncSetsRunning) { - final String popped = syncSetsRunning.pop(); - if (!popped.equals(syncSetName)) { - throw new RuntimeException("syncSetsRunning not balanced: popped=" + popped + " actual=" + syncSetName); + switch (syncSetName) { + case SyncShadow.SYNC_SET_MABEL: + syncMabel(syncResult); + break; + case SyncShadow.SYNC_SET_HISTORY: + syncHistory(syncResult); + break; + case SyncShadow.SYNC_SET_PINS: + syncPins(syncResult); + break; + case SyncShadow.SYNC_SET_RP: + syncRp(syncResult); + break; + } + } finally { + synchronized (syncSetsRunning) { + final String popped = syncSetsRunning.pop(); + if (!popped.equals(syncSetName)) { + throw new RuntimeException("syncSetsRunning not balanced: popped=" + popped + " actual=" + syncSetName); + } } - } - App.getLbm().sendBroadcast(new Intent(SyncSettingsActivity.ACTION_RELOAD)); + App.getLbm().sendBroadcast(new Intent(SyncSettingsActivity.ACTION_RELOAD)); + } } - } Log.d(TAG, "Sync result: " + syncResult + " hasSoftError=" + syncResult.hasSoftError() + " hasHardError=" + syncResult.hasHardError() + " ioex=" + syncResult.stats.numIoExceptions); } @@ -214,13 +214,14 @@ static void fillInStatsFromAppendDelta(final Sync.Delta append_delta, fin * Also, the current state must be updated using the append delta from the server. * * @param clientState will be modified + * @return true iff chopped */ - static void chopClientState(final Sync.ClientState clientState, final String syncSetName) { + static boolean chopClientState(final Sync.ClientState clientState, final String syncSetName) { final List> src = clientState.delta.operations; // fast path if the operation count is below threshold if (src.size() <= PARTIAL_SYNC_THRESHOLD) { - return; + return false; } final List> dst = new ArrayList<>(); @@ -245,8 +246,12 @@ static void chopClientState(final Sync.ClientState clientState, final Str SyncRecorder.log(SyncRecorder.EventKind.partial_sync_info, syncSetName, "client_delta_operations_size_original", src.size(), "client_delta_operations_size_chopped", dst.size()); + final boolean res = dst.size() < src.size(); + src.clear(); src.addAll(dst); + + return res; } void syncMabel(final SyncResult sr) { @@ -267,7 +272,7 @@ void syncMabel(final SyncResult sr) { SyncRecorder.log(SyncRecorder.EventKind.current_entities_gathered, syncSetName, "base_revno", clientState.base_revno, "client_delta_operations_size", clientState.delta.operations.size(), "client_entities_size", entitiesBeforeSync.size()); - chopClientState(clientState, syncSetName); + final boolean isPartial = chopClientState(clientState, syncSetName); final String serverPrefix = Sync.getEffectiveServerPrefix(); Log.d(TAG, "@@syncMabel step 20: building http request. Server prefix: " + serverPrefix); @@ -328,6 +333,10 @@ void syncMabel(final SyncResult sr) { fillInStatsFromAppendDelta(append_delta, sr); + if (isPartial) { + Sync.notifySyncNeeded(syncSetName); + } + // success! Tell our world. SyncRecorder.log(SyncRecorder.EventKind.all_succeeded, syncSetName, "insert_count", sr.stats.numInserts, "update_count", sr.stats.numUpdates, "delete_count", sr.stats.numDeletes); From ab9a8871f484caea34bb4f46b1a02040dc2ea6e0 Mon Sep 17 00:00:00 2001 From: Yuku on yuku8 Date: Fri, 10 Jul 2015 16:46:11 +0800 Subject: [PATCH 08/17] Add check hash of all entities for sync algo verification Remove display of user token --- .../base/ac/SecretSyncDebugActivity.java | 74 +++++- .../res/layout/activity_secret_sync_debug.xml | 217 +++++++++--------- 2 files changed, 177 insertions(+), 114 deletions(-) diff --git a/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java b/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java index 4429771da..b6548a581 100644 --- a/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java +++ b/Alkitab/src/main/java/yuku/alkitab/base/ac/SecretSyncDebugActivity.java @@ -3,10 +3,12 @@ import android.content.Intent; import android.os.Bundle; import android.view.View; +import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.EditText; -import android.widget.TextView; +import android.widget.Spinner; import com.afollestad.materialdialogs.AlertDialogWrapper; +import com.afollestad.materialdialogs.MaterialDialog; import com.google.gson.reflect.TypeToken; import com.squareup.okhttp.Call; import com.squareup.okhttp.Callback; @@ -24,7 +26,10 @@ import yuku.alkitab.base.model.SyncShadow; import yuku.alkitab.base.storage.Prefkey; import yuku.alkitab.base.sync.Sync; +import yuku.alkitab.base.sync.Sync_History; import yuku.alkitab.base.sync.Sync_Mabel; +import yuku.alkitab.base.sync.Sync_Pins; +import yuku.alkitab.base.sync.Sync_Rp; import yuku.alkitab.base.util.Highlights; import yuku.alkitab.debug.R; import yuku.alkitab.model.Label; @@ -32,20 +37,23 @@ import yuku.alkitab.model.Marker_Label; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; public class SecretSyncDebugActivity extends BaseActivity { public static final String TAG = SecretSyncDebugActivity.class.getSimpleName(); EditText tServer; - TextView tUser; EditText tUserEmail; CheckBox cMakeDirtyMarker; CheckBox cMakeDirtyLabel; CheckBox cMakeDirtyMarker_Label; + Spinner cbSyncSetName; @Override protected void onCreate(Bundle savedInstanceState) { @@ -53,7 +61,6 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_secret_sync_debug); tServer = V.get(this, R.id.tServer); - tUser = V.get(this, R.id.tUser); tUserEmail = V.get(this, R.id.tUserEmail); cMakeDirtyMarker = V.get(this, R.id.cMakeDirtyMarker); cMakeDirtyLabel = V.get(this, R.id.cMakeDirtyLabel); @@ -89,7 +96,10 @@ protected void onCreate(Bundle savedInstanceState) { V.get(this, R.id.bLogout).setOnClickListener(bLogout_click); V.get(this, R.id.bSync).setOnClickListener(bSync_click); - displayUser(); + cbSyncSetName = V.get(this, R.id.cbSyncSetName); + cbSyncSetName.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, SyncShadow.ALL_SYNC_SET_NAMES)); + + V.get(this, R.id.bCheckHash).setOnClickListener(bCheckHash_click); } View.OnClickListener bMabelClientState_click = v -> { @@ -177,10 +187,6 @@ private String randomString(final String prefix, final int word_count, final int return sb.toString(); } - void displayUser() { - tUser.setText(Preferences.getString(getString(R.string.pref_syncAccountName_key), "not logged in") + ": " + Preferences.getString(Prefkey.sync_simpleToken)); - } - View.OnClickListener bLogout_click = v -> { Preferences.hold(); Preferences.remove(getString(R.string.pref_syncAccountName_key)); @@ -191,8 +197,6 @@ void displayUser() { for (final String syncSetName : SyncShadow.ALL_SYNC_SET_NAMES) { S.getDb().deleteSyncShadowBySyncSetName(syncSetName); } - - displayUser(); }; View.OnClickListener bSync_click = v -> { @@ -284,4 +288,54 @@ public void onResponse(final Response response) throws IOException { } }); }; + + View.OnClickListener bCheckHash_click = v -> { + final String syncSetName = (String) cbSyncSetName.getSelectedItem(); + final List> entities = new ArrayList<>(); + + final MaterialDialog pd = new MaterialDialog.Builder(this) + .progress(true, 0) + .content("getting entities…") + .show(); + + new Thread(() -> { + switch (syncSetName) { + case SyncShadow.SYNC_SET_MABEL: + entities.addAll(Sync_Mabel.getEntitiesFromCurrent()); + break; + case SyncShadow.SYNC_SET_RP: + entities.addAll(Sync_Rp.getEntitiesFromCurrent()); + break; + case SyncShadow.SYNC_SET_PINS: + entities.addAll(Sync_Pins.getEntitiesFromCurrent()); + break; + case SyncShadow.SYNC_SET_HISTORY: + entities.addAll(Sync_History.getEntitiesFromCurrent()); + break; + } + + Collections.sort(entities, (lhs, rhs) -> lhs.gid.compareTo(rhs.gid)); + + int hashCode = 1; + for (final Sync.Entity entity : entities) { + int elementHashCode; + + if (entity == null) { + elementHashCode = 0; + } else { + elementHashCode = (entity).hashCode(); + } + hashCode = 31 * hashCode + elementHashCode; + } + + pd.dismiss(); + + final int finalHashCode = hashCode; + + runOnUiThread(() -> new MaterialDialog.Builder(this) + .content("entities.size=" + entities.size() + " hash=" + String.format(Locale.US, "0x%08x", finalHashCode)) + .positiveText("OK") + .show()); + }).start(); + }; } diff --git a/Alkitab/src/main/res/layout/activity_secret_sync_debug.xml b/Alkitab/src/main/res/layout/activity_secret_sync_debug.xml index 9adb67827..49341def3 100644 --- a/Alkitab/src/main/res/layout/activity_secret_sync_debug.xml +++ b/Alkitab/src/main/res/layout/activity_secret_sync_debug.xml @@ -1,133 +1,142 @@ + - + android:layout_height="match_parent"> + android:layout_height="match_parent" + android:orientation="vertical"> - -