mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 15:32:23 -04:00
restore the deletion log
the initial plan was to zero the creation time and leave the cards/facts there until we have a chance to garbage collect them on a schema change, but such an approach won't work with deck subscriptions
This commit is contained in:
parent
f2604b7805
commit
3d370f675b
6 changed files with 39 additions and 62 deletions
|
@ -10,7 +10,7 @@ from anki.utils import intTime, hexifyID
|
|||
|
||||
# Type: 0=new, 1=learning, 2=due
|
||||
# Queue: same as above, and:
|
||||
# -1=suspended, -2=user buried, -3=sched buried, -4=deleted
|
||||
# -1=suspended, -2=user buried, -3=sched buried
|
||||
# Due is used differently for different queues.
|
||||
# - new queue: fact id or random int
|
||||
# - rev queue: integer day
|
||||
|
|
|
@ -20,6 +20,10 @@ REV_CARDS_OLD_FIRST = 0
|
|||
REV_CARDS_NEW_FIRST = 1
|
||||
REV_CARDS_RANDOM = 2
|
||||
|
||||
# deletion types
|
||||
DEL_CARD = 0
|
||||
DEL_FACT = 1
|
||||
|
||||
# Labels
|
||||
##########################################################################
|
||||
|
||||
|
|
63
anki/deck.py
63
anki/deck.py
|
@ -152,8 +152,6 @@ qconf=?, conf=?, data=?""",
|
|||
if not self.schemaChanged():
|
||||
if check and not runFilter("modSchema", True):
|
||||
raise AnkiError("abortSchemaMod")
|
||||
# next sync will be full
|
||||
self.emptyTrash()
|
||||
self.scm = intTime()
|
||||
|
||||
def schemaChanged(self):
|
||||
|
@ -213,11 +211,18 @@ qconf=?, conf=?, data=?""",
|
|||
self.modelCache = {}
|
||||
self.sched.reset()
|
||||
|
||||
# Deletion logging
|
||||
##########################################################################
|
||||
|
||||
def _logDels(self, ids, type):
|
||||
self.db.executemany("insert into graves values (%d, ?, %d)" % (
|
||||
intTime(), type), ([x] for x in ids))
|
||||
|
||||
# Facts
|
||||
##########################################################################
|
||||
|
||||
def factCount(self):
|
||||
return self.db.scalar("select count() from facts where crt != 0")
|
||||
return self.db.scalar("select count() from facts")
|
||||
|
||||
def newFact(self):
|
||||
"Return a new fact with the current model."
|
||||
|
@ -251,13 +256,7 @@ qconf=?, conf=?, data=?""",
|
|||
strids = ids2str(ids)
|
||||
self.db.execute("delete from facts where id in %s" % strids)
|
||||
self.db.execute("delete from fsums where fid in %s" % strids)
|
||||
|
||||
def _delDanglingFacts(self):
|
||||
"Delete any facts without cards. Don't call this directly."
|
||||
ids = self.db.list("""
|
||||
select id from facts where id not in (select distinct fid from cards)""")
|
||||
self._delFacts(ids)
|
||||
return ids
|
||||
self._logDels(ids, DEL_FACT)
|
||||
|
||||
# Card creation
|
||||
##########################################################################
|
||||
|
@ -341,41 +340,23 @@ select id from facts where id not in (select distinct fid from cards)""")
|
|||
##########################################################################
|
||||
|
||||
def cardCount(self):
|
||||
return self.db.scalar("select count() from cards where crt != 0")
|
||||
|
||||
def delCard(self, id):
|
||||
"Delete a card given its id. Delete any unused facts."
|
||||
self.delCards([id])
|
||||
return self.db.scalar("select count() from cards")
|
||||
|
||||
def delCards(self, ids):
|
||||
"Bulk delete cards by ID."
|
||||
if not ids:
|
||||
return
|
||||
sids = ids2str(ids)
|
||||
if self.schemaChanged():
|
||||
# immediate delete?
|
||||
self.db.execute("delete from cards where id in %s" % sids)
|
||||
self.db.execute("delete from revlog where cid in %s" % sids)
|
||||
# remove any dangling facts
|
||||
self._delDanglingFacts()
|
||||
else:
|
||||
# trash
|
||||
sfids = ids2str(
|
||||
self.db.list("select fid from cards where id in "+sids))
|
||||
# need to handle delete of fsums/revlog remotely after sync
|
||||
self.db.execute(
|
||||
"update cards set crt = 0, queue = -4, mod = ? where id in "+sids,
|
||||
intTime())
|
||||
self.db.execute(
|
||||
"update facts set crt = 0, mod = ? where id in "+sfids,
|
||||
intTime())
|
||||
self.db.execute("delete from fsums where fid in "+sfids)
|
||||
self.db.execute("delete from revlog where cid in "+sids)
|
||||
|
||||
def emptyTrash(self):
|
||||
self.db.executescript("""
|
||||
delete from facts where id in (select fid from cards where queue = -4);
|
||||
delete from cards where queue = -4;""")
|
||||
fids = self.db.list("select fid from cards where id in "+sids)
|
||||
# remove cards
|
||||
self.db.execute("delete from cards where id in "+sids)
|
||||
self.db.execute("delete from revlog where cid in "+sids)
|
||||
self._logDels(ids, DEL_CARD)
|
||||
# then facts
|
||||
fids = self.db.list("""
|
||||
select id from facts where id in %s and id not in (select fid from cards)""" %
|
||||
ids2str(fids))
|
||||
self._delFacts(fids)
|
||||
|
||||
# Models
|
||||
##########################################################################
|
||||
|
@ -783,6 +764,10 @@ update facts set tags = :t, mod = :n where id = :id""", [fix(row) for row in res
|
|||
problems = []
|
||||
self.save()
|
||||
oldSize = os.stat(self.path)[stat.ST_SIZE]
|
||||
# delete any facts with missing cards
|
||||
ids = self.db.list("""
|
||||
select id from facts where id not in (select distinct fid from cards)""")
|
||||
self._delFacts(ids)
|
||||
# tags
|
||||
self.db.execute("delete from tags")
|
||||
self.updateFactTags()
|
||||
|
|
|
@ -135,6 +135,12 @@ create table if not exists gconf (
|
|||
conf text not null
|
||||
);
|
||||
|
||||
create table if not exists graves (
|
||||
time integer not null,
|
||||
oid integer not null,
|
||||
type integer not null
|
||||
);
|
||||
|
||||
create table if not exists revlog (
|
||||
time integer primary key,
|
||||
cid integer not null,
|
||||
|
|
|
@ -53,12 +53,10 @@ def test_delete():
|
|||
f['Back'] = u'2'
|
||||
deck.addFact(f)
|
||||
cid = f.cards()[0].id
|
||||
# when the schema is dirty, deletion should be immediate
|
||||
assert deck.schemaChanged() == True
|
||||
deck.reset()
|
||||
deck.sched.answerCard(deck.sched.getCard(), 2)
|
||||
assert deck.db.scalar("select count() from revlog") == 1
|
||||
deck.delCard(cid)
|
||||
deck.delCards([cid])
|
||||
assert deck.cardCount() == 0
|
||||
assert deck.factCount() == 0
|
||||
assert deck.db.scalar("select count() from facts") == 0
|
||||
|
@ -68,26 +66,10 @@ def test_delete():
|
|||
# add the fact back
|
||||
deck.addFact(f)
|
||||
assert deck.cardCount() == 1
|
||||
# mark the schema as clean
|
||||
deck.lastSync = deck.scm + 1
|
||||
# and deck as syncable
|
||||
deck.syncName = "abc"
|
||||
# cards/facts should go in the deletion log instead
|
||||
cid = f.cards()[0].id
|
||||
deck.delCard(cid)
|
||||
deck.delCards([cid])
|
||||
assert deck.cardCount() == 0
|
||||
assert deck.factCount() == 0
|
||||
assert deck.db.scalar("select count() from facts") == 1
|
||||
assert deck.db.scalar("select count() from cards") == 1
|
||||
assert deck.db.scalar("select 1 from cards where crt = 0") == 1
|
||||
assert deck.db.scalar("select 1 from facts where crt = 0") == 1
|
||||
assert deck.db.scalar("select queue from cards") == -4
|
||||
# modifying the schema should empty the trash
|
||||
deck.modSchema()
|
||||
assert deck.cardCount() == 0
|
||||
assert deck.factCount() == 0
|
||||
assert deck.db.scalar("select count() from facts") == 0
|
||||
assert deck.db.scalar("select count() from cards") == 0
|
||||
|
||||
def test_misc():
|
||||
d = getEmptyDeck()
|
||||
|
|
|
@ -74,11 +74,11 @@ def test_factAddDelete():
|
|||
id1 = cards[0].id; id2 = cards[1].id
|
||||
assert deck.cardCount() == 2
|
||||
assert deck.factCount() == 1
|
||||
deck.delCard(id1)
|
||||
deck.delCards([id1])
|
||||
assert deck.cardCount() == 1
|
||||
assert deck.factCount() == 1
|
||||
# and the second should clear the fact
|
||||
deck.delCard(id2)
|
||||
deck.delCards([id2])
|
||||
assert deck.cardCount() == 0
|
||||
assert deck.factCount() == 0
|
||||
|
||||
|
|
Loading…
Reference in a new issue