package yuku.alkitab.base.sync; 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; import yuku.alkitab.base.model.SyncShadow; import yuku.alkitab.base.util.History; import yuku.alkitab.base.util.Literals; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; public class Sync_History { /** * @return base revno, delta of shadow -> current. */ public static Pair<Sync.ClientState<Content>, List<Sync.Entity<Content>>> getClientStateAndCurrentEntities() { final SyncShadow ss = S.getDb().getSyncShadowBySyncSetName(SyncShadow.SYNC_SET_HISTORY); final List<Sync.Entity<Content>> srcs = ss == null? Literals.List(): entitiesFromShadow(ss); final List<Sync.Entity<Content>> dsts = getEntitiesFromCurrent(); final Sync.Delta<Content> delta = new Sync.Delta<>(); // additions and modifications (should not happen for history) for (final Sync.Entity<Content> dst : dsts) { final Sync.Entity<Content> existing = findEntity(srcs, dst.gid, dst.kind); if (existing == null) { delta.operations.add(new Sync.Operation<>(Sync.Opkind.add, dst.kind, dst.gid, dst.content)); } else { if (!isSameContent(dst, existing)) { // only when it changes delta.operations.add(new Sync.Operation<>(Sync.Opkind.mod, dst.kind, dst.gid, dst.content)); } } } // deletions for (final Sync.Entity<Content> src : srcs) { final Sync.Entity<Content> still_have = findEntity(dsts, src.gid, src.kind); if (still_have == null) { delta.operations.add(new Sync.Operation<>(Sync.Opkind.del, src.kind, src.gid, null)); } } return Pair.create(new Sync.ClientState<>(ss == null ? 0 : ss.revno, delta), dsts); } private static boolean isSameContent(final Sync.Entity<Content> a, final Sync.Entity<Content> b) { if (!U.equals(a.gid, b.gid)) return false; if (!U.equals(a.kind, b.kind)) return false; return U.equals(a.content, b.content); } private static Sync.Entity<Content> findEntity(final List<Sync.Entity<Content>> list, final String gid, final String kind) { for (final Sync.Entity<Content> entity : list) { if (U.equals(gid, entity.gid) && U.equals(kind, entity.kind)) { return entity; } } return null; } private static List<Sync.Entity<Content>> entitiesFromShadow(@NonNull final SyncShadow ss) { final BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(ss.data), Charset.forName("utf-8"))); final Sync.SyncShadowDataJson<Content> data = App.getDefaultGson().fromJson(reader, new TypeToken<Sync.SyncShadowDataJson<Content>>() {}.getType()); return data.entities; } @NonNull public static SyncShadow shadowFromEntities(@NonNull final List<Sync.Entity<Content>> entities, final int revno) { final Sync.SyncShadowDataJson<Content> data = new Sync.SyncShadowDataJson<>(); data.entities = entities; final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(baos, Charset.forName("utf-8"))); App.getDefaultGson().toJson(data, new TypeToken<Sync.SyncShadowDataJson<Content>>() {}.getType(), w); U.wontThrow(() -> w.flush()); final SyncShadow res = new SyncShadow(); res.data = baos.toByteArray(); res.syncSetName = SyncShadow.SYNC_SET_HISTORY; res.revno = revno; return res; } @NonNull public static List<Sync.Entity<Content>> getEntitiesFromCurrent() { final List<Sync.Entity<Content>> res = new ArrayList<>(); for (final History.HistoryEntry entry: History.getInstance().listAllEntries()) { final Content content = new Content(); content.ari = entry.ari; content.timestamp = entry.timestamp; final Sync.Entity<Content> entity = new Sync.Entity<>(Sync.Entity.KIND_HISTORY_ENTRY, entry.gid, content); res.add(entity); } return res; } public static class Content { public Integer ari; public Long timestamp; //region boilerplate equals and hashCode methods @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final Content content = (Content) o; if (ari != null ? !ari.equals(content.ari) : content.ari != null) return false; if (timestamp != null ? !timestamp.equals(content.timestamp) : content.timestamp != null) return false; return true; } @Override public int hashCode() { int result = ari != null ? ari.hashCode() : 0; result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0); return result; } //endregion @Override public String toString() { return "{" + ari + ", ts=" + timestamp + '}'; } } }