diff -Nru anki-2.0.31+dfsg/anki/collection.py anki-2.0.32+dfsg/anki/collection.py --- anki-2.0.31+dfsg/anki/collection.py 2014-10-09 01:32:06.000000000 +0000 +++ anki-2.0.32+dfsg/anki/collection.py 2015-03-12 01:51:47.000000000 +0000 @@ -171,7 +171,7 @@ self.load() self.lock() - def modSchema(self, check=True): + def modSchema(self, check): "Mark schema modified. Call this first so user can abort if necessary." if not self.schemaChanged(): if check and not runFilter("modSchema", True): @@ -197,7 +197,7 @@ self.models.beforeUpload() self.tags.beforeUpload() self.decks.beforeUpload() - self.modSchema() + self.modSchema(check=False) self.ls = self.scm # ensure db is compacted before upload self.db.execute("vacuum") @@ -498,6 +498,7 @@ fields['Tags'] = data[5].strip() fields['Type'] = model['name'] fields['Deck'] = self.decks.name(data[3]) + fields['Subdeck'] = fields['Deck'].split('::')[-1] if model['type'] == MODEL_STD: template = model['tmpls'][data[4]] else: diff -Nru anki-2.0.31+dfsg/anki/consts.py anki-2.0.32+dfsg/anki/consts.py --- anki-2.0.31+dfsg/anki/consts.py 2014-10-17 09:21:59.000000000 +0000 +++ anki-2.0.32+dfsg/anki/consts.py 2015-03-13 05:24:47.000000000 +0000 @@ -49,6 +49,7 @@ SYNC_ZIP_SIZE = int(2.5*1024*1024) SYNC_ZIP_COUNT = 25 SYNC_BASE = "https://ankiweb.net/" +SYNC_MEDIA_BASE = "https://msync.ankiweb.net/" SYNC_VER = 8 HELP_SITE="http://ankisrs.net/docs/manual.html" diff -Nru anki-2.0.31+dfsg/anki/decks.py anki-2.0.32+dfsg/anki/decks.py --- anki-2.0.31+dfsg/anki/decks.py 2014-08-22 13:06:13.000000000 +0000 +++ anki-2.0.32+dfsg/anki/decks.py 2015-03-12 01:51:47.000000000 +0000 @@ -344,7 +344,7 @@ def remConf(self, id): "Remove a configuration and update all decks using it." assert int(id) != 1 - self.col.modSchema() + self.col.modSchema(check=True) del self.dconf[str(id)] for g in self.all(): # ignore cram decks diff -Nru anki-2.0.31+dfsg/anki/__init__.py anki-2.0.32+dfsg/anki/__init__.py --- anki-2.0.31+dfsg/anki/__init__.py 2014-10-19 08:00:20.000000000 +0000 +++ anki-2.0.32+dfsg/anki/__init__.py 2015-03-24 14:12:39.000000000 +0000 @@ -30,6 +30,6 @@ sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( sys.version_info[1], arch[0][0:2]))) -version="2.0.31" # build scripts grep this line, so preserve formatting +version="2.0.32" # build scripts grep this line, so preserve formatting from anki.storage import Collection __all__ = ["Collection"] diff -Nru anki-2.0.31+dfsg/anki/models.py anki-2.0.32+dfsg/anki/models.py --- anki-2.0.31+dfsg/anki/models.py 2014-08-22 13:06:13.000000000 +0000 +++ anki-2.0.32+dfsg/anki/models.py 2015-03-12 01:51:47.000000000 +0000 @@ -147,7 +147,7 @@ def rem(self, m): "Delete model, and all its cards/notes." - self.col.modSchema() + self.col.modSchema(check=True) current = self.current()['id'] == m['id'] # delete notes/cards self.col.remCards(self.col.db.list(""" @@ -241,7 +241,7 @@ def setSortIdx(self, m, idx): assert idx >= 0 and idx < len(m['flds']) - self.col.modSchema() + self.col.modSchema(check=True) m['sortf'] = idx self.col.updateFieldCache(self.nids(m)) self.save(m) @@ -249,7 +249,7 @@ def addField(self, m, field): # only mod schema if model isn't new if m['id']: - self.col.modSchema() + self.col.modSchema(check=True) m['flds'].append(field) self._updateFieldOrds(m) self.save(m) @@ -259,7 +259,7 @@ self._transformFields(m, add) def remField(self, m, field): - self.col.modSchema() + self.col.modSchema(check=True) # save old sort field sortFldName = m['flds'][m['sortf']]['name'] idx = m['flds'].index(field) @@ -282,7 +282,7 @@ self.renameField(m, field, None) def moveField(self, m, field, idx): - self.col.modSchema() + self.col.modSchema(check=True) oldidx = m['flds'].index(field) if oldidx == idx: return @@ -303,8 +303,8 @@ self._transformFields(m, move) def renameField(self, m, field, newName): - self.col.modSchema() - pat = r'{{(.*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}' + self.col.modSchema(check=True) + pat = r'{{([^{}]*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}' def wrap(txt): def repl(match): return '{{' + match.group(1) + match.group(2) + txt + '}}' @@ -347,7 +347,7 @@ def addTemplate(self, m, template): "Note: should col.genCards() afterwards." if m['id']: - self.col.modSchema() + self.col.modSchema(check=True) m['tmpls'].append(template) self._updateTemplOrds(m) self.save(m) @@ -370,7 +370,7 @@ limit 1""" % ids2str(cids)): return False # ok to proceed; remove cards - self.col.modSchema() + self.col.modSchema(check=True) self.col.remCards(cids) # shift ordinals self.col.db.execute(""" @@ -414,7 +414,7 @@ # - newModel should be self if model is not changing def change(self, m, nids, newModel, fmap, cmap): - self.col.modSchema() + self.col.modSchema(check=True) assert newModel['id'] == m['id'] or (fmap and cmap) if fmap: self._changeNotes(nids, newModel, fmap) diff -Nru anki-2.0.31+dfsg/anki/sched.py anki-2.0.32+dfsg/anki/sched.py --- anki-2.0.31+dfsg/anki/sched.py 2014-10-17 15:53:22.000000000 +0000 +++ anki-2.0.32+dfsg/anki/sched.py 2015-03-19 04:25:59.000000000 +0000 @@ -975,8 +975,8 @@ self.col.db.execute(""" update cards set did = odid, queue = (case when type = 1 then 0 else type end), type = (case when type = 1 then 0 else type end), -due = odue, odue = 0, odid = 0, usn = ?, mod = ? where %s""" % lim, - self.col.usn(), intTime()) +due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim, + self.col.usn()) def remFromDyn(self, cids): self.emptyDyn(None, "id in %s and odid" % ids2str(cids)) @@ -1012,7 +1012,7 @@ t = intTime(); u = self.col.usn() for c, id in enumerate(ids): # start at -100000 so that reviews are all due - data.append((did, -100000+c, t, u, id)) + data.append((did, -100000+c, u, id)) # due reviews stay in the review queue. careful: can't use # "odid or did", as sqlite converts to boolean queue = """ @@ -1023,7 +1023,7 @@ update cards set odid = (case when odid then odid else did end), odue = (case when odue then odue else due end), -did = ?, queue = %s, due = ?, mod = ?, usn = ? where id = ?""" % queue, data) +did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data) def _dynIvlBoost(self, card): assert card.odid and card.type == 2 diff -Nru anki-2.0.31+dfsg/anki/stdmodels.py anki-2.0.32+dfsg/anki/stdmodels.py --- anki-2.0.31+dfsg/anki/stdmodels.py 2014-01-21 17:55:43.000000000 +0000 +++ anki-2.0.32+dfsg/anki/stdmodels.py 2015-03-12 01:51:47.000000000 +0000 @@ -39,7 +39,7 @@ mm.addTemplate(m, t) return m -models.append((lambda: _("Forward & Reverse"), addForwardReverse)) +models.append((lambda: _("Basic (and reversed card)"), addForwardReverse)) # Forward & Optional Reverse ########################################################################## @@ -57,7 +57,8 @@ mm.addTemplate(m, t) return m -models.append((lambda: _("Forward & Optional Reverse"), addForwardOptionalReverse)) +models.append((lambda: _("Basic (optional reversed card)"), + addForwardOptionalReverse)) # Cloze ########################################################################## diff -Nru anki-2.0.31+dfsg/anki/storage.py anki-2.0.32+dfsg/anki/storage.py --- anki-2.0.31+dfsg/anki/storage.py 2013-11-14 10:36:04.000000000 +0000 +++ anki-2.0.32+dfsg/anki/storage.py 2015-03-12 01:51:47.000000000 +0000 @@ -88,7 +88,7 @@ d['collapsed'] = False col.decks.save(d) if ver < 4: - col.modSchema() + col.modSchema(check=False) clozes = [] for m in col.models.all(): if not "{{cloze:" in m['tmpls'][0]['qfmt']: @@ -103,7 +103,7 @@ col.db.execute("update cards set odue = 0 where queue = 2") col.db.execute("update col set ver = 5") if ver < 6: - col.modSchema() + col.modSchema(check=False) import anki.models for m in col.models.all(): m['css'] = anki.models.defaultModel['css'] @@ -117,13 +117,13 @@ col.models.save(m) col.db.execute("update col set ver = 6") if ver < 7: - col.modSchema() + col.modSchema(check=False) col.db.execute( "update cards set odue = 0 where (type = 1 or queue = 2) " "and not odid") col.db.execute("update col set ver = 7") if ver < 8: - col.modSchema() + col.modSchema(check=False) col.db.execute( "update cards set due = due / 1000 where due > 4294967296") col.db.execute("update col set ver = 8") @@ -149,7 +149,7 @@ update cards set left = left + left*1000 where queue = 1""") col.db.execute("update col set ver = 10") if ver < 11: - col.modSchema() + col.modSchema(check=False) for d in col.decks.all(): if d['dyn']: order = d['order'] diff -Nru anki-2.0.31+dfsg/anki/sync.py anki-2.0.32+dfsg/anki/sync.py --- anki-2.0.31+dfsg/anki/sync.py 2014-10-19 07:56:36.000000000 +0000 +++ anki-2.0.32+dfsg/anki/sync.py 2015-03-19 04:24:51.000000000 +0000 @@ -111,23 +111,22 @@ self.col.log("rmeta", meta) if not meta: return "badAuth" + # server requested abort? + self.syncMsg = meta['msg'] + if not meta['cont']: + return "serverAbort" + else: + # don't abort, but if 'msg' is not blank, gui should show 'msg' + # after sync finishes and wait for confirmation before hiding + pass rscm = meta['scm'] rts = meta['ts'] self.rmod = meta['mod'] self.maxUsn = meta['usn'] - self.mediaUsn = meta['musn'] - self.syncMsg = meta['msg'] # this is a temporary measure to address the problem of users # forgetting which email address they've used - it will be removed # when enough time has passed self.uname = meta.get("uname", "") - # server requested abort? - if not meta['cont']: - return "serverAbort" - else: - # don't abort, but ui should show message after sync finishes - # and require confirmation if it's non-empty - pass meta = self.meta() self.col.log("lmeta", meta) self.lmod = meta['mod'] @@ -183,7 +182,7 @@ if ret['status'] != "ok": # roll back and force full sync self.col.rollback() - self.col.modSchema() + self.col.modSchema(False) self.col.save() return "sanityCheckFailed" # finalize @@ -860,7 +859,7 @@ def syncURL(self): if os.getenv("DEV"): return "https://l1.ankiweb.net/msync/" - return SYNC_BASE + "msync/" + return SYNC_MEDIA_BASE def begin(self): self.postVars = dict( diff -Nru anki-2.0.31+dfsg/aqt/addons.py anki-2.0.32+dfsg/aqt/addons.py --- anki-2.0.31+dfsg/aqt/addons.py 2014-08-09 04:27:57.000000000 +0000 +++ anki-2.0.32+dfsg/aqt/addons.py 2015-03-12 01:51:47.000000000 +0000 @@ -4,13 +4,15 @@ import sys, os, traceback from cStringIO import StringIO +import zipfile from aqt.qt import * from aqt.utils import showInfo, openFolder, isWin, openLink, \ - askUser, restoreGeom, saveGeom + askUser, restoreGeom, saveGeom, showWarning from zipfile import ZipFile import aqt.forms import aqt from aqt.downloader import download +from anki.lang import _ # in the future, it would be nice to save the addon id and unzippped file list # to the config so that we can clear up all files and check for updates @@ -122,7 +124,11 @@ open(path, "wb").write(data) return # .zip file - z = ZipFile(StringIO(data)) + try: + z = ZipFile(StringIO(data)) + except zipfile.BadZipFile: + showWarning(_("The download was corrupt. Please try again.")) + return base = self.addonsFolder() for n in z.namelist(): if n.endswith("/"): diff -Nru anki-2.0.31+dfsg/aqt/browser.py anki-2.0.32+dfsg/aqt/browser.py --- anki-2.0.31+dfsg/aqt/browser.py 2014-10-10 08:58:11.000000000 +0000 +++ anki-2.0.32+dfsg/aqt/browser.py 2015-03-12 01:51:47.000000000 +0000 @@ -821,7 +821,7 @@ root = self.CallbackItem(root, _("My Searches"), None) root.setExpanded(True) root.setIcon(0, QIcon(":/icons/emblem-favorite-dark.png")) - for name, filt in saved.items(): + for name, filt in sorted(saved.items()): item = self.CallbackItem(root, name, lambda s=filt: self.setFilter(s)) item.setIcon(0, QIcon(":/icons/emblem-favorite-dark.png")) diff -Nru anki-2.0.31+dfsg/aqt/editor.py anki-2.0.32+dfsg/aqt/editor.py --- anki-2.0.31+dfsg/aqt/editor.py 2014-09-15 06:01:44.000000000 +0000 +++ anki-2.0.32+dfsg/aqt/editor.py 2015-03-17 02:36:56.000000000 +0000 @@ -20,8 +20,8 @@ import anki.js from BeautifulSoup import BeautifulSoup -pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg") -audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv", "m4a") +pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp") +audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv", "m4a", "3gp", "spx", "oga") _html = """ %s