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.Literals; import yuku.alkitab.base.util.Sqlitil; import yuku.alkitab.base.widget.AttributeView; import yuku.alkitab.model.ProgressMark; 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.Collections; import java.util.Comparator; import java.util.List; /** * Pin is the new name for progress mark. */ public class Sync_Pins { private static final String GID_SPECIAL_PINS = "g2:pins"; /** * @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_PINS); 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 at all for pins) 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_PINS; res.revno = revno; return res; } @NonNull public static List<Sync.Entity<Content>> getEntitiesFromCurrent() { final List<Sync.Entity<Content>> res = new ArrayList<>(); final Content content = new Content(); final List<Content.Pin> pins = content.pins = new ArrayList<>(); for (int preset_id = 0; preset_id < AttributeView.PROGRESS_MARK_TOTAL_COUNT; preset_id++) { final ProgressMark pm = S.getDb().getProgressMarkByPresetId(preset_id); if (pm == null) continue; final Content.Pin pin = new Content.Pin(); pin.preset_id = pm.preset_id; pin.ari = pm.ari; pin.caption = pm.caption; pin.modifyTime = Sqlitil.toInt(pm.modifyTime); pins.add(pin); } final Sync.Entity<Content> entity = new Sync.Entity<>(Sync.Entity.KIND_PINS, GID_SPECIAL_PINS, content); res.add(entity); return res; } public static class Content { public List<Pin> pins; static Comparator<Pin> listSorter = (lhs, rhs) -> lhs.preset_id - rhs.preset_id; //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 (pins == null) { if (content.pins != null) return false; } else { if (content.pins == null) return false; final List<Pin> list1 = new ArrayList<>(pins); final List<Pin> list2 = new ArrayList<>(content.pins); Collections.sort(list1, listSorter); Collections.sort(list2, listSorter); if (!pins.equals(content.pins)) return false; } return true; } @Override public int hashCode() { return pins != null ? pins.hashCode() : 0; } //endregion @Override public String toString() { return "Content{" + "pins=" + pins + '}'; } public static class Pin { public int preset_id; public String caption; public int ari; public int modifyTime; //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 Pin pin = (Pin) o; if (preset_id != pin.preset_id) return false; if (ari != pin.ari) return false; if (modifyTime != pin.modifyTime) return false; if (caption != null ? !caption.equals(pin.caption) : pin.caption != null) return false; return true; } @Override public int hashCode() { int result = preset_id; result = 31 * result + (caption != null ? caption.hashCode() : 0); result = 31 * result + ari; result = 31 * result + modifyTime; return result; } //endregion @Override public String toString() { return "Pin{" + "preset_id=" + preset_id + ", caption='" + caption + '\'' + ", ari=" + ari + ", modifyTime=" + modifyTime + '}'; } } } }