diff --git a/anki/cards.py b/anki/cards.py index 90c9d6b74..b04a1b510 100644 --- a/anki/cards.py +++ b/anki/cards.py @@ -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 diff --git a/anki/consts.py b/anki/consts.py index faf1d24a3..f587a6960 100644 --- a/anki/consts.py +++ b/anki/consts.py @@ -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 ########################################################################## diff --git a/anki/deck.py b/anki/deck.py index 713fbe7d9..0402d7c42 100644 --- a/anki/deck.py +++ b/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() diff --git a/anki/storage.py b/anki/storage.py index d8def5625..679d43e63 100644 --- a/anki/storage.py +++ b/anki/storage.py @@ -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, diff --git a/tests/test_cards.py b/tests/test_cards.py index 134a126fd..d4c7ace5d 100644 --- a/tests/test_cards.py +++ b/tests/test_cards.py @@ -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() diff --git a/tests/test_deck.py b/tests/test_deck.py index b5056fc83..3a76dc71b 100644 --- a/tests/test_deck.py +++ b/tests/test_deck.py @@ -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