Codebase list anki / 37b567c
Imported Upstream version 2.0.32+dfsg Andreas Bombe 9 years ago
74 changed file(s) with 95 addition(s) and 60 deletion(s). Raw diff Collapse all Expand all
4444 I have not tested the build scripts on Windows, so you'll need to solve any
4545 problems you encounter on your own. The easiest way is to use a source
4646 tarball instead of git, as that way you don't need to build the UI yourself.
47 If you do want to use git, a user contributed the following, which should get
48 you most of the way there:
47
48 If you do want to use git, two alternatives have been contributed by users. As
49 these are not official solutions, I'm afraid we can not provide you with any
50 support for these.
51
52 A powershell script:
53
54 https://gist.github.com/vermiceli/108fec65759d19645ee3
55
56 Or a way with git bash and perl:
4957
5058 1) Install "git bash".
5159 2) In the tools directory, modify build_ui.sh. Locate the line that reads
2929 sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % (
3030 sys.version_info[1], arch[0][0:2])))
3131
32 version="2.0.31" # build scripts grep this line, so preserve formatting
32 version="2.0.32" # build scripts grep this line, so preserve formatting
3333 from anki.storage import Collection
3434 __all__ = ["Collection"]
170170 self.load()
171171 self.lock()
172172
173 def modSchema(self, check=True):
173 def modSchema(self, check):
174174 "Mark schema modified. Call this first so user can abort if necessary."
175175 if not self.schemaChanged():
176176 if check and not runFilter("modSchema", True):
196196 self.models.beforeUpload()
197197 self.tags.beforeUpload()
198198 self.decks.beforeUpload()
199 self.modSchema()
199 self.modSchema(check=False)
200200 self.ls = self.scm
201201 # ensure db is compacted before upload
202202 self.db.execute("vacuum")
497497 fields['Tags'] = data[5].strip()
498498 fields['Type'] = model['name']
499499 fields['Deck'] = self.decks.name(data[3])
500 fields['Subdeck'] = fields['Deck'].split('::')[-1]
500501 if model['type'] == MODEL_STD:
501502 template = model['tmpls'][data[4]]
502503 else:
4848 SYNC_ZIP_SIZE = int(2.5*1024*1024)
4949 SYNC_ZIP_COUNT = 25
5050 SYNC_BASE = "https://ankiweb.net/"
51 SYNC_MEDIA_BASE = "https://msync.ankiweb.net/"
5152 SYNC_VER = 8
5253
5354 HELP_SITE="http://ankisrs.net/docs/manual.html"
343343 def remConf(self, id):
344344 "Remove a configuration and update all decks using it."
345345 assert int(id) != 1
346 self.col.modSchema()
346 self.col.modSchema(check=True)
347347 del self.dconf[str(id)]
348348 for g in self.all():
349349 # ignore cram decks
146146
147147 def rem(self, m):
148148 "Delete model, and all its cards/notes."
149 self.col.modSchema()
149 self.col.modSchema(check=True)
150150 current = self.current()['id'] == m['id']
151151 # delete notes/cards
152152 self.col.remCards(self.col.db.list("""
240240
241241 def setSortIdx(self, m, idx):
242242 assert idx >= 0 and idx < len(m['flds'])
243 self.col.modSchema()
243 self.col.modSchema(check=True)
244244 m['sortf'] = idx
245245 self.col.updateFieldCache(self.nids(m))
246246 self.save(m)
248248 def addField(self, m, field):
249249 # only mod schema if model isn't new
250250 if m['id']:
251 self.col.modSchema()
251 self.col.modSchema(check=True)
252252 m['flds'].append(field)
253253 self._updateFieldOrds(m)
254254 self.save(m)
258258 self._transformFields(m, add)
259259
260260 def remField(self, m, field):
261 self.col.modSchema()
261 self.col.modSchema(check=True)
262262 # save old sort field
263263 sortFldName = m['flds'][m['sortf']]['name']
264264 idx = m['flds'].index(field)
281281 self.renameField(m, field, None)
282282
283283 def moveField(self, m, field, idx):
284 self.col.modSchema()
284 self.col.modSchema(check=True)
285285 oldidx = m['flds'].index(field)
286286 if oldidx == idx:
287287 return
302302 self._transformFields(m, move)
303303
304304 def renameField(self, m, field, newName):
305 self.col.modSchema()
306 pat = r'{{(.*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}'
305 self.col.modSchema(check=True)
306 pat = r'{{([^{}]*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}'
307307 def wrap(txt):
308308 def repl(match):
309309 return '{{' + match.group(1) + match.group(2) + txt + '}}'
346346 def addTemplate(self, m, template):
347347 "Note: should col.genCards() afterwards."
348348 if m['id']:
349 self.col.modSchema()
349 self.col.modSchema(check=True)
350350 m['tmpls'].append(template)
351351 self._updateTemplOrds(m)
352352 self.save(m)
369369 limit 1""" % ids2str(cids)):
370370 return False
371371 # ok to proceed; remove cards
372 self.col.modSchema()
372 self.col.modSchema(check=True)
373373 self.col.remCards(cids)
374374 # shift ordinals
375375 self.col.db.execute("""
413413 # - newModel should be self if model is not changing
414414
415415 def change(self, m, nids, newModel, fmap, cmap):
416 self.col.modSchema()
416 self.col.modSchema(check=True)
417417 assert newModel['id'] == m['id'] or (fmap and cmap)
418418 if fmap:
419419 self._changeNotes(nids, newModel, fmap)
974974 self.col.db.execute("""
975975 update cards set did = odid, queue = (case when type = 1 then 0
976976 else type end), type = (case when type = 1 then 0 else type end),
977 due = odue, odue = 0, odid = 0, usn = ?, mod = ? where %s""" % lim,
978 self.col.usn(), intTime())
977 due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim,
978 self.col.usn())
979979
980980 def remFromDyn(self, cids):
981981 self.emptyDyn(None, "id in %s and odid" % ids2str(cids))
10111011 t = intTime(); u = self.col.usn()
10121012 for c, id in enumerate(ids):
10131013 # start at -100000 so that reviews are all due
1014 data.append((did, -100000+c, t, u, id))
1014 data.append((did, -100000+c, u, id))
10151015 # due reviews stay in the review queue. careful: can't use
10161016 # "odid or did", as sqlite converts to boolean
10171017 queue = """
10221022 update cards set
10231023 odid = (case when odid then odid else did end),
10241024 odue = (case when odue then odue else due end),
1025 did = ?, queue = %s, due = ?, mod = ?, usn = ? where id = ?""" % queue, data)
1025 did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
10261026
10271027 def _dynIvlBoost(self, card):
10281028 assert card.odid and card.type == 2
3838 mm.addTemplate(m, t)
3939 return m
4040
41 models.append((lambda: _("Forward & Reverse"), addForwardReverse))
41 models.append((lambda: _("Basic (and reversed card)"), addForwardReverse))
4242
4343 # Forward & Optional Reverse
4444 ##########################################################################
5656 mm.addTemplate(m, t)
5757 return m
5858
59 models.append((lambda: _("Forward & Optional Reverse"), addForwardOptionalReverse))
59 models.append((lambda: _("Basic (optional reversed card)"),
60 addForwardOptionalReverse))
6061
6162 # Cloze
6263 ##########################################################################
8787 d['collapsed'] = False
8888 col.decks.save(d)
8989 if ver < 4:
90 col.modSchema()
90 col.modSchema(check=False)
9191 clozes = []
9292 for m in col.models.all():
9393 if not "{{cloze:" in m['tmpls'][0]['qfmt']:
102102 col.db.execute("update cards set odue = 0 where queue = 2")
103103 col.db.execute("update col set ver = 5")
104104 if ver < 6:
105 col.modSchema()
105 col.modSchema(check=False)
106106 import anki.models
107107 for m in col.models.all():
108108 m['css'] = anki.models.defaultModel['css']
116116 col.models.save(m)
117117 col.db.execute("update col set ver = 6")
118118 if ver < 7:
119 col.modSchema()
119 col.modSchema(check=False)
120120 col.db.execute(
121121 "update cards set odue = 0 where (type = 1 or queue = 2) "
122122 "and not odid")
123123 col.db.execute("update col set ver = 7")
124124 if ver < 8:
125 col.modSchema()
125 col.modSchema(check=False)
126126 col.db.execute(
127127 "update cards set due = due / 1000 where due > 4294967296")
128128 col.db.execute("update col set ver = 8")
148148 update cards set left = left + left*1000 where queue = 1""")
149149 col.db.execute("update col set ver = 10")
150150 if ver < 11:
151 col.modSchema()
151 col.modSchema(check=False)
152152 for d in col.decks.all():
153153 if d['dyn']:
154154 order = d['order']
110110 self.col.log("rmeta", meta)
111111 if not meta:
112112 return "badAuth"
113 # server requested abort?
114 self.syncMsg = meta['msg']
115 if not meta['cont']:
116 return "serverAbort"
117 else:
118 # don't abort, but if 'msg' is not blank, gui should show 'msg'
119 # after sync finishes and wait for confirmation before hiding
120 pass
113121 rscm = meta['scm']
114122 rts = meta['ts']
115123 self.rmod = meta['mod']
116124 self.maxUsn = meta['usn']
117 self.mediaUsn = meta['musn']
118 self.syncMsg = meta['msg']
119125 # this is a temporary measure to address the problem of users
120126 # forgetting which email address they've used - it will be removed
121127 # when enough time has passed
122128 self.uname = meta.get("uname", "")
123 # server requested abort?
124 if not meta['cont']:
125 return "serverAbort"
126 else:
127 # don't abort, but ui should show message after sync finishes
128 # and require confirmation if it's non-empty
129 pass
130129 meta = self.meta()
131130 self.col.log("lmeta", meta)
132131 self.lmod = meta['mod']
182181 if ret['status'] != "ok":
183182 # roll back and force full sync
184183 self.col.rollback()
185 self.col.modSchema()
184 self.col.modSchema(False)
186185 self.col.save()
187186 return "sanityCheckFailed"
188187 # finalize
859858 def syncURL(self):
860859 if os.getenv("DEV"):
861860 return "https://l1.ankiweb.net/msync/"
862 return SYNC_BASE + "msync/"
861 return SYNC_MEDIA_BASE
863862
864863 def begin(self):
865864 self.postVars = dict(
33
44 import sys, os, traceback
55 from cStringIO import StringIO
6 import zipfile
67 from aqt.qt import *
78 from aqt.utils import showInfo, openFolder, isWin, openLink, \
8 askUser, restoreGeom, saveGeom
9 askUser, restoreGeom, saveGeom, showWarning
910 from zipfile import ZipFile
1011 import aqt.forms
1112 import aqt
1213 from aqt.downloader import download
14 from anki.lang import _
1315
1416 # in the future, it would be nice to save the addon id and unzippped file list
1517 # to the config so that we can clear up all files and check for updates
121123 open(path, "wb").write(data)
122124 return
123125 # .zip file
124 z = ZipFile(StringIO(data))
126 try:
127 z = ZipFile(StringIO(data))
128 except zipfile.BadZipFile:
129 showWarning(_("The download was corrupt. Please try again."))
130 return
125131 base = self.addonsFolder()
126132 for n in z.namelist():
127133 if n.endswith("/"):
820820 root = self.CallbackItem(root, _("My Searches"), None)
821821 root.setExpanded(True)
822822 root.setIcon(0, QIcon(":/icons/emblem-favorite-dark.png"))
823 for name, filt in saved.items():
823 for name, filt in sorted(saved.items()):
824824 item = self.CallbackItem(root, name, lambda s=filt: self.setFilter(s))
825825 item.setIcon(0, QIcon(":/icons/emblem-favorite-dark.png"))
826826
1919 import anki.js
2020 from BeautifulSoup import BeautifulSoup
2121
22 pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg")
23 audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv", "m4a")
22 pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp")
23 audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv", "m4a", "3gp", "spx", "oga")
2424
2525 _html = """
2626 <html><head>%s<style>
461461 "editFocusLost", False, self.note, self.currentField):
462462 # something updated the note; schedule reload
463463 def onUpdate():
464 if not self.note:
465 return
464466 self.stealFocus = True
465467 self.loadNote()
466468 self.checkValid()
11
22 # Resource object code
33 #
4 # Created: Sun Oct 19 17:01:06 2014
4 # Created: Wed Mar 25 01:20:58 2015
55 # by: The Resource Compiler for PyQt (Qt v4.8.5)
66 #
77 # WARNING! All changes made in this file will be lost!
312312 else:
313313 # if it's an apkg/zip, first test it's a valid file
314314 if importer.__class__.__name__ == "AnkiPackageImporter":
315 z = zipfile.ZipFile(importer.file)
316315 try:
316 z = zipfile.ZipFile(importer.file)
317317 z.getinfo("collection.anki2")
318318 except:
319319 showWarning(invalidZipMsg())
6868
6969 def setupUI(self):
7070 self.col = None
71 self.hideSchemaMsg = False
7271 self.setupAppMsg()
7372 self.setupKeys()
7473 self.setupThreads()
264263 ##########################################################################
265264
266265 def loadCollection(self):
267 self.hideSchemaMsg = True
268266 cpath = self.pm.collectionPath()
269267 try:
270268 self.col = Collection(cpath, log=True)
286284 return
287285 self.unloadProfile()
288286 raise
289 self.hideSchemaMsg = False
290287 self.progress.setupDB(self.col.db)
291288 self.maybeEnableUndo()
292289 self.moveToState("deckBrowser")
902899 ##########################################################################
903900
904901 def onSchemaMod(self, arg):
905 # if triggered in sync, make sure we don't use the gui
906 if not self.inMainThread():
907 return True
908 # if from the full sync menu, ignore
909 if self.hideSchemaMsg:
910 return True
911902 return askUser(_("""\
912903 The requested change will require a full upload of the database when \
913904 you next synchronize your collection. If you have reviews or other changes \
104104 self.prof['autoSync'] = self.form.syncOnProgramOpen.isChecked()
105105 self.prof['syncMedia'] = self.form.syncMedia.isChecked()
106106 if self.form.fullSync.isChecked():
107 self.mw.hideSchemaMsg = True
108 self.mw.col.modSchema()
107 self.mw.col.modSchema(check=False)
109108 self.mw.col.setMod()
110 self.mw.hideSchemaMsg = False
111109
112110 # Backup
113111 ######################################################################
366366 return self.fireEvent("clockOff")
367367 elif ret == "basicCheckFailed" or ret == "sanityCheckFailed":
368368 return self.fireEvent("checkFailed")
369 # note mediaUSN for later
370 self.mediaUsn = self.client.mediaUsn
371369 # full sync?
372370 if ret == "fullSync":
373371 return self._fullSync()
377375 elif ret == "success":
378376 self.fireEvent("success")
379377 elif ret == "serverAbort":
380 self.fireEvent("error", self.client.syncMsg)
378 pass
381379 else:
382380 self.fireEvent("error", "Unknown sync return code.")
383381 self.syncMsg = self.client.syncMsg
331331 print "load %d" % ((time.time() - t)*1000); t = time.time()
332332 assert client.sync() == "success"
333333 print "sync %d" % ((time.time() - t)*1000); t = time.time()
334
335 @nose.with_setup(setup_modified)
336 def test_filtered_delete():
337 test_sync()
338 nid = deck1.db.scalar("select id from notes")
339 note = deck1.getNote(nid)
340 card = note.cards()[0]
341 card.type = 2
342 card.ivl = 10
343 card.factor = 2500
344 card.due = deck1.sched.today
345 card.flush()
346 # put cards into a filtered deck
347 did = deck1.decks.newDyn("dyn")
348 deck1.sched.rebuildDyn(did)
349 # sync the filtered deck
350 assert client.sync() == "success"
351 # answer the card locally
352 time.sleep(1)
353 card.load()
354 card.startTimer()
355 deck1.sched.answerCard(card, 4)
356 assert card.ivl > 10
357 # delete the filtered deck
358 deck1.decks.rem(did)
359 # sync again
360 assert client.sync() == "success"
361 card.load()
362 assert card.ivl > 10
363 return