From d6874de8c8a1467323ac12337ec956afd1475a67 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 26 Jul 2018 19:41:52 +1000 Subject: [PATCH] strip out unused syncing code --- anki/sync.py | 137 +++++------------ tests/test_sync.py | 370 --------------------------------------------- 2 files changed, 38 insertions(+), 469 deletions(-) delete mode 100644 tests/test_sync.py diff --git a/anki/sync.py b/anki/sync.py index 811b95f3d..605e325fb 100644 --- a/anki/sync.py +++ b/anki/sync.py @@ -54,9 +54,6 @@ class Syncer: rts = meta['ts'] self.rmod = meta['mod'] self.maxUsn = meta['usn'] - # this is a temporary measure to address the problem of users - # forgetting which email address they've used - it will be removed - # when enough time has passed self.uname = meta.get("uname", "") meta = self.meta() self.col.log("lmeta", meta) @@ -143,13 +140,6 @@ class Syncer: d['crt'] = self.col.crt return d - def applyChanges(self, changes): - self.rchg = changes - lchg = self.changes() - # merge our side before returning - self.mergeChanges(lchg, self.rchg) - return lchg - def mergeChanges(self, lchg, rchg): # then the other objects self.mergeModels(rchg['models']) @@ -177,14 +167,8 @@ class Syncer: return "tag had usn = -1" found = False for m in self.col.models.all(): - if self.col.server: - # the web upgrade was mistakenly setting usn - if m['usn'] < 0: - m['usn'] = 0 - found = True - else: - if m['usn'] == -1: - return "model had usn = -1" + if m['usn'] == -1: + return "model had usn = -1" if found: self.col.models.save() self.col.sched.reset() @@ -202,22 +186,10 @@ class Syncer: len(self.col.decks.allConf()), ] - def sanityCheck2(self, client): - server = self.sanityCheck() - if client != server: - return dict(status="bad", c=client, s=server) - return dict(status="ok") - def usnLim(self): - if self.col.server: - return "usn >= %d" % self.minUsn - else: - return "usn = -1" + return "usn = -1" def finish(self, mod=None): - if not mod: - # server side; we decide new mod time - mod = intTime(1000) self.col.ls = mod self.col._usn = self.maxUsn + 1 # ensure we save the mod time even if no changes made @@ -262,11 +234,10 @@ from notes where %s""" % d) # table is empty self.tablesLeft.pop(0) self.cursor = None - # if we're the client, mark the objects as having been sent - if not self.col.server: - self.col.db.execute( - "update %s set usn=? where usn=-1"%curTable, - self.maxUsn) + # mark the objects as having been sent + self.col.db.execute( + "update %s set usn=? where usn=-1"%curTable, + self.maxUsn) buf[curTable] = rows lim -= fetched if not self.tablesLeft: @@ -288,12 +259,10 @@ from notes where %s""" % d) cards = [] notes = [] decks = [] - if self.col.server: - curs = self.col.db.execute( - "select oid, type from graves where usn >= ?", self.minUsn) - else: - curs = self.col.db.execute( - "select oid, type from graves where usn = -1") + + curs = self.col.db.execute( + "select oid, type from graves where usn = -1") + for oid, type in curs: if type == REM_CARD: cards.append(oid) @@ -301,23 +270,16 @@ from notes where %s""" % d) notes.append(oid) else: decks.append(oid) - if not self.col.server: - self.col.db.execute("update graves set usn=? where usn=-1", - self.maxUsn) - return dict(cards=cards, notes=notes, decks=decks) - def start(self, minUsn, lnewer, graves): - self.maxUsn = self.col._usn - self.minUsn = minUsn - self.lnewer = not lnewer - lgraves = self.removed() - self.remove(graves) - return lgraves + self.col.db.execute("update graves set usn=? where usn=-1", + self.maxUsn) + + return dict(cards=cards, notes=notes, decks=decks) def remove(self, graves): # pretend to be the server so we don't set usn = -1 - wasServer = self.col.server self.col.server = True + # notes first, so we don't end up with duplicate graves self.col._remNotes(graves['notes']) # then cards @@ -325,20 +287,18 @@ from notes where %s""" % d) # and decks for oid in graves['decks']: self.col.decks.rem(oid, childrenToo=False) - self.col.server = wasServer + + self.col.server = False # Models ########################################################################## def getModels(self): - if self.col.server: - return [m for m in self.col.models.all() if m['usn'] >= self.minUsn] - else: - mods = [m for m in self.col.models.all() if m['usn'] == -1] - for m in mods: - m['usn'] = self.maxUsn - self.col.models.save() - return mods + mods = [m for m in self.col.models.all() if m['usn'] == -1] + for m in mods: + m['usn'] = self.maxUsn + self.col.models.save() + return mods def mergeModels(self, rchg): for r in rchg: @@ -351,20 +311,14 @@ from notes where %s""" % d) ########################################################################## def getDecks(self): - if self.col.server: - return [ - [g for g in self.col.decks.all() if g['usn'] >= self.minUsn], - [g for g in self.col.decks.allConf() if g['usn'] >= self.minUsn] - ] - else: - decks = [g for g in self.col.decks.all() if g['usn'] == -1] - for g in decks: - g['usn'] = self.maxUsn - dconf = [g for g in self.col.decks.allConf() if g['usn'] == -1] - for g in dconf: - g['usn'] = self.maxUsn - self.col.decks.save() - return [decks, dconf] + decks = [g for g in self.col.decks.all() if g['usn'] == -1] + for g in decks: + g['usn'] = self.maxUsn + dconf = [g for g in self.col.decks.allConf() if g['usn'] == -1] + for g in dconf: + g['usn'] = self.maxUsn + self.col.decks.save() + return [decks, dconf] def mergeDecks(self, rchg): for r in rchg[0]: @@ -389,17 +343,13 @@ from notes where %s""" % d) ########################################################################## def getTags(self): - if self.col.server: - return [t for t, usn in self.col.tags.allItems() - if usn >= self.minUsn] - else: - tags = [] - for t, usn in self.col.tags.allItems(): - if usn == -1: - self.col.tags.tags[t] = self.maxUsn - tags.append(t) - self.col.tags.save() - return tags + tags = [] + for t, usn in self.col.tags.allItems(): + if usn == -1: + self.col.tags.tags[t] = self.maxUsn + tags.append(t) + self.col.tags.save() + return tags def mergeTags(self, tags): self.col.tags.register(tags, usn=self.maxUsn) @@ -448,17 +398,6 @@ from notes where %s""" % d) def mergeConf(self, conf): self.col.conf = conf -# Local syncing for unit tests -########################################################################## - -class LocalServer(Syncer): - - # serialize/deserialize payload, so we don't end up sharing objects - # between cols - def applyChanges(self, changes): - l = json.loads; d = json.dumps - return l(d(Syncer.applyChanges(self, l(d(changes))))) - # Wrapper for requests that tracks upload/download progress ########################################################################## diff --git a/tests/test_sync.py b/tests/test_sync.py deleted file mode 100644 index 11e48ca60..000000000 --- a/tests/test_sync.py +++ /dev/null @@ -1,370 +0,0 @@ -# coding: utf-8 - -import nose, os, shutil, time - -from anki import Collection as aopen, Collection -from anki.utils import intTime -from anki.sync import Syncer, LocalServer -from anki.consts import STARTING_FACTOR -from tests.shared import getEmptyCol, getEmptyDeckWith -import anki.stdmodels - -# Local tests -########################################################################## - -deck1=None -deck2=None -client=None -server=None -server2=None - -def setup_basic(): - global deck1, deck2, client, server - deck1 = getEmptyCol() - # add a note to deck 1 - f = deck1.newNote() - f['Front'] = "foo"; f['Back'] = "bar"; f.tags = ["foo"] - deck1.addNote(f) - # answer it - deck1.reset(); deck1.sched.answerCard(deck1.sched.getCard(), 4) - # repeat for deck2 - deck2 = getEmptyDeckWith(server=True) - f = deck2.newNote() - f['Front'] = "bar"; f['Back'] = "bar"; f.tags = ["bar"] - deck2.addNote(f) - deck2.reset(); deck2.sched.answerCard(deck2.sched.getCard(), 4) - # start with same schema and sync time - deck1.scm = deck2.scm = 0 - # and same mod time, so sync does nothing - t = intTime(1000) - deck1.save(mod=t); deck2.save(mod=t) - server = LocalServer(deck2) - client = Syncer(deck1, server) - -def setup_modified(): - setup_basic() - # mark deck1 as changed - time.sleep(0.1) - deck1.setMod() - deck1.save() - -@nose.with_setup(setup_basic) -def test_nochange(): - assert client.sync() == "noChanges" - -@nose.with_setup(setup_modified) -def test_changedSchema(): - deck1.scm += 1 - deck1.setMod() - assert client.sync() == "fullSync" - -@nose.with_setup(setup_modified) -def test_sync(): - def check(num): - for d in deck1, deck2: - for t in ("revlog", "notes", "cards"): - assert d.db.scalar("select count() from %s" % t) == num - assert len(d.models.all()) == num*len(anki.stdmodels.models) - # the default deck and config have an id of 1, so always 1 - assert len(d.decks.all()) == 1 - assert len(d.decks.dconf) == 1 - assert len(d.tags.all()) == num - check(1) - origUsn = deck1.usn() - assert client.sync() == "success" - # last sync times and mod times should agree - assert deck1.mod == deck2.mod - assert deck1._usn == deck2._usn - assert deck1.mod == deck1.ls - assert deck1._usn != origUsn - # because everything was created separately it will be merged in. in - # actual use, we use a full sync to ensure a common starting point. - check(2) - # repeating it does nothing - assert client.sync() == "noChanges" - # if we bump mod time, the decks will sync but should remain the same. - deck1.setMod() - deck1.save(mod=deck1.mod+1) - assert client.sync() == "success" - check(2) - # crt should be synced - deck1.crt = 123 - deck1.setMod() - deck1.save(mod=deck1.mod+1) - ret = client.sync(); assert ret == "success" - assert deck1.crt == deck2.crt - -@nose.with_setup(setup_modified) -def test_models(): - test_sync() - # update model one - cm = deck1.models.current() - cm['name'] = "new" - time.sleep(1) - deck1.models.save(cm) - deck1.save() - assert deck2.models.get(cm['id'])['name'].startswith("Basic") - assert client.sync() == "success" - assert deck2.models.get(cm['id'])['name'] == "new" - # deleting triggers a full sync - deck1.scm = deck2.scm = 0 - deck1.models.rem(cm) - deck1.save() - assert client.sync() == "fullSync" - -@nose.with_setup(setup_modified) -def test_notes(): - test_sync() - # modifications should be synced - nid = deck1.db.scalar("select id from notes") - note = deck1.getNote(nid) - assert note['Front'] != "abc" - note['Front'] = "abc" - note.flush() - deck1.save() - assert client.sync() == "success" - assert deck2.getNote(nid)['Front'] == "abc" - # deletions too - assert deck1.db.scalar("select 1 from notes where id = ?", nid) - deck1.remNotes([nid]) - deck1.save() - assert client.sync() == "success" - assert not deck1.db.scalar("select 1 from notes where id = ?", nid) - assert not deck2.db.scalar("select 1 from notes where id = ?", nid) - -@nose.with_setup(setup_modified) -def test_cards(): - test_sync() - nid = deck1.db.scalar("select id from notes") - note = deck1.getNote(nid) - card = note.cards()[0] - # answer the card locally - card.startTimer() - deck1.sched.answerCard(card, 4) - assert card.reps == 2 - deck1.save() - assert deck2.getCard(card.id).reps == 1 - assert client.sync() == "success" - assert deck2.getCard(card.id).reps == 2 - # if it's modified on both sides , later mod time should win - for test in ((deck1, deck2), (deck2, deck1)): - time.sleep(1) - c = test[0].getCard(card.id) - c.reps = 5; c.flush() - test[0].save() - time.sleep(1) - c = test[1].getCard(card.id) - c.reps = 3; c.flush() - test[1].save() - assert client.sync() == "success" - assert test[1].getCard(card.id).reps == 3 - assert test[0].getCard(card.id).reps == 3 - # removals should work too - deck1.remCards([card.id]) - deck1.save() - assert deck2.db.scalar("select 1 from cards where id = ?", card.id) - assert client.sync() == "success" - assert not deck2.db.scalar("select 1 from cards where id = ?", card.id) - -@nose.with_setup(setup_modified) -def test_tags(): - test_sync() - def sortedTags(deck): - return sorted(deck.tags.all()) - assert sortedTags(deck1) == sortedTags(deck2) - deck1.tags.register(["abc"]) - deck2.tags.register(["xyz"]) - assert sortedTags(deck1) != sortedTags(deck2) - deck1.save() - time.sleep(0.1) - deck2.save() - assert client.sync() == "success" - assert sortedTags(deck1) == sortedTags(deck2) - -@nose.with_setup(setup_modified) -def test_decks(): - test_sync() - assert len(deck1.decks.all()) == 1 - assert len(deck1.decks.all()) == len(deck2.decks.all()) - deck1.decks.id("new") - assert len(deck1.decks.all()) != len(deck2.decks.all()) - time.sleep(0.1) - deck2.decks.id("new2") - deck1.save() - time.sleep(0.1) - deck2.save() - assert client.sync() == "success" - assert sorted(deck1.tags.all()) == sorted(deck2.tags.all()) - assert len(deck1.decks.all()) == len(deck2.decks.all()) - assert len(deck1.decks.all()) == 3 - assert deck1.decks.confForDid(1)['maxTaken'] == 60 - deck2.decks.confForDid(1)['maxTaken'] = 30 - deck2.decks.save(deck2.decks.confForDid(1)) - deck2.save() - assert client.sync() == "success" - assert deck1.decks.confForDid(1)['maxTaken'] == 30 - -@nose.with_setup(setup_modified) -def test_conf(): - test_sync() - assert deck2.conf['curDeck'] == 1 - deck1.conf['curDeck'] = 2 - time.sleep(0.1) - deck1.setMod() - deck1.save() - assert client.sync() == "success" - assert deck2.conf['curDeck'] == 2 - -@nose.with_setup(setup_modified) -def test_threeway(): - test_sync() - deck1.close(save=False) - d3path = deck1.path.replace(".anki", "2.anki") - shutil.copy2(deck1.path, d3path) - deck1.reopen() - deck3 = aopen(d3path) - client2 = Syncer(deck3, server) - assert client2.sync() == "noChanges" - # client 1 adds a card at time 1 - time.sleep(1) - f = deck1.newNote() - f['Front'] = "1"; - deck1.addNote(f) - deck1.save() - # at time 2, client 2 syncs to server - time.sleep(1) - deck3.setMod() - deck3.save() - assert client2.sync() == "success" - # at time 3, client 1 syncs, adding the older note - time.sleep(1) - assert client.sync() == "success" - assert deck1.noteCount() == deck2.noteCount() - # syncing client2 should pick it up - assert client2.sync() == "success" - assert deck1.noteCount() == deck2.noteCount() == deck3.noteCount() - -def test_threeway2(): - # for this test we want ms precision of notes so we don't have to - # sleep a lot - import anki.notes - intTime = anki.notes.intTime - anki.notes.intTime = lambda x=1: intTime(1000) - def setup(): - # create collection 1 with a single note - c1 = getEmptyCol() - f = c1.newNote() - f['Front'] = "startingpoint" - nid = f.id - c1.addNote(f) - cid = f.cards()[0].id - c1.beforeUpload() - # start both clients and server off in this state - s1path = c1.path.replace(".anki2", "-s1.anki2") - c2path = c1.path.replace(".anki2", "-c2.anki2") - shutil.copy2(c1.path, s1path) - shutil.copy2(c1.path, c2path) - # open them - c1 = Collection(c1.path) - c2 = Collection(c2path) - s1 = Collection(s1path, server=True) - return c1, c2, s1, nid, cid - c1, c2, s1, nid, cid = setup() - # modify c1 then sync c1->s1 - n = c1.getNote(nid) - t = "firstmod" - n['Front'] = t - n.flush() - c1.db.execute("update cards set mod=1, usn=-1") - srv = LocalServer(s1) - clnt1 = Syncer(c1, srv) - clnt1.sync() - n.load() - assert n['Front'] == t - assert s1.getNote(nid)['Front'] == t - assert s1.db.scalar("select mod from cards") == 1 - # sync s1->c2 - clnt2 = Syncer(c2, srv) - clnt2.sync() - assert c2.getNote(nid)['Front'] == t - assert c2.db.scalar("select mod from cards") == 1 - # modify c1 and sync - time.sleep(0.001) - t = "secondmod" - n = c1.getNote(nid) - n['Front'] = t - n.flush() - c1.db.execute("update cards set mod=2, usn=-1") - clnt1.sync() - # modify c2 and sync - both c2 and server should be the same - time.sleep(0.001) - t2 = "thirdmod" - n = c2.getNote(nid) - n['Front'] = t2 - n.flush() - c2.db.execute("update cards set mod=3, usn=-1") - clnt2.sync() - n.load() - assert n['Front'] == t2 - assert c2.db.scalar("select mod from cards") == 3 - n = s1.getNote(nid) - assert n['Front'] == t2 - assert s1.db.scalar("select mod from cards") == 3 - # and syncing c1 again should yield the updated note as well - clnt1.sync() - n = s1.getNote(nid) - assert n['Front'] == t2 - assert s1.db.scalar("select mod from cards") == 3 - n = c1.getNote(nid) - assert n['Front'] == t2 - assert c1.db.scalar("select mod from cards") == 3 - -def _test_speed(): - t = time.time() - deck1 = aopen(os.path.expanduser("~/rapid.anki")) - for tbl in "revlog", "cards", "notes", "graves": - deck1.db.execute("update %s set usn = -1 where usn != -1"%tbl) - for m in deck1.models.all(): - m['usn'] = -1 - for tx in deck1.tags.all(): - deck1.tags.tags[tx] = -1 - deck1._usn = -1 - deck1.save() - deck2 = getEmptyDeckWith(server=True) - deck1.scm = deck2.scm = 0 - server = LocalServer(deck2) - client = Syncer(deck1, server) - print("load %d" % ((time.time() - t)*1000)); t = time.time() - assert client.sync() == "success" - print("sync %d" % ((time.time() - t)*1000)); t = time.time() - -@nose.with_setup(setup_modified) -def test_filtered_delete(): - test_sync() - nid = deck1.db.scalar("select id from notes") - note = deck1.getNote(nid) - card = note.cards()[0] - card.queue = 2 - card.type = 2 - card.ivl = 10 - card.factor = STARTING_FACTOR - card.due = deck1.sched.today - card.flush() - # put cards into a filtered deck - did = deck1.decks.newDyn("dyn") - deck1.sched.rebuildDyn(did) - # sync the filtered deck - assert client.sync() == "success" - # answer the card locally - time.sleep(1) - card.load() - card.startTimer() - deck1.sched.answerCard(card, 4) - assert card.ivl > 10 - # delete the filtered deck - deck1.decks.rem(did) - # sync again - assert client.sync() == "success" - card.load() - assert card.ivl > 10 - return