diff --git a/anki/deck.py b/anki/deck.py
index 505a06050..0e674c7a8 100644
--- a/anki/deck.py
+++ b/anki/deck.py
@@ -28,6 +28,7 @@ from anki.hooks import runHook, hookEmpty
from anki.template import render
from anki.media import updateMediaCount, mediaFiles, \
rebuildMediaDir
+from anki.upgrade import upgradeSchema, updateIndices, upgradeDeck
import anki.latex # sets up hook
# ensure all the DB metadata in other files is loaded before proceeding
@@ -3155,7 +3156,7 @@ where id = :id""", fid=f.id, cmid=m.cardModels[0].id, id=id)
"Please restore from automatic backup (see FAQ).")
# ensure correct views and indexes are available
self.updateProgress()
- DeckStorage._addIndices(self)
+ updateIndices(self)
# does the user have a model?
self.updateProgress()
if not self.s.scalar("select count(id) from models"):
@@ -3556,15 +3557,7 @@ class DeckStorage(object):
metadata.create_all(engine)
deck = DeckStorage._init(s)
else:
- ver = s.scalar("select version from decks limit 1")
- # add a checksum to fields
- if ver < 71:
- try:
- s.execute(
- "alter table fields add column chksum text "+
- "not null default ''")
- except:
- pass
+ ver = upgradeSchema(s)
# add any possibly new tables if we're upgrading
if ver < DECK_VERSION:
metadata.create_all(engine)
@@ -3589,10 +3582,7 @@ class DeckStorage(object):
deck.s = SessionHelper(s, lock=lock)
# force a write lock
deck.s.execute("update decks set modified = modified")
- needUnpack = False
- if deck.utcOffset in (-1, -2):
- # do the rest later
- needUnpack = deck.utcOffset == -1
+ if deck.utcOffset == -2:
# make sure we do this before initVars
DeckStorage._setUTCOffset(deck)
deck.created = time.time()
@@ -3606,19 +3596,14 @@ class DeckStorage(object):
deck.s.execute("vacuum")
# add tags and indices
initTagTables(deck.s)
- DeckStorage._addIndices(deck)
+ updateIndices(deck)
deck.s.statement("analyze")
deck._initVars()
else:
if backup:
DeckStorage.backup(deck, path)
deck._initVars()
- try:
- deck = DeckStorage._upgradeDeck(deck, path)
- except:
- traceback.print_exc()
- deck.fixIntegrity()
- deck = DeckStorage._upgradeDeck(deck, path)
+ upgradeDeck(deck)
except OperationalError, e:
engine.dispose()
if (str(e.orig).startswith("database table is locked") or
@@ -3632,12 +3617,6 @@ class DeckStorage(object):
deck._globalStats = globalStats(deck)
deck._dailyStats = dailyStats(deck)
return deck
- if needUnpack:
- deck.startProgress()
- DeckStorage._addIndices(deck)
- for m in deck.models:
- deck.updateCardsFromModel(m)
- deck.finishProgress()
oldMod = deck.modified
# fix a bug with current model being unset
if not deck.currentModel and deck.models:
@@ -3700,236 +3679,6 @@ class DeckStorage(object):
return deck
_init = staticmethod(_init)
- def _addIndices(deck):
- "Add indices to the DB."
- # counts, failed cards
- deck.s.statement("""
-create index if not exists ix_cards_typeCombined on cards
-(type, combinedDue, factId)""")
- # scheduler-agnostic type
- deck.s.statement("""
-create index if not exists ix_cards_relativeDelay on cards
-(relativeDelay)""")
- # index on modified, to speed up sync summaries
- deck.s.statement("""
-create index if not exists ix_cards_modified on cards
-(modified)""")
- deck.s.statement("""
-create index if not exists ix_facts_modified on facts
-(modified)""")
- # priority - temporary index to make compat code faster. this can be
- # removed when all clients are on 1.2, as can the ones below
- deck.s.statement("""
-create index if not exists ix_cards_priority on cards
-(priority)""")
- # average factor
- deck.s.statement("""
-create index if not exists ix_cards_factor on cards
-(type, factor)""")
- # card spacing
- deck.s.statement("""
-create index if not exists ix_cards_factId on cards (factId)""")
- # stats
- deck.s.statement("""
-create index if not exists ix_stats_typeDay on stats (type, day)""")
- # fields
- deck.s.statement("""
-create index if not exists ix_fields_factId on fields (factId)""")
- deck.s.statement("""
-create index if not exists ix_fields_fieldModelId on fields (fieldModelId)""")
- deck.s.statement("""
-create index if not exists ix_fields_chksum on fields (chksum)""")
- # media
- deck.s.statement("""
-create unique index if not exists ix_media_filename on media (filename)""")
- deck.s.statement("""
-create index if not exists ix_media_originalPath on media (originalPath)""")
- # deletion tracking
- deck.s.statement("""
-create index if not exists ix_cardsDeleted_cardId on cardsDeleted (cardId)""")
- deck.s.statement("""
-create index if not exists ix_modelsDeleted_modelId on modelsDeleted (modelId)""")
- deck.s.statement("""
-create index if not exists ix_factsDeleted_factId on factsDeleted (factId)""")
- deck.s.statement("""
-create index if not exists ix_mediaDeleted_factId on mediaDeleted (mediaId)""")
- # tags
- txt = "create unique index if not exists ix_tags_tag on tags (tag)"
- try:
- deck.s.statement(txt)
- except:
- deck.s.statement("""
-delete from tags where exists (select 1 from tags t2 where tags.tag = t2.tag
-and tags.rowid > t2.rowid)""")
- deck.s.statement(txt)
- deck.s.statement("""
-create index if not exists ix_cardTags_tagCard on cardTags (tagId, cardId)""")
- deck.s.statement("""
-create index if not exists ix_cardTags_cardId on cardTags (cardId)""")
- _addIndices = staticmethod(_addIndices)
-
- def _upgradeDeck(deck, path):
- "Upgrade deck to the latest version."
- if deck.version < DECK_VERSION:
- prog = True
- deck.startProgress()
- deck.updateProgress(_("Upgrading Deck..."))
- if deck.utcOffset == -1:
- # we're opening a shared deck with no indices - we'll need
- # them if we want to rebuild the queue
- DeckStorage._addIndices(deck)
- oldmod = deck.modified
- else:
- prog = False
- deck.path = path
- if deck.version < 43:
- raise Exception("oldDeckVersion")
- if deck.version < 44:
- # leaner indices
- deck.s.statement("drop index if exists ix_cards_factId")
- deck.version = 44
- deck.s.commit()
- if deck.version < 48:
- deck.updateFieldCache(deck.s.column0("select id from facts"))
- deck.version = 48
- deck.s.commit()
- if deck.version < 52:
- dname = deck.name()
- sname = deck.syncName
- if sname and dname != sname:
- deck.notify(_("""\
-When syncing, Anki now uses the same deck name on the server as the deck \
-name on your computer. Because you had '%(dname)s' set to sync to \
-'%(sname)s' on the server, syncing has been temporarily disabled.
-
-If you want to keep your changes to the online version, please use \
-File>Download>Personal Deck to download the online version.
-
-If you want to keep the version on your computer, please enable \
-syncing again via Settings>Deck Properties>Synchronisation.
-
-If you have syncing disabled in the preferences, you can ignore \
-this message. (ERR-0101)""") % {
- 'sname':sname, 'dname':dname})
- deck.disableSyncing()
- elif sname:
- deck.enableSyncing()
- deck.version = 52
- deck.s.commit()
- if deck.version < 53:
- if deck.getBool("perDay"):
- if deck.hardIntervalMin == 0.333:
- deck.hardIntervalMin = max(1.0, deck.hardIntervalMin)
- deck.hardIntervalMax = max(1.1, deck.hardIntervalMax)
- deck.version = 53
- deck.s.commit()
- if deck.version < 54:
- # broken versions of the DB orm die if this is a bool with a
- # non-int value
- deck.s.statement("update fieldModels set editFontFamily = 1");
- deck.version = 54
- deck.s.commit()
- if deck.version < 61:
- # do our best to upgrade templates to the new style
- txt = '''\
-%s'''
- for m in deck.models:
- unstyled = []
- for fm in m.fieldModels:
- # find which fields had explicit formatting
- if fm.quizFontFamily or fm.quizFontSize or fm.quizFontColour:
- pass
- else:
- unstyled.append(fm.name)
- # fill out missing info
- fm.quizFontFamily = fm.quizFontFamily or u"Arial"
- fm.quizFontSize = fm.quizFontSize or 20
- fm.quizFontColour = fm.quizFontColour or "#000000"
- fm.editFontSize = fm.editFontSize or 20
- unstyled = set(unstyled)
- for cm in m.cardModels:
- # embed the old font information into card templates
- cm.qformat = txt % (
- cm.questionFontFamily,
- cm.questionFontSize,
- cm.questionFontColour,
- cm.qformat)
- cm.aformat = txt % (
- cm.answerFontFamily,
- cm.answerFontSize,
- cm.answerFontColour,
- cm.aformat)
- # escape fields that had no previous styling
- for un in unstyled:
- cm.qformat = cm.qformat.replace("%("+un+")s", "{{{%s}}}"%un)
- cm.aformat = cm.aformat.replace("%("+un+")s", "{{{%s}}}"%un)
- # rebuild q/a for the above & because latex has changed
- for m in deck.models:
- deck.updateCardsFromModel(m, dirty=False)
- # rebuild the media db based on new format
- rebuildMediaDir(deck, dirty=False)
- deck.version = 61
- deck.s.commit()
- if deck.version < 62:
- # updated indices
- deck.s.statement("drop index if exists ix_cards_typeCombined")
- DeckStorage._addIndices(deck)
- deck.version = 62
- deck.s.commit()
- if deck.version < 64:
- # remove old static indices, as all clients should be libanki1.2+
- for d in ("ix_cards_duePriority",
- "ix_cards_priorityDue"):
- deck.s.statement("drop index if exists %s" % d)
- deck.version = 64
- deck.s.commit()
- # note: we keep the priority index for now
- if deck.version < 65:
- # we weren't correctly setting relativeDelay when answering cards
- # in previous versions, so ensure everything is set correctly
- deck.rebuildTypes()
- deck.version = 65
- deck.s.commit()
- # skip a few to allow for updates to stable tree
- if deck.version < 70:
- # update dynamic indices given we don't use priority anymore
- for d in ("intervalDesc", "intervalAsc", "randomOrder",
- "dueAsc", "dueDesc"):
- deck.s.statement("drop index if exists ix_cards_%s2" % d)
- deck.s.statement("drop index if exists ix_cards_%s" % d)
- deck.updateDynamicIndices()
- # remove old views
- for v in ("failedCards", "revCardsOld", "revCardsNew",
- "revCardsDue", "revCardsRandom", "acqCardsRandom",
- "acqCardsOld", "acqCardsNew"):
- deck.s.statement("drop view if exists %s" % v)
- deck.version = 70
- deck.s.commit()
- if deck.version < 71:
- # remove the expensive value cache
- deck.s.statement("drop index if exists ix_fields_value")
- # add checksums and index
- deck.updateAllFieldChecksums()
- DeckStorage._addIndices(deck)
- deck.s.execute("vacuum")
- deck.s.execute("analyze")
- deck.version = 71
- deck.s.commit()
- # executing a pragma here is very slow on large decks, so we store
- # our own record
- if not deck.getInt("pageSize") == 4096:
- deck.s.commit()
- deck.s.execute("pragma page_size = 4096")
- deck.s.execute("pragma legacy_file_format = 0")
- deck.s.execute("vacuum")
- deck.setVar("pageSize", 4096, mod=False)
- deck.s.commit()
- if prog:
- assert deck.modified == oldmod
- deck.finishProgress()
- return deck
- _upgradeDeck = staticmethod(_upgradeDeck)
-
def _setUTCOffset(deck):
# 4am
deck.utcOffset = time.timezone + 60*60*4
diff --git a/anki/upgrade.py b/anki/upgrade.py
new file mode 100644
index 000000000..09da06086
--- /dev/null
+++ b/anki/upgrade.py
@@ -0,0 +1,243 @@
+# -*- coding: utf-8 -*-
+# Copyright: Damien Elmes
+# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
+
+def upgradeSchema(s):
+ "Alter tables prior to ORM initialization."
+ ver = s.scalar("select version from decks limit 1")
+ # add a checksum to fields
+ if ver < 71:
+ try:
+ s.execute(
+ "alter table fields add column chksum text "+
+ "not null default ''")
+ except:
+ pass
+ return ver
+
+def updateIndices(deck):
+ "Add indices to the DB."
+ # counts, failed cards
+ deck.s.statement("""
+create index if not exists ix_cards_typeCombined on cards
+(type, combinedDue, factId)""")
+ # scheduler-agnostic type
+ deck.s.statement("""
+create index if not exists ix_cards_relativeDelay on cards
+(relativeDelay)""")
+ # index on modified, to speed up sync summaries
+ deck.s.statement("""
+create index if not exists ix_cards_modified on cards
+(modified)""")
+ deck.s.statement("""
+create index if not exists ix_facts_modified on facts
+(modified)""")
+ # priority - temporary index to make compat code faster. this can be
+ # removed when all clients are on 1.2, as can the ones below
+ deck.s.statement("""
+create index if not exists ix_cards_priority on cards
+(priority)""")
+ # average factor
+ deck.s.statement("""
+create index if not exists ix_cards_factor on cards
+(type, factor)""")
+ # card spacing
+ deck.s.statement("""
+create index if not exists ix_cards_factId on cards (factId)""")
+ # stats
+ deck.s.statement("""
+create index if not exists ix_stats_typeDay on stats (type, day)""")
+ # fields
+ deck.s.statement("""
+create index if not exists ix_fields_factId on fields (factId)""")
+ deck.s.statement("""
+create index if not exists ix_fields_fieldModelId on fields (fieldModelId)""")
+ deck.s.statement("""
+create index if not exists ix_fields_chksum on fields (chksum)""")
+ # media
+ deck.s.statement("""
+create unique index if not exists ix_media_filename on media (filename)""")
+ deck.s.statement("""
+create index if not exists ix_media_originalPath on media (originalPath)""")
+ # deletion tracking
+ deck.s.statement("""
+create index if not exists ix_cardsDeleted_cardId on cardsDeleted (cardId)""")
+ deck.s.statement("""
+create index if not exists ix_modelsDeleted_modelId on modelsDeleted (modelId)""")
+ deck.s.statement("""
+create index if not exists ix_factsDeleted_factId on factsDeleted (factId)""")
+ deck.s.statement("""
+create index if not exists ix_mediaDeleted_factId on mediaDeleted (mediaId)""")
+ # tags
+ txt = "create unique index if not exists ix_tags_tag on tags (tag)"
+ try:
+ deck.s.statement(txt)
+ except:
+ deck.s.statement("""
+delete from tags where exists (select 1 from tags t2 where tags.tag = t2.tag
+and tags.rowid > t2.rowid)""")
+ deck.s.statement(txt)
+ deck.s.statement("""
+create index if not exists ix_cardTags_tagCard on cardTags (tagId, cardId)""")
+ deck.s.statement("""
+create index if not exists ix_cardTags_cardId on cardTags (cardId)""")
+
+def upgradeDeck(deck):
+ "Upgrade deck to the latest version."
+ from anki.deck import DECK_VERSION
+ if deck.version < DECK_VERSION:
+ prog = True
+ deck.startProgress()
+ deck.updateProgress(_("Upgrading Deck..."))
+ if deck.utcOffset == -1:
+ # we're opening a shared deck with no indices - we'll need
+ # them if we want to rebuild the queue
+ updateIndices(deck)
+ oldmod = deck.modified
+ else:
+ prog = False
+ if deck.version < 43:
+ raise Exception("oldDeckVersion")
+ if deck.version < 44:
+ # leaner indices
+ deck.s.statement("drop index if exists ix_cards_factId")
+ deck.version = 44
+ deck.s.commit()
+ if deck.version < 48:
+ deck.updateFieldCache(deck.s.column0("select id from facts"))
+ deck.version = 48
+ deck.s.commit()
+ if deck.version < 52:
+ dname = deck.name()
+ sname = deck.syncName
+ if sname and dname != sname:
+ deck.notify(_("""\
+When syncing, Anki now uses the same deck name on the server as the deck \
+name on your computer. Because you had '%(dname)s' set to sync to \
+'%(sname)s' on the server, syncing has been temporarily disabled.
+
+If you want to keep your changes to the online version, please use \
+File>Download>Personal Deck to download the online version.
+
+If you want to keep the version on your computer, please enable \
+syncing again via Settings>Deck Properties>Synchronisation.
+
+If you have syncing disabled in the preferences, you can ignore \
+this message. (ERR-0101)""") % {
+ 'sname':sname, 'dname':dname})
+ deck.disableSyncing()
+ elif sname:
+ deck.enableSyncing()
+ deck.version = 52
+ deck.s.commit()
+ if deck.version < 53:
+ if deck.getBool("perDay"):
+ if deck.hardIntervalMin == 0.333:
+ deck.hardIntervalMin = max(1.0, deck.hardIntervalMin)
+ deck.hardIntervalMax = max(1.1, deck.hardIntervalMax)
+ deck.version = 53
+ deck.s.commit()
+ if deck.version < 54:
+ # broken versions of the DB orm die if this is a bool with a
+ # non-int value
+ deck.s.statement("update fieldModels set editFontFamily = 1");
+ deck.version = 54
+ deck.s.commit()
+ if deck.version < 61:
+ # do our best to upgrade templates to the new style
+ txt = '''\
+%s'''
+ for m in deck.models:
+ unstyled = []
+ for fm in m.fieldModels:
+ # find which fields had explicit formatting
+ if fm.quizFontFamily or fm.quizFontSize or fm.quizFontColour:
+ pass
+ else:
+ unstyled.append(fm.name)
+ # fill out missing info
+ fm.quizFontFamily = fm.quizFontFamily or u"Arial"
+ fm.quizFontSize = fm.quizFontSize or 20
+ fm.quizFontColour = fm.quizFontColour or "#000000"
+ fm.editFontSize = fm.editFontSize or 20
+ unstyled = set(unstyled)
+ for cm in m.cardModels:
+ # embed the old font information into card templates
+ cm.qformat = txt % (
+ cm.questionFontFamily,
+ cm.questionFontSize,
+ cm.questionFontColour,
+ cm.qformat)
+ cm.aformat = txt % (
+ cm.answerFontFamily,
+ cm.answerFontSize,
+ cm.answerFontColour,
+ cm.aformat)
+ # escape fields that had no previous styling
+ for un in unstyled:
+ cm.qformat = cm.qformat.replace("%("+un+")s", "{{{%s}}}"%un)
+ cm.aformat = cm.aformat.replace("%("+un+")s", "{{{%s}}}"%un)
+ # rebuild q/a for the above & because latex has changed
+ for m in deck.models:
+ deck.updateCardsFromModel(m, dirty=False)
+ # rebuild the media db based on new format
+ rebuildMediaDir(deck, dirty=False)
+ deck.version = 61
+ deck.s.commit()
+ if deck.version < 62:
+ # updated indices
+ deck.s.statement("drop index if exists ix_cards_typeCombined")
+ updateIndices(deck)
+ deck.version = 62
+ deck.s.commit()
+ if deck.version < 64:
+ # remove old static indices, as all clients should be libanki1.2+
+ for d in ("ix_cards_duePriority",
+ "ix_cards_priorityDue"):
+ deck.s.statement("drop index if exists %s" % d)
+ deck.version = 64
+ deck.s.commit()
+ # note: we keep the priority index for now
+ if deck.version < 65:
+ # we weren't correctly setting relativeDelay when answering cards
+ # in previous versions, so ensure everything is set correctly
+ deck.rebuildTypes()
+ deck.version = 65
+ deck.s.commit()
+ # skip a few to allow for updates to stable tree
+ if deck.version < 70:
+ # update dynamic indices given we don't use priority anymore
+ for d in ("intervalDesc", "intervalAsc", "randomOrder",
+ "dueAsc", "dueDesc"):
+ deck.s.statement("drop index if exists ix_cards_%s2" % d)
+ deck.s.statement("drop index if exists ix_cards_%s" % d)
+ deck.updateDynamicIndices()
+ # remove old views
+ for v in ("failedCards", "revCardsOld", "revCardsNew",
+ "revCardsDue", "revCardsRandom", "acqCardsRandom",
+ "acqCardsOld", "acqCardsNew"):
+ deck.s.statement("drop view if exists %s" % v)
+ deck.version = 70
+ deck.s.commit()
+ if deck.version < 71:
+ # remove the expensive value cache
+ deck.s.statement("drop index if exists ix_fields_value")
+ # add checksums and index
+ deck.updateAllFieldChecksums()
+ updateIndices(deck)
+ deck.s.execute("vacuum")
+ deck.s.execute("analyze")
+ deck.version = 71
+ deck.s.commit()
+ # executing a pragma here is very slow on large decks, so we store
+ # our own record
+ if not deck.getInt("pageSize") == 4096:
+ deck.s.commit()
+ deck.s.execute("pragma page_size = 4096")
+ deck.s.execute("pragma legacy_file_format = 0")
+ deck.s.execute("vacuum")
+ deck.setVar("pageSize", 4096, mod=False)
+ deck.s.commit()
+ if prog:
+ assert deck.modified == oldmod
+ deck.finishProgress()