package yuku.alkitab.base.ac; import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; import android.view.View; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import com.google.gson.reflect.TypeToken; import okhttp3.Call; import okhttp3.Callback; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import yuku.afw.V; import yuku.afw.storage.Preferences; import yuku.alkitab.base.App; import yuku.alkitab.base.IsiActivity; import yuku.alkitab.base.S; import yuku.alkitab.base.U; import yuku.alkitab.base.ac.base.BaseActivity; 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.Background; import yuku.alkitab.base.util.Highlights; import yuku.alkitab.debug.BuildConfig; import yuku.alkitab.debug.R; import yuku.alkitab.model.Label; import yuku.alkitab.model.Marker; 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; import java.util.concurrent.atomic.AtomicBoolean; public class SecretSyncDebugActivity extends BaseActivity { public static final String TAG = SecretSyncDebugActivity.class.getSimpleName(); EditText tServer; EditText tUserEmail; CheckBox cMakeDirtyMarker; CheckBox cMakeDirtyLabel; CheckBox cMakeDirtyMarker_Label; Spinner cbSyncSetName; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_secret_sync_debug); tServer = V.get(this, R.id.tServer); tUserEmail = V.get(this, R.id.tUserEmail); cMakeDirtyMarker = V.get(this, R.id.cMakeDirtyMarker); cMakeDirtyLabel = V.get(this, R.id.cMakeDirtyLabel); cMakeDirtyMarker_Label = V.get(this, R.id.cMakeDirtyMarker_Label); V.get(this, R.id.bServerSave).setOnClickListener(v -> new MaterialDialog.Builder(this) .content("This will reset your synced shadow to revision 0.") .positiveText(R.string.ok) .onPositive((d, w) -> { Preferences.setString(Prefkey.sync_server_prefix, tServer.getText().toString().trim()); // do the same as logging out bLogout_click.onClick(null); }) .negativeText(R.string.cancel) .show()); V.get(this, R.id.bServerReset).setOnClickListener(v -> new MaterialDialog.Builder(this) .content("This will reset your synced shadow to revision 0.") .positiveText(R.string.ok) .onPositive((d, w) -> { Preferences.remove(Prefkey.sync_server_prefix); // do the same as logging out bLogout_click.onClick(null); tServer.setText(""); }) .negativeText(R.string.cancel) .show()); V.get(this, R.id.bMabelClientState).setOnClickListener(bMabelClientState_click); V.get(this, R.id.bGenerateDummies).setOnClickListener(bGenerateDummies_click); V.get(this, R.id.bGenerateDummies2).setOnClickListener(bGenerateDummies2_click); V.get(this, R.id.bMabelMonkey).setOnClickListener(bMabelMonkey_click); V.get(this, R.id.bLogout).setOnClickListener(bLogout_click); V.get(this, R.id.bSync).setOnClickListener(bSync_click); 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 -> { final StringBuilder sb = new StringBuilder(); final Sync.GetClientStateResult<Sync_Mabel.Content> pair = Sync_Mabel.getClientStateAndCurrentEntities(); final Sync.ClientState<Sync_Mabel.Content> clientState = pair.clientState; sb.append("Base revno: ").append(clientState.base_revno).append('\n'); sb.append("Delta operations (size ").append(clientState.delta.operations.size()).append("):\n"); for (final Sync.Operation<Sync_Mabel.Content> operation : clientState.delta.operations) { sb.append("\u2022 ").append(operation).append('\n'); } new MaterialDialog.Builder(this) .content(sb) .positiveText(R.string.ok) .show(); }; int rand(int n) { return (int) (Math.random() * n); } View.OnClickListener bGenerateDummies_click = v -> { final Label label1 = S.getDb().insertLabel(randomString("L1_", 1, 3, 8), U.encodeLabelBackgroundColor(rand(0xffffff))); final Label label2 = S.getDb().insertLabel(randomString("L2_", 1, 3, 8), U.encodeLabelBackgroundColor(rand(0xffffff))); for (int i = 0; i < 10; i++) { final Marker marker = S.getDb().insertMarker(0x000101 + rand(30), Marker.Kind.values()[rand(3)], randomString("M" + i + "_", rand(2) + 1, 4, 7), rand(2) + 1, new Date(), new Date()); final Set<Label> labelSet = new HashSet<>(); if (rand(10) < 5) { labelSet.add(label1); } if (rand(10) < 3) { labelSet.add(label2); } S.getDb().updateLabels(marker, labelSet); } new MaterialDialog.Builder(this) .content("10 markers, 2 labels generated.") .positiveText(R.string.ok) .show(); }; View.OnClickListener bGenerateDummies2_click = v -> { final Label label1 = S.getDb().insertLabel(randomString("LL1_", 1, 3, 8), U.encodeLabelBackgroundColor(rand(0xffffff))); final Label label2 = S.getDb().insertLabel(randomString("LL2_", 1, 3, 8), U.encodeLabelBackgroundColor(rand(0xffffff))); for (int i = 0; i < 1000; i++) { final Marker.Kind kind = Marker.Kind.values()[rand(3)]; final Date now = new Date(); final Marker marker = S.getDb().insertMarker(0x000101 + rand(30), kind, kind == Marker.Kind.highlight? Highlights.encode(rand(0xffffff)): randomString("MM" + i + "_", rand(10) < 5? rand(81): rand(400) + 4, 5, 15), rand(2) + 1, now, now); final Set<Label> labelSet = new HashSet<>(); if (rand(10) < 1) { labelSet.add(label1); } if (rand(10) < 4) { labelSet.add(label2); } S.getDb().updateLabels(marker, labelSet); } new MaterialDialog.Builder(this) .content("1000 markers, 2 labels generated.") .positiveText(R.string.ok) .show(); }; static MonkeyThread monkey; Handler toastHandler = new Handler(); class MonkeyThread extends Thread { final AtomicBoolean stopRequested = new AtomicBoolean(); final Toast toast; @SuppressLint("ShowToast") MonkeyThread() { toast = Toast.makeText(SecretSyncDebugActivity.this, "none", Toast.LENGTH_SHORT); } void toast(String msg) { toastHandler.post(() -> { toast.setText(msg); toast.show(); }); } @Override public void run() { while (!stopRequested.get()) { toast("preparing"); SystemClock.sleep(5000); { int nlabel = rand(5); toast("creating " + nlabel + " labels"); for (int i = 0; i < nlabel; i++) { S.getDb().insertLabel(randomString("monkey L " + i + " ", 1, 3, 8), U.encodeLabelBackgroundColor(rand(0xffffff))); } } if (stopRequested.get()) return; toast("waiting for 10 secs"); SystemClock.sleep(10000); final List<Label> labels = S.getDb().listAllLabels(); { int nmarker = rand(500); toast("creating " + nmarker + " markers"); for (int i = 0; i < nmarker; i++) { final Marker.Kind kind = Marker.Kind.values()[rand(3)]; final Date now = new Date(); final Marker marker = S.getDb().insertMarker(0x000101 + rand(30), kind, kind == Marker.Kind.highlight ? Highlights.encode(rand(0xffffff)) : randomString("monkey M " + i + " ", rand(8) + 2, 3, 5), rand(2) + 1, now, now); if (rand(10) < 1 && labels.size() > 0) { final Set<Label> labelSet = new HashSet<>(); labelSet.add(labels.get(rand(labels.size()))); S.getDb().updateLabels(marker, labelSet); } } } if (stopRequested.get()) return; toast("waiting for 10 secs"); SystemClock.sleep(10000); final List<Marker> markers = S.getDb().listAllMarkers(); if (markers.size() > 10) { int nmarker = rand(markers.size() / 10); toast("deleting up to 10% of markers: " + nmarker + " markers"); for (int i = 0; i < nmarker; i++) { final Marker marker = markers.get(rand(markers.size())); markers.remove(marker); final List<Marker_Label> mls = S.getDb().listMarker_LabelsByMarker(marker); for (final Marker_Label ml : mls) { S.getDb().deleteMarker_LabelByGid(ml.gid); } S.getDb().deleteMarkerByGid(marker.gid); } } if (stopRequested.get()) return; toast("waiting for 10 secs"); SystemClock.sleep(10000); if (labels.size() > 10) { int nlabel = rand(labels.size() / 10); toast("deleting up to 10% of label: " + nlabel + " labels"); for (int i = 0; i < nlabel; i++) { final Label label = labels.get(rand(labels.size())); labels.remove(label); S.getDb().deleteLabelAndMarker_LabelsByLabelId(label._id); } } if (stopRequested.get()) return; toast("waiting for 10 secs"); SystemClock.sleep(10000); { int nmarker = rand(markers.size() / 5); toast("editing up to 20% of markers: " + nmarker + " markers"); for (int i = 0; i < nmarker; i++) { final Marker marker = markers.get(rand(markers.size())); marker.caption = randomString("monkey edit M " + i + " ", rand(8) + 2, 3, 5); S.getDb().insertOrUpdateMarker(marker); } } if (stopRequested.get()) return; toast("waiting for 40 secs"); SystemClock.sleep(40000); } } void requestStop() { stopRequested.set(true); } } final View.OnClickListener bMabelMonkey_click = v -> { if (!BuildConfig.DEBUG) return; if (monkey != null) { monkey.requestStop(); monkey = null; new MaterialDialog.Builder(this) .content("monkey stopped") .show(); return; } new MaterialDialog.Builder(this) .content("This will MESS UP YOUR MARKERS. JANGAN TEKAN TOMBOL INI karena segala datamu akan rusak.") .positiveText("DO NOT PRESS") .negativeText("OK") .onPositive((dialog, which) -> { monkey = new MonkeyThread(); monkey.start(); }) .show(); }; private String randomString(final String prefix, final int word_count, final int minwordlen, final int maxwordlen) { final StringBuilder sb = new StringBuilder(prefix); for (int i = 0; i < word_count; i++) { for (int j = 0, wordlen = rand(maxwordlen - minwordlen) + minwordlen; j < wordlen; j++) { if (j % 2 == 0) { sb.append("bcdfghjklmnpqrstvwyz".charAt(rand(20))); } else { sb.append("aeiou".charAt(rand(5))); } } if (i != word_count - 1) { sb.append(' '); } } return sb.toString(); } View.OnClickListener bLogout_click = v -> { Preferences.hold(); Preferences.remove(getString(R.string.pref_syncAccountName_key)); Preferences.remove(Prefkey.sync_simpleToken); Preferences.remove(Prefkey.sync_token_obtained_time); Preferences.unhold(); for (final String syncSetName : SyncShadow.ALL_SYNC_SET_NAMES) { S.getDb().deleteSyncShadowBySyncSetName(syncSetName); } }; View.OnClickListener bSync_click = v -> { final String simpleToken = Preferences.getString(Prefkey.sync_simpleToken); if (simpleToken == null) { new MaterialDialog.Builder(this) .content("not logged in") .positiveText(R.string.ok) .show(); return; } final Sync.GetClientStateResult<Sync_Mabel.Content> pair = Sync_Mabel.getClientStateAndCurrentEntities(); final Sync.ClientState<Sync_Mabel.Content> clientState = pair.clientState; final List<Sync.Entity<Sync_Mabel.Content>> entitiesBeforeSync = pair.currentEntities; final RequestBody requestBody = new FormBody.Builder() .add("simpleToken", simpleToken) .add("syncSetName", SyncShadow.SYNC_SET_MABEL) .add("installation_id", U.getInstallationId()) .add("clientState", App.getDefaultGson().toJson(clientState)) .build(); final Call call = App.getLongTimeoutOkHttpClient().newCall( new Request.Builder() .url(Sync.getEffectiveServerPrefix() + "sync/api/sync") .post(requestBody) .build() ); if (cMakeDirtyMarker.isChecked()) { S.getDb().insertMarker(0x000101 + rand(30), Marker.Kind.values()[rand(3)], randomString("MMD0_", rand(2) + 1, 4, 7), rand(2) + 1, new Date(), new Date()); } if (cMakeDirtyLabel.isChecked()) { S.getDb().insertLabel(randomString("LMD_", 1, 3, 8), U.encodeLabelBackgroundColor(rand(0xffffff))); } if (cMakeDirtyMarker_Label.isChecked()) { final List<Label> labels = S.getDb().listAllLabels(); final List<Marker> markers = S.getDb().listAllMarkers(); if (labels.size() > 0 && markers.size() > 0) { final Marker_Label marker_label = Marker_Label.createNewMarker_Label(markers.get(0).gid, labels.get(0).gid); S.getDb().insertOrUpdateMarker_Label(marker_label); } else { new MaterialDialog.Builder(this) .content("not enough markers and labels to create marker_label") .positiveText(R.string.ok) .show(); return; } } call.enqueue(new Callback() { @Override public void onFailure(final Call call, final IOException e) { runOnUiThread(() -> new MaterialDialog.Builder(SecretSyncDebugActivity.this) .content("Error: " + e.getMessage()) .positiveText(R.string.ok) .show()); } @Override public void onResponse(final Call call, final Response response) throws IOException { final Sync.SyncResponseJson<Sync_Mabel.Content> debugSyncResponse = App.getDefaultGson().fromJson(response.body().charStream(), new TypeToken<Sync.SyncResponseJson<Sync_Mabel.Content>>() {}.getType()); runOnUiThread(() -> { if (debugSyncResponse.success) { final int final_revno = debugSyncResponse.final_revno; final Sync.Delta<Sync_Mabel.Content> append_delta = debugSyncResponse.append_delta; final Sync.ApplyAppendDeltaResult applyResult = S.getDb().applyMabelAppendDelta(final_revno, pair.shadowEntities, clientState, append_delta, entitiesBeforeSync, simpleToken); new MaterialDialog.Builder(SecretSyncDebugActivity.this) .content("Final revno: " + final_revno + "\nApply result: " + applyResult + "\nAppend delta: " + append_delta) .positiveText(R.string.ok) .show(); if (applyResult == Sync.ApplyAppendDeltaResult.ok) { App.getLbm().sendBroadcast(new Intent(IsiActivity.ACTION_ATTRIBUTE_MAP_CHANGED)); App.getLbm().sendBroadcast(new Intent(MarkersActivity.ACTION_RELOAD)); App.getLbm().sendBroadcast(new Intent(MarkerListActivity.ACTION_RELOAD)); } } else { new MaterialDialog.Builder(SecretSyncDebugActivity.this) .content(debugSyncResponse.message) .positiveText(R.string.ok) .show(); } }); } }); }; View.OnClickListener bCheckHash_click = v -> { final String syncSetName = (String) cbSyncSetName.getSelectedItem(); final List<Sync.Entity<?>> entities = new ArrayList<>(); final MaterialDialog pd = new MaterialDialog.Builder(this) .progress(true, 0) .content("getting entities…") .show(); Background.run(() -> { 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()); }); }; }