strip out unused syncing code

This commit is contained in:
Damien Elmes 2018-07-26 19:41:52 +10:00
parent 8325f5b396
commit d6874de8c8
2 changed files with 38 additions and 469 deletions

View file

@ -54,9 +54,6 @@ class Syncer:
rts = meta['ts'] rts = meta['ts']
self.rmod = meta['mod'] self.rmod = meta['mod']
self.maxUsn = meta['usn'] 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", "") self.uname = meta.get("uname", "")
meta = self.meta() meta = self.meta()
self.col.log("lmeta", meta) self.col.log("lmeta", meta)
@ -143,13 +140,6 @@ class Syncer:
d['crt'] = self.col.crt d['crt'] = self.col.crt
return d 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): def mergeChanges(self, lchg, rchg):
# then the other objects # then the other objects
self.mergeModels(rchg['models']) self.mergeModels(rchg['models'])
@ -177,14 +167,8 @@ class Syncer:
return "tag had usn = -1" return "tag had usn = -1"
found = False found = False
for m in self.col.models.all(): for m in self.col.models.all():
if self.col.server: if m['usn'] == -1:
# the web upgrade was mistakenly setting usn return "model had usn = -1"
if m['usn'] < 0:
m['usn'] = 0
found = True
else:
if m['usn'] == -1:
return "model had usn = -1"
if found: if found:
self.col.models.save() self.col.models.save()
self.col.sched.reset() self.col.sched.reset()
@ -202,22 +186,10 @@ class Syncer:
len(self.col.decks.allConf()), 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): def usnLim(self):
if self.col.server: return "usn = -1"
return "usn >= %d" % self.minUsn
else:
return "usn = -1"
def finish(self, mod=None): def finish(self, mod=None):
if not mod:
# server side; we decide new mod time
mod = intTime(1000)
self.col.ls = mod self.col.ls = mod
self.col._usn = self.maxUsn + 1 self.col._usn = self.maxUsn + 1
# ensure we save the mod time even if no changes made # ensure we save the mod time even if no changes made
@ -262,11 +234,10 @@ from notes where %s""" % d)
# table is empty # table is empty
self.tablesLeft.pop(0) self.tablesLeft.pop(0)
self.cursor = None self.cursor = None
# if we're the client, mark the objects as having been sent # mark the objects as having been sent
if not self.col.server: self.col.db.execute(
self.col.db.execute( "update %s set usn=? where usn=-1"%curTable,
"update %s set usn=? where usn=-1"%curTable, self.maxUsn)
self.maxUsn)
buf[curTable] = rows buf[curTable] = rows
lim -= fetched lim -= fetched
if not self.tablesLeft: if not self.tablesLeft:
@ -288,12 +259,10 @@ from notes where %s""" % d)
cards = [] cards = []
notes = [] notes = []
decks = [] decks = []
if self.col.server:
curs = self.col.db.execute( curs = self.col.db.execute(
"select oid, type from graves where usn >= ?", self.minUsn) "select oid, type from graves where usn = -1")
else:
curs = self.col.db.execute(
"select oid, type from graves where usn = -1")
for oid, type in curs: for oid, type in curs:
if type == REM_CARD: if type == REM_CARD:
cards.append(oid) cards.append(oid)
@ -301,23 +270,16 @@ from notes where %s""" % d)
notes.append(oid) notes.append(oid)
else: else:
decks.append(oid) 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.col.db.execute("update graves set usn=? where usn=-1",
self.maxUsn = self.col._usn self.maxUsn)
self.minUsn = minUsn
self.lnewer = not lnewer return dict(cards=cards, notes=notes, decks=decks)
lgraves = self.removed()
self.remove(graves)
return lgraves
def remove(self, graves): def remove(self, graves):
# pretend to be the server so we don't set usn = -1 # pretend to be the server so we don't set usn = -1
wasServer = self.col.server
self.col.server = True self.col.server = True
# notes first, so we don't end up with duplicate graves # notes first, so we don't end up with duplicate graves
self.col._remNotes(graves['notes']) self.col._remNotes(graves['notes'])
# then cards # then cards
@ -325,20 +287,18 @@ from notes where %s""" % d)
# and decks # and decks
for oid in graves['decks']: for oid in graves['decks']:
self.col.decks.rem(oid, childrenToo=False) self.col.decks.rem(oid, childrenToo=False)
self.col.server = wasServer
self.col.server = False
# Models # Models
########################################################################## ##########################################################################
def getModels(self): def getModels(self):
if self.col.server: mods = [m for m in self.col.models.all() if m['usn'] == -1]
return [m for m in self.col.models.all() if m['usn'] >= self.minUsn] for m in mods:
else: m['usn'] = self.maxUsn
mods = [m for m in self.col.models.all() if m['usn'] == -1] self.col.models.save()
for m in mods: return mods
m['usn'] = self.maxUsn
self.col.models.save()
return mods
def mergeModels(self, rchg): def mergeModels(self, rchg):
for r in rchg: for r in rchg:
@ -351,20 +311,14 @@ from notes where %s""" % d)
########################################################################## ##########################################################################
def getDecks(self): def getDecks(self):
if self.col.server: decks = [g for g in self.col.decks.all() if g['usn'] == -1]
return [ for g in decks:
[g for g in self.col.decks.all() if g['usn'] >= self.minUsn], g['usn'] = self.maxUsn
[g for g in self.col.decks.allConf() if g['usn'] >= self.minUsn] dconf = [g for g in self.col.decks.allConf() if g['usn'] == -1]
] for g in dconf:
else: g['usn'] = self.maxUsn
decks = [g for g in self.col.decks.all() if g['usn'] == -1] self.col.decks.save()
for g in decks: return [decks, dconf]
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): def mergeDecks(self, rchg):
for r in rchg[0]: for r in rchg[0]:
@ -389,17 +343,13 @@ from notes where %s""" % d)
########################################################################## ##########################################################################
def getTags(self): def getTags(self):
if self.col.server: tags = []
return [t for t, usn in self.col.tags.allItems() for t, usn in self.col.tags.allItems():
if usn >= self.minUsn] if usn == -1:
else: self.col.tags.tags[t] = self.maxUsn
tags = [] tags.append(t)
for t, usn in self.col.tags.allItems(): self.col.tags.save()
if usn == -1: return tags
self.col.tags.tags[t] = self.maxUsn
tags.append(t)
self.col.tags.save()
return tags
def mergeTags(self, tags): def mergeTags(self, tags):
self.col.tags.register(tags, usn=self.maxUsn) self.col.tags.register(tags, usn=self.maxUsn)
@ -448,17 +398,6 @@ from notes where %s""" % d)
def mergeConf(self, conf): def mergeConf(self, conf):
self.col.conf = 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 # Wrapper for requests that tracks upload/download progress
########################################################################## ##########################################################################

View file

@ -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