diff --git a/anki/deck.py b/anki/deck.py index 288b8ae2f..ffc70ee38 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -2263,6 +2263,7 @@ Return new path, relative to media dir.""" self.s.flush() def saveAs(self, newPath): + "Returns new deck. Old connection is closed without saving." oldMediaDir = self.mediaDir() self.s.flush() # remove new deck if it exists @@ -2270,43 +2271,32 @@ Return new path, relative to media dir.""" os.unlink(newPath) except OSError: pass - # setup new db tables, then close - newDeck = DeckStorage.Deck(newPath) - newDeck.close() - # attach new db and copy everything in - s = self.s.statement - s("attach database :path as new", path=newPath) - s("delete from new.decks") - s("delete from new.stats") - s("delete from new.tags") - s("delete from new.cardTags") - s("delete from new.deckVars") - s("insert into new.decks select * from decks") - s("insert into new.fieldModels select * from fieldModels") - s("insert into new.modelsDeleted select * from modelsDeleted") - s("insert into new.cardModels select * from cardModels") - s("insert into new.facts select * from facts") - s("insert into new.fields select * from fields") - s("insert into new.cards select * from cards") - s("insert into new.factsDeleted select * from factsDeleted") - s("insert into new.reviewHistory select * from reviewHistory") - s("insert into new.cardsDeleted select * from cardsDeleted") - s("insert into new.models select * from models") - s("insert into new.stats select * from stats") - s("insert into new.media select * from media") - s("insert into new.tags select * from tags") - s("insert into new.cardTags select * from cardTags") - s("insert into new.deckVars select * from deckVars") - s("detach database new") - # close ourselves - self.s.commit() + # copy tables, avoiding implicit commit on current db + DeckStorage.Deck(newPath).close() + new = sqlite.connect(newPath) + for table in self.s.column0( + "select name from sqlite_master where type = 'table'"): + if table.startswith("sqlite_"): + continue + new.execute("delete from %s" % table) + cols = [str(x[1]) for x in new.execute( + "pragma table_info('%s')" % table).fetchall()] + q = "select 'insert into %(table)s values(" + q += ",".join(["'||quote(\"" + col + "\")||'" for col in cols]) + q += ")' from %(table)s" + q = q % {'table': table} + for row in self.s.execute(q): + new.execute(row[0]) + # save new, close both + new.commit() + new.close() self.close() - # open new db + # open again in orm newDeck = DeckStorage.Deck(newPath) # move media if oldMediaDir: newDeck.renameMediaDir(oldMediaDir) - # and return the new deck object + # and return the new deck return newDeck # DB maintenance diff --git a/tests/test_deck.py b/tests/test_deck.py index 6e0a3ecb3..3933f3c69 100644 --- a/tests/test_deck.py +++ b/tests/test_deck.py @@ -61,17 +61,33 @@ def test_saveAs(): os.unlink(path) except OSError: pass + path2 = "/tmp/test_saveAs2.anki" + try: + os.unlink(path2) + except OSError: + pass + # start with an in-memory deck deck = DeckStorage.Deck() deck.addModel(BasicModel()) # add a card f = deck.newFact() f['Front'] = u"foo"; f['Back'] = u"bar" deck.addFact(f) + assert deck.cardCount == 1 # save in new deck newDeck = deck.saveAs(path) assert newDeck.cardCount == 1 + # delete card + id = newDeck.s.scalar("select id from cards") + newDeck.deleteCard(id) + # save into new deck + newDeck2 = newDeck.saveAs(path2) + # new deck should have zero cards + assert newDeck2.cardCount == 0 + # but old deck should have reverted the unsaved changes + newDeck = DeckStorage.Deck(path) + assert newDeck.cardCount == 1 newDeck.close() - deck.close() def test_factAddDelete(): deck = DeckStorage.Deck()