implement save as without attaching a db

Attaching new to old causes old to be implicitly commited.
We also can't access old from new if a write lock has been
taken out, so the only option left is to move all the data
through Python and take the speed hit.
This commit is contained in:
Damien Elmes 2010-02-09 01:27:49 +09:00
parent dfd88cc4c1
commit 095a7dbb6f
2 changed files with 39 additions and 33 deletions

View file

@ -2263,6 +2263,7 @@ Return new path, relative to media dir."""
self.s.flush() self.s.flush()
def saveAs(self, newPath): def saveAs(self, newPath):
"Returns new deck. Old connection is closed without saving."
oldMediaDir = self.mediaDir() oldMediaDir = self.mediaDir()
self.s.flush() self.s.flush()
# remove new deck if it exists # remove new deck if it exists
@ -2270,43 +2271,32 @@ Return new path, relative to media dir."""
os.unlink(newPath) os.unlink(newPath)
except OSError: except OSError:
pass pass
# setup new db tables, then close # copy tables, avoiding implicit commit on current db
newDeck = DeckStorage.Deck(newPath) DeckStorage.Deck(newPath).close()
newDeck.close() new = sqlite.connect(newPath)
# attach new db and copy everything in for table in self.s.column0(
s = self.s.statement "select name from sqlite_master where type = 'table'"):
s("attach database :path as new", path=newPath) if table.startswith("sqlite_"):
s("delete from new.decks") continue
s("delete from new.stats") new.execute("delete from %s" % table)
s("delete from new.tags") cols = [str(x[1]) for x in new.execute(
s("delete from new.cardTags") "pragma table_info('%s')" % table).fetchall()]
s("delete from new.deckVars") q = "select 'insert into %(table)s values("
s("insert into new.decks select * from decks") q += ",".join(["'||quote(\"" + col + "\")||'" for col in cols])
s("insert into new.fieldModels select * from fieldModels") q += ")' from %(table)s"
s("insert into new.modelsDeleted select * from modelsDeleted") q = q % {'table': table}
s("insert into new.cardModels select * from cardModels") for row in self.s.execute(q):
s("insert into new.facts select * from facts") new.execute(row[0])
s("insert into new.fields select * from fields") # save new, close both
s("insert into new.cards select * from cards") new.commit()
s("insert into new.factsDeleted select * from factsDeleted") new.close()
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()
self.close() self.close()
# open new db # open again in orm
newDeck = DeckStorage.Deck(newPath) newDeck = DeckStorage.Deck(newPath)
# move media # move media
if oldMediaDir: if oldMediaDir:
newDeck.renameMediaDir(oldMediaDir) newDeck.renameMediaDir(oldMediaDir)
# and return the new deck object # and return the new deck
return newDeck return newDeck
# DB maintenance # DB maintenance

View file

@ -61,17 +61,33 @@ def test_saveAs():
os.unlink(path) os.unlink(path)
except OSError: except OSError:
pass pass
path2 = "/tmp/test_saveAs2.anki"
try:
os.unlink(path2)
except OSError:
pass
# start with an in-memory deck
deck = DeckStorage.Deck() deck = DeckStorage.Deck()
deck.addModel(BasicModel()) deck.addModel(BasicModel())
# add a card # add a card
f = deck.newFact() f = deck.newFact()
f['Front'] = u"foo"; f['Back'] = u"bar" f['Front'] = u"foo"; f['Back'] = u"bar"
deck.addFact(f) deck.addFact(f)
assert deck.cardCount == 1
# save in new deck # save in new deck
newDeck = deck.saveAs(path) newDeck = deck.saveAs(path)
assert newDeck.cardCount == 1 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() newDeck.close()
deck.close()
def test_factAddDelete(): def test_factAddDelete():
deck = DeckStorage.Deck() deck = DeckStorage.Deck()