Imported Upstream version 2.0.32+dfsg
Andreas Bombe
9 years ago
44 | 44 | I have not tested the build scripts on Windows, so you'll need to solve any |
45 | 45 | problems you encounter on your own. The easiest way is to use a source |
46 | 46 | 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: | |
49 | 57 | |
50 | 58 | 1) Install "git bash". |
51 | 59 | 2) In the tools directory, modify build_ui.sh. Locate the line that reads |
29 | 29 | sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( |
30 | 30 | sys.version_info[1], arch[0][0:2]))) |
31 | 31 | |
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 | |
33 | 33 | from anki.storage import Collection |
34 | 34 | __all__ = ["Collection"] |
170 | 170 | self.load() |
171 | 171 | self.lock() |
172 | 172 | |
173 | def modSchema(self, check=True): | |
173 | def modSchema(self, check): | |
174 | 174 | "Mark schema modified. Call this first so user can abort if necessary." |
175 | 175 | if not self.schemaChanged(): |
176 | 176 | if check and not runFilter("modSchema", True): |
196 | 196 | self.models.beforeUpload() |
197 | 197 | self.tags.beforeUpload() |
198 | 198 | self.decks.beforeUpload() |
199 | self.modSchema() | |
199 | self.modSchema(check=False) | |
200 | 200 | self.ls = self.scm |
201 | 201 | # ensure db is compacted before upload |
202 | 202 | self.db.execute("vacuum") |
497 | 497 | fields['Tags'] = data[5].strip() |
498 | 498 | fields['Type'] = model['name'] |
499 | 499 | fields['Deck'] = self.decks.name(data[3]) |
500 | fields['Subdeck'] = fields['Deck'].split('::')[-1] | |
500 | 501 | if model['type'] == MODEL_STD: |
501 | 502 | template = model['tmpls'][data[4]] |
502 | 503 | else: |
48 | 48 | SYNC_ZIP_SIZE = int(2.5*1024*1024) |
49 | 49 | SYNC_ZIP_COUNT = 25 |
50 | 50 | SYNC_BASE = "https://ankiweb.net/" |
51 | SYNC_MEDIA_BASE = "https://msync.ankiweb.net/" | |
51 | 52 | SYNC_VER = 8 |
52 | 53 | |
53 | 54 | HELP_SITE="http://ankisrs.net/docs/manual.html" |
343 | 343 | def remConf(self, id): |
344 | 344 | "Remove a configuration and update all decks using it." |
345 | 345 | assert int(id) != 1 |
346 | self.col.modSchema() | |
346 | self.col.modSchema(check=True) | |
347 | 347 | del self.dconf[str(id)] |
348 | 348 | for g in self.all(): |
349 | 349 | # ignore cram decks |
146 | 146 | |
147 | 147 | def rem(self, m): |
148 | 148 | "Delete model, and all its cards/notes." |
149 | self.col.modSchema() | |
149 | self.col.modSchema(check=True) | |
150 | 150 | current = self.current()['id'] == m['id'] |
151 | 151 | # delete notes/cards |
152 | 152 | self.col.remCards(self.col.db.list(""" |
240 | 240 | |
241 | 241 | def setSortIdx(self, m, idx): |
242 | 242 | assert idx >= 0 and idx < len(m['flds']) |
243 | self.col.modSchema() | |
243 | self.col.modSchema(check=True) | |
244 | 244 | m['sortf'] = idx |
245 | 245 | self.col.updateFieldCache(self.nids(m)) |
246 | 246 | self.save(m) |
248 | 248 | def addField(self, m, field): |
249 | 249 | # only mod schema if model isn't new |
250 | 250 | if m['id']: |
251 | self.col.modSchema() | |
251 | self.col.modSchema(check=True) | |
252 | 252 | m['flds'].append(field) |
253 | 253 | self._updateFieldOrds(m) |
254 | 254 | self.save(m) |
258 | 258 | self._transformFields(m, add) |
259 | 259 | |
260 | 260 | def remField(self, m, field): |
261 | self.col.modSchema() | |
261 | self.col.modSchema(check=True) | |
262 | 262 | # save old sort field |
263 | 263 | sortFldName = m['flds'][m['sortf']]['name'] |
264 | 264 | idx = m['flds'].index(field) |
281 | 281 | self.renameField(m, field, None) |
282 | 282 | |
283 | 283 | def moveField(self, m, field, idx): |
284 | self.col.modSchema() | |
284 | self.col.modSchema(check=True) | |
285 | 285 | oldidx = m['flds'].index(field) |
286 | 286 | if oldidx == idx: |
287 | 287 | return |
302 | 302 | self._transformFields(m, move) |
303 | 303 | |
304 | 304 | def renameField(self, m, field, newName): |
305 | self.col.modSchema() | |
306 | pat = r'{{(.*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}' | |
305 | self.col.modSchema(check=True) | |
306 | pat = r'{{([^{}]*)([:#^/]|[^:#/^}][^:}]*?:|)%s}}' | |
307 | 307 | def wrap(txt): |
308 | 308 | def repl(match): |
309 | 309 | return '{{' + match.group(1) + match.group(2) + txt + '}}' |
346 | 346 | def addTemplate(self, m, template): |
347 | 347 | "Note: should col.genCards() afterwards." |
348 | 348 | if m['id']: |
349 | self.col.modSchema() | |
349 | self.col.modSchema(check=True) | |
350 | 350 | m['tmpls'].append(template) |
351 | 351 | self._updateTemplOrds(m) |
352 | 352 | self.save(m) |
369 | 369 | limit 1""" % ids2str(cids)): |
370 | 370 | return False |
371 | 371 | # ok to proceed; remove cards |
372 | self.col.modSchema() | |
372 | self.col.modSchema(check=True) | |
373 | 373 | self.col.remCards(cids) |
374 | 374 | # shift ordinals |
375 | 375 | self.col.db.execute(""" |
413 | 413 | # - newModel should be self if model is not changing |
414 | 414 | |
415 | 415 | def change(self, m, nids, newModel, fmap, cmap): |
416 | self.col.modSchema() | |
416 | self.col.modSchema(check=True) | |
417 | 417 | assert newModel['id'] == m['id'] or (fmap and cmap) |
418 | 418 | if fmap: |
419 | 419 | self._changeNotes(nids, newModel, fmap) |
974 | 974 | self.col.db.execute(""" |
975 | 975 | update cards set did = odid, queue = (case when type = 1 then 0 |
976 | 976 | 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()) | |
979 | 979 | |
980 | 980 | def remFromDyn(self, cids): |
981 | 981 | self.emptyDyn(None, "id in %s and odid" % ids2str(cids)) |
1011 | 1011 | t = intTime(); u = self.col.usn() |
1012 | 1012 | for c, id in enumerate(ids): |
1013 | 1013 | # 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)) | |
1015 | 1015 | # due reviews stay in the review queue. careful: can't use |
1016 | 1016 | # "odid or did", as sqlite converts to boolean |
1017 | 1017 | queue = """ |
1022 | 1022 | update cards set |
1023 | 1023 | odid = (case when odid then odid else did end), |
1024 | 1024 | 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) | |
1026 | 1026 | |
1027 | 1027 | def _dynIvlBoost(self, card): |
1028 | 1028 | assert card.odid and card.type == 2 |
38 | 38 | mm.addTemplate(m, t) |
39 | 39 | return m |
40 | 40 | |
41 | models.append((lambda: _("Forward & Reverse"), addForwardReverse)) | |
41 | models.append((lambda: _("Basic (and reversed card)"), addForwardReverse)) | |
42 | 42 | |
43 | 43 | # Forward & Optional Reverse |
44 | 44 | ########################################################################## |
56 | 56 | mm.addTemplate(m, t) |
57 | 57 | return m |
58 | 58 | |
59 | models.append((lambda: _("Forward & Optional Reverse"), addForwardOptionalReverse)) | |
59 | models.append((lambda: _("Basic (optional reversed card)"), | |
60 | addForwardOptionalReverse)) | |
60 | 61 | |
61 | 62 | # Cloze |
62 | 63 | ########################################################################## |
87 | 87 | d['collapsed'] = False |
88 | 88 | col.decks.save(d) |
89 | 89 | if ver < 4: |
90 | col.modSchema() | |
90 | col.modSchema(check=False) | |
91 | 91 | clozes = [] |
92 | 92 | for m in col.models.all(): |
93 | 93 | if not "{{cloze:" in m['tmpls'][0]['qfmt']: |
102 | 102 | col.db.execute("update cards set odue = 0 where queue = 2") |
103 | 103 | col.db.execute("update col set ver = 5") |
104 | 104 | if ver < 6: |
105 | col.modSchema() | |
105 | col.modSchema(check=False) | |
106 | 106 | import anki.models |
107 | 107 | for m in col.models.all(): |
108 | 108 | m['css'] = anki.models.defaultModel['css'] |
116 | 116 | col.models.save(m) |
117 | 117 | col.db.execute("update col set ver = 6") |
118 | 118 | if ver < 7: |
119 | col.modSchema() | |
119 | col.modSchema(check=False) | |
120 | 120 | col.db.execute( |
121 | 121 | "update cards set odue = 0 where (type = 1 or queue = 2) " |
122 | 122 | "and not odid") |
123 | 123 | col.db.execute("update col set ver = 7") |
124 | 124 | if ver < 8: |
125 | col.modSchema() | |
125 | col.modSchema(check=False) | |
126 | 126 | col.db.execute( |
127 | 127 | "update cards set due = due / 1000 where due > 4294967296") |
128 | 128 | col.db.execute("update col set ver = 8") |
148 | 148 | update cards set left = left + left*1000 where queue = 1""") |
149 | 149 | col.db.execute("update col set ver = 10") |
150 | 150 | if ver < 11: |
151 | col.modSchema() | |
151 | col.modSchema(check=False) | |
152 | 152 | for d in col.decks.all(): |
153 | 153 | if d['dyn']: |
154 | 154 | order = d['order'] |
110 | 110 | self.col.log("rmeta", meta) |
111 | 111 | if not meta: |
112 | 112 | 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 | |
113 | 121 | rscm = meta['scm'] |
114 | 122 | rts = meta['ts'] |
115 | 123 | self.rmod = meta['mod'] |
116 | 124 | self.maxUsn = meta['usn'] |
117 | self.mediaUsn = meta['musn'] | |
118 | self.syncMsg = meta['msg'] | |
119 | 125 | # this is a temporary measure to address the problem of users |
120 | 126 | # forgetting which email address they've used - it will be removed |
121 | 127 | # when enough time has passed |
122 | 128 | 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 | |
130 | 129 | meta = self.meta() |
131 | 130 | self.col.log("lmeta", meta) |
132 | 131 | self.lmod = meta['mod'] |
182 | 181 | if ret['status'] != "ok": |
183 | 182 | # roll back and force full sync |
184 | 183 | self.col.rollback() |
185 | self.col.modSchema() | |
184 | self.col.modSchema(False) | |
186 | 185 | self.col.save() |
187 | 186 | return "sanityCheckFailed" |
188 | 187 | # finalize |
859 | 858 | def syncURL(self): |
860 | 859 | if os.getenv("DEV"): |
861 | 860 | return "https://l1.ankiweb.net/msync/" |
862 | return SYNC_BASE + "msync/" | |
861 | return SYNC_MEDIA_BASE | |
863 | 862 | |
864 | 863 | def begin(self): |
865 | 864 | self.postVars = dict( |
3 | 3 | |
4 | 4 | import sys, os, traceback |
5 | 5 | from cStringIO import StringIO |
6 | import zipfile | |
6 | 7 | from aqt.qt import * |
7 | 8 | from aqt.utils import showInfo, openFolder, isWin, openLink, \ |
8 | askUser, restoreGeom, saveGeom | |
9 | askUser, restoreGeom, saveGeom, showWarning | |
9 | 10 | from zipfile import ZipFile |
10 | 11 | import aqt.forms |
11 | 12 | import aqt |
12 | 13 | from aqt.downloader import download |
14 | from anki.lang import _ | |
13 | 15 | |
14 | 16 | # in the future, it would be nice to save the addon id and unzippped file list |
15 | 17 | # to the config so that we can clear up all files and check for updates |
121 | 123 | open(path, "wb").write(data) |
122 | 124 | return |
123 | 125 | # .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 | |
125 | 131 | base = self.addonsFolder() |
126 | 132 | for n in z.namelist(): |
127 | 133 | if n.endswith("/"): |
820 | 820 | root = self.CallbackItem(root, _("My Searches"), None) |
821 | 821 | root.setExpanded(True) |
822 | 822 | root.setIcon(0, QIcon(":/icons/emblem-favorite-dark.png")) |
823 | for name, filt in saved.items(): | |
823 | for name, filt in sorted(saved.items()): | |
824 | 824 | item = self.CallbackItem(root, name, lambda s=filt: self.setFilter(s)) |
825 | 825 | item.setIcon(0, QIcon(":/icons/emblem-favorite-dark.png")) |
826 | 826 |
19 | 19 | import anki.js |
20 | 20 | from BeautifulSoup import BeautifulSoup |
21 | 21 | |
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") | |
24 | 24 | |
25 | 25 | _html = """ |
26 | 26 | <html><head>%s<style> |
461 | 461 | "editFocusLost", False, self.note, self.currentField): |
462 | 462 | # something updated the note; schedule reload |
463 | 463 | def onUpdate(): |
464 | if not self.note: | |
465 | return | |
464 | 466 | self.stealFocus = True |
465 | 467 | self.loadNote() |
466 | 468 | self.checkValid() |
1 | 1 | |
2 | 2 | # Resource object code |
3 | 3 | # |
4 | # Created: Sun Oct 19 17:01:06 2014 | |
4 | # Created: Wed Mar 25 01:20:58 2015 | |
5 | 5 | # by: The Resource Compiler for PyQt (Qt v4.8.5) |
6 | 6 | # |
7 | 7 | # WARNING! All changes made in this file will be lost! |
312 | 312 | else: |
313 | 313 | # if it's an apkg/zip, first test it's a valid file |
314 | 314 | if importer.__class__.__name__ == "AnkiPackageImporter": |
315 | z = zipfile.ZipFile(importer.file) | |
316 | 315 | try: |
316 | z = zipfile.ZipFile(importer.file) | |
317 | 317 | z.getinfo("collection.anki2") |
318 | 318 | except: |
319 | 319 | showWarning(invalidZipMsg()) |
68 | 68 | |
69 | 69 | def setupUI(self): |
70 | 70 | self.col = None |
71 | self.hideSchemaMsg = False | |
72 | 71 | self.setupAppMsg() |
73 | 72 | self.setupKeys() |
74 | 73 | self.setupThreads() |
264 | 263 | ########################################################################## |
265 | 264 | |
266 | 265 | def loadCollection(self): |
267 | self.hideSchemaMsg = True | |
268 | 266 | cpath = self.pm.collectionPath() |
269 | 267 | try: |
270 | 268 | self.col = Collection(cpath, log=True) |
286 | 284 | return |
287 | 285 | self.unloadProfile() |
288 | 286 | raise |
289 | self.hideSchemaMsg = False | |
290 | 287 | self.progress.setupDB(self.col.db) |
291 | 288 | self.maybeEnableUndo() |
292 | 289 | self.moveToState("deckBrowser") |
902 | 899 | ########################################################################## |
903 | 900 | |
904 | 901 | 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 | |
911 | 902 | return askUser(_("""\ |
912 | 903 | The requested change will require a full upload of the database when \ |
913 | 904 | you next synchronize your collection. If you have reviews or other changes \ |
104 | 104 | self.prof['autoSync'] = self.form.syncOnProgramOpen.isChecked() |
105 | 105 | self.prof['syncMedia'] = self.form.syncMedia.isChecked() |
106 | 106 | if self.form.fullSync.isChecked(): |
107 | self.mw.hideSchemaMsg = True | |
108 | self.mw.col.modSchema() | |
107 | self.mw.col.modSchema(check=False) | |
109 | 108 | self.mw.col.setMod() |
110 | self.mw.hideSchemaMsg = False | |
111 | 109 | |
112 | 110 | # Backup |
113 | 111 | ###################################################################### |
366 | 366 | return self.fireEvent("clockOff") |
367 | 367 | elif ret == "basicCheckFailed" or ret == "sanityCheckFailed": |
368 | 368 | return self.fireEvent("checkFailed") |
369 | # note mediaUSN for later | |
370 | self.mediaUsn = self.client.mediaUsn | |
371 | 369 | # full sync? |
372 | 370 | if ret == "fullSync": |
373 | 371 | return self._fullSync() |
377 | 375 | elif ret == "success": |
378 | 376 | self.fireEvent("success") |
379 | 377 | elif ret == "serverAbort": |
380 | self.fireEvent("error", self.client.syncMsg) | |
378 | pass | |
381 | 379 | else: |
382 | 380 | self.fireEvent("error", "Unknown sync return code.") |
383 | 381 | self.syncMsg = self.client.syncMsg |
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
331 | 331 | print "load %d" % ((time.time() - t)*1000); t = time.time() |
332 | 332 | assert client.sync() == "success" |
333 | 333 | 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 |