mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Merge branch 'master' of https://github.com/dae/anki
This commit is contained in:
commit
367a961bba
12 changed files with 162 additions and 51 deletions
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
import pprint
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from anki.utils import intTime, timestampID, joinFields
|
from anki.utils import intTime, timestampID, joinFields
|
||||||
|
@ -106,6 +107,7 @@ insert or replace into cards values
|
||||||
self.odid,
|
self.odid,
|
||||||
self.flags,
|
self.flags,
|
||||||
self.data)
|
self.data)
|
||||||
|
self.col.log(self)
|
||||||
|
|
||||||
def flushSched(self):
|
def flushSched(self):
|
||||||
self.mod = intTime()
|
self.mod = intTime()
|
||||||
|
@ -121,6 +123,7 @@ lapses=?, left=?, odue=?, odid=?, did=? where id = ?""",
|
||||||
self.mod, self.usn, self.type, self.queue, self.due, self.ivl,
|
self.mod, self.usn, self.type, self.queue, self.due, self.ivl,
|
||||||
self.factor, self.reps, self.lapses,
|
self.factor, self.reps, self.lapses,
|
||||||
self.left, self.odue, self.odid, self.did, self.id)
|
self.left, self.odue, self.odid, self.did, self.id)
|
||||||
|
self.col.log(self)
|
||||||
|
|
||||||
def q(self, reload=False, browser=False):
|
def q(self, reload=False, browser=False):
|
||||||
return self.css() + self._getQA(reload, browser)['q']
|
return self.css() + self._getQA(reload, browser)['q']
|
||||||
|
@ -180,3 +183,12 @@ lapses=?, left=?, odue=?, odid=?, did=? where id = ?""",
|
||||||
self.model(), joinFields(self.note().fields))
|
self.model(), joinFields(self.note().fields))
|
||||||
if self.ord not in ords:
|
if self.ord not in ords:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
d = dict(self.__dict__)
|
||||||
|
# remove non-useful elements
|
||||||
|
del d['_note']
|
||||||
|
del d['_qa']
|
||||||
|
del d['col']
|
||||||
|
del d['timerStarted']
|
||||||
|
return pprint.pformat(d, width=300)
|
||||||
|
|
|
@ -51,6 +51,7 @@ class _Collection(object):
|
||||||
def __init__(self, db, server=False):
|
def __init__(self, db, server=False):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.path = db._path
|
self.path = db._path
|
||||||
|
self.log(self.path, anki.version)
|
||||||
self.server = server
|
self.server = server
|
||||||
self._lastSave = time.time()
|
self._lastSave = time.time()
|
||||||
self.clearUndo()
|
self.clearUndo()
|
||||||
|
@ -204,8 +205,11 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
# Object creation helpers
|
# Object creation helpers
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def getCard(self, id):
|
def getCard(self, id, log=True):
|
||||||
return anki.cards.Card(self, id)
|
c = anki.cards.Card(self, id)
|
||||||
|
if log:
|
||||||
|
self.log(c, stack=1)
|
||||||
|
return c
|
||||||
|
|
||||||
def getNote(self, id):
|
def getNote(self, id):
|
||||||
return anki.notes.Note(self, id=id)
|
return anki.notes.Note(self, id=id)
|
||||||
|
@ -767,3 +771,9 @@ and queue = 0""", intTime(), self.usn())
|
||||||
self.db.execute("vacuum")
|
self.db.execute("vacuum")
|
||||||
self.db.execute("analyze")
|
self.db.execute("analyze")
|
||||||
self.lock()
|
self.lock()
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def log(self, *args, **kwargs):
|
||||||
|
runHook("log", args, kwargs)
|
||||||
|
|
|
@ -71,6 +71,8 @@ defaultConf = {
|
||||||
'minSpace': 1, # not currently used
|
'minSpace': 1, # not currently used
|
||||||
'ivlFct': 1,
|
'ivlFct': 1,
|
||||||
'maxIvl': 36500,
|
'maxIvl': 36500,
|
||||||
|
# may not be set on old decks
|
||||||
|
'bury': True,
|
||||||
},
|
},
|
||||||
'maxTaken': 60,
|
'maxTaken': 60,
|
||||||
'timer': 0,
|
'timer': 0,
|
||||||
|
|
|
@ -225,7 +225,7 @@ class MediaManager(object):
|
||||||
nfcFile = unicodedata.normalize("NFC", file)
|
nfcFile = unicodedata.normalize("NFC", file)
|
||||||
# we enforce NFC fs encoding on non-macs; on macs we'll have gotten
|
# we enforce NFC fs encoding on non-macs; on macs we'll have gotten
|
||||||
# NFD so we use the above variable for comparing references
|
# NFD so we use the above variable for comparing references
|
||||||
if not isMac:
|
if not isMac and local:
|
||||||
if file != nfcFile:
|
if file != nfcFile:
|
||||||
# delete if we already have the NFC form, otherwise rename
|
# delete if we already have the NFC form, otherwise rename
|
||||||
if os.path.exists(nfcFile):
|
if os.path.exists(nfcFile):
|
||||||
|
|
|
@ -69,7 +69,7 @@ insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
return joinFields(self.fields)
|
return joinFields(self.fields)
|
||||||
|
|
||||||
def cards(self):
|
def cards(self):
|
||||||
return [self.col.getCard(id) for id in self.col.db.list(
|
return [self.col.getCard(id, log=False) for id in self.col.db.list(
|
||||||
"select id from cards where nid = ? order by ord", self.id)]
|
"select id from cards where nid = ? order by ord", self.id)]
|
||||||
|
|
||||||
def model(self):
|
def model(self):
|
||||||
|
|
|
@ -3,9 +3,12 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import time, random, itertools
|
import time
|
||||||
|
import random
|
||||||
|
import itertools
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from heapq import *
|
from heapq import *
|
||||||
|
|
||||||
#from anki.cards import Card
|
#from anki.cards import Card
|
||||||
from anki.utils import ids2str, intTime, fmtTimeSpan
|
from anki.utils import ids2str, intTime, fmtTimeSpan
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
@ -52,6 +55,7 @@ class Scheduler(object):
|
||||||
self._haveQueues = True
|
self._haveQueues = True
|
||||||
|
|
||||||
def answerCard(self, card, ease):
|
def answerCard(self, card, ease):
|
||||||
|
self.col.log()
|
||||||
assert ease >= 1 and ease <= 4
|
assert ease >= 1 and ease <= 4
|
||||||
self.col.markReview(card)
|
self.col.markReview(card)
|
||||||
if self._burySiblingsOnAnswer:
|
if self._burySiblingsOnAnswer:
|
||||||
|
@ -139,14 +143,19 @@ order by due""" % self._deckLimit(),
|
||||||
def unburyCards(self):
|
def unburyCards(self):
|
||||||
"Unbury cards."
|
"Unbury cards."
|
||||||
self.col.conf['lastUnburied'] = self.today
|
self.col.conf['lastUnburied'] = self.today
|
||||||
|
self.col.log(
|
||||||
|
self.col.db.list("select id from cards where queue = -2"))
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"update cards set mod=?,usn=?,queue=type where queue = -2",
|
"update cards set queue=type where queue = -2")
|
||||||
intTime(), self.col.usn())
|
|
||||||
|
|
||||||
def unburyCardsForDeck(self):
|
def unburyCardsForDeck(self):
|
||||||
|
sids = ids2str(self.col.decks.active())
|
||||||
|
self.col.log(
|
||||||
|
self.col.db.list("select id from cards where queue = -2 and did in %s"
|
||||||
|
% sids))
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"update cards set mod=?,usn=?,queue=type where queue = -2 and did in %s"
|
"update cards set mod=?,usn=?,queue=type where queue = -2 and did in %s"
|
||||||
% ids2str(self.col.decks.active()), intTime(), self.col.usn())
|
% sids, intTime(), self.col.usn())
|
||||||
|
|
||||||
# Rev/lrn/time daily stats
|
# Rev/lrn/time daily stats
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -943,12 +952,14 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
|
||||||
ids = []
|
ids = []
|
||||||
return ids
|
return ids
|
||||||
# move the cards over
|
# move the cards over
|
||||||
|
self.col.log(deck['id'], ids)
|
||||||
self._moveToDyn(deck['id'], ids)
|
self._moveToDyn(deck['id'], ids)
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
def emptyDyn(self, did, lim=None):
|
def emptyDyn(self, did, lim=None):
|
||||||
if not lim:
|
if not lim:
|
||||||
lim = "did = %s" % did
|
lim = "did = %s" % did
|
||||||
|
self.col.log(self.col.db.list("select id from cards where %s" % lim))
|
||||||
# move out of cram queue
|
# move out of cram queue
|
||||||
self.col.db.execute("""
|
self.col.db.execute("""
|
||||||
update cards set did = odid, queue = (case when type = 1 then 0
|
update cards set did = odid, queue = (case when type = 1 then 0
|
||||||
|
@ -1111,6 +1122,7 @@ did = ?, queue = %s, due = ?, mod = ?, usn = ? where id = ?""" % queue, data)
|
||||||
self.today = int((time.time() - self.col.crt) // 86400)
|
self.today = int((time.time() - self.col.crt) // 86400)
|
||||||
# end of day cutoff
|
# end of day cutoff
|
||||||
self.dayCutoff = self.col.crt + (self.today+1)*86400
|
self.dayCutoff = self.col.crt + (self.today+1)*86400
|
||||||
|
self.col.log(self.today, self.dayCutoff)
|
||||||
# update all daily counts, but don't save decks to prevent needless
|
# update all daily counts, but don't save decks to prevent needless
|
||||||
# conflicts. we'll save on card answer instead
|
# conflicts. we'll save on card answer instead
|
||||||
def update(g):
|
def update(g):
|
||||||
|
@ -1239,6 +1251,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
||||||
|
|
||||||
def suspendCards(self, ids):
|
def suspendCards(self, ids):
|
||||||
"Suspend cards."
|
"Suspend cards."
|
||||||
|
self.col.log(ids)
|
||||||
self.remFromDyn(ids)
|
self.remFromDyn(ids)
|
||||||
self.removeLrn(ids)
|
self.removeLrn(ids)
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
|
@ -1247,6 +1260,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
||||||
|
|
||||||
def unsuspendCards(self, ids):
|
def unsuspendCards(self, ids):
|
||||||
"Unsuspend cards."
|
"Unsuspend cards."
|
||||||
|
self.col.log(ids)
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"update cards set queue=type,mod=?,usn=? "
|
"update cards set queue=type,mod=?,usn=? "
|
||||||
"where queue = -1 and id in "+ ids2str(ids),
|
"where queue = -1 and id in "+ ids2str(ids),
|
||||||
|
@ -1256,6 +1270,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
|
||||||
"Bury all cards for note until next session."
|
"Bury all cards for note until next session."
|
||||||
cids = self.col.db.list(
|
cids = self.col.db.list(
|
||||||
"select id from cards where nid = ? and queue >= 0", nid)
|
"select id from cards where nid = ? and queue >= 0", nid)
|
||||||
|
self.col.log(cids)
|
||||||
self.removeLrn(cids)
|
self.removeLrn(cids)
|
||||||
self.col.db.execute("""
|
self.col.db.execute("""
|
||||||
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
|
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
|
||||||
|
@ -1266,15 +1281,19 @@ update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
|
||||||
|
|
||||||
def _burySiblings(self, card):
|
def _burySiblings(self, card):
|
||||||
toBury = []
|
toBury = []
|
||||||
conf = self._newConf(card)
|
nconf = self._newConf(card)
|
||||||
buryNew = conf.get("bury", True)
|
buryNew = nconf.get("bury", True)
|
||||||
|
rconf = self._revConf(card)
|
||||||
|
buryRev = rconf.get("bury", True)
|
||||||
# loop through and remove from queues
|
# loop through and remove from queues
|
||||||
for cid,queue in self.col.db.execute("""
|
for cid,queue in self.col.db.execute("""
|
||||||
select id, queue from cards where nid=? and id!=?
|
select id, queue from cards where nid=? and id!=?
|
||||||
and (queue=0 or (queue=2 and due<=?))""",
|
and (queue=0 or (queue=2 and due<=?))""",
|
||||||
card.nid, card.id, self.today):
|
card.nid, card.id, self.today):
|
||||||
if queue == 2:
|
if queue == 2:
|
||||||
toBury.append(cid)
|
if buryRev:
|
||||||
|
toBury.append(cid)
|
||||||
|
# if bury disabled, we still discard to give same-day spacing
|
||||||
try:
|
try:
|
||||||
self._revQueue.remove(cid)
|
self._revQueue.remove(cid)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -1288,9 +1307,11 @@ and (queue=0 or (queue=2 and due<=?))""",
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
# then bury
|
# then bury
|
||||||
self.col.db.execute(
|
if toBury:
|
||||||
"update cards set queue=-2,mod=?,usn=? where id in "+ids2str(toBury),
|
self.col.db.execute(
|
||||||
intTime(), self.col.usn())
|
"update cards set queue=-2,mod=?,usn=? where id in "+ids2str(toBury),
|
||||||
|
intTime(), self.col.usn())
|
||||||
|
self.col.log(toBury)
|
||||||
|
|
||||||
# Resetting
|
# Resetting
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -1304,6 +1325,7 @@ and (queue=0 or (queue=2 and due<=?))""",
|
||||||
"select max(due) from cards where type=0") or 0
|
"select max(due) from cards where type=0") or 0
|
||||||
# takes care of mod + usn
|
# takes care of mod + usn
|
||||||
self.sortCards(ids, start=pmax+1)
|
self.sortCards(ids, start=pmax+1)
|
||||||
|
self.col.log(ids)
|
||||||
|
|
||||||
def reschedCards(self, ids, imin, imax):
|
def reschedCards(self, ids, imin, imax):
|
||||||
"Put cards in review queue with a new interval in days (min, max)."
|
"Put cards in review queue with a new interval in days (min, max)."
|
||||||
|
@ -1319,6 +1341,7 @@ and (queue=0 or (queue=2 and due<=?))""",
|
||||||
update cards set type=2,queue=2,ivl=:ivl,due=:due,
|
update cards set type=2,queue=2,ivl=:ivl,due=:due,
|
||||||
usn=:usn, mod=:mod, factor=:fact where id=:id and odid=0 and queue >=0""",
|
usn=:usn, mod=:mod, factor=:fact where id=:id and odid=0 and queue >=0""",
|
||||||
d)
|
d)
|
||||||
|
self.col.log(ids)
|
||||||
|
|
||||||
def resetCards(self, ids):
|
def resetCards(self, ids):
|
||||||
"Completely reset cards for export."
|
"Completely reset cards for export."
|
||||||
|
@ -1328,6 +1351,7 @@ usn=:usn, mod=:mod, factor=:fact where id=:id and odid=0 and queue >=0""",
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
"update cards set reps=0, lapses=0 where id in " + ids2str(nonNew))
|
"update cards set reps=0, lapses=0 where id in " + ids2str(nonNew))
|
||||||
self.forgetCards(nonNew)
|
self.forgetCards(nonNew)
|
||||||
|
self.col.log(ids)
|
||||||
|
|
||||||
# Repositioning new cards
|
# Repositioning new cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -1370,6 +1394,7 @@ and due >= ? and queue = 0""" % scids, now, self.col.usn(), shiftby, low)
|
||||||
d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id))
|
d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id))
|
||||||
self.col.db.executemany(
|
self.col.db.executemany(
|
||||||
"update cards set due=:due,mod=:now,usn=:usn where id = :cid", d)
|
"update cards set due=:due,mod=:now,usn=:usn where id = :cid", d)
|
||||||
|
self.col.log(cids)
|
||||||
|
|
||||||
def randomizeCards(self, did):
|
def randomizeCards(self, did):
|
||||||
cids = self.col.db.list("select id from cards where did = ?", did)
|
cids = self.col.db.list("select id from cards where did = ?", did)
|
||||||
|
|
|
@ -3,11 +3,15 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
import time, datetime, json
|
import time
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
import anki.js
|
import anki.js
|
||||||
from anki.utils import fmtTimeSpan, ids2str
|
from anki.utils import fmtTimeSpan, ids2str
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
|
|
||||||
|
|
||||||
# Card stats
|
# Card stats
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -56,6 +60,8 @@ class CardStats(object):
|
||||||
self.addLine(_("Card Type"), c.template()['name'])
|
self.addLine(_("Card Type"), c.template()['name'])
|
||||||
self.addLine(_("Note Type"), c.model()['name'])
|
self.addLine(_("Note Type"), c.model()['name'])
|
||||||
self.addLine(_("Deck"), self.col.decks.name(c.did))
|
self.addLine(_("Deck"), self.col.decks.name(c.did))
|
||||||
|
self.addLine(_("Note ID"), c.nid)
|
||||||
|
self.addLine(_("Card ID"), c.id)
|
||||||
self.txt += "</table>"
|
self.txt += "</table>"
|
||||||
return self.txt
|
return self.txt
|
||||||
|
|
||||||
|
|
14
anki/sync.py
14
anki/sync.py
|
@ -17,9 +17,12 @@ from hooks import runHook
|
||||||
import anki
|
import anki
|
||||||
|
|
||||||
# syncing vars
|
# syncing vars
|
||||||
HTTP_TIMEOUT = 30
|
HTTP_TIMEOUT = 90
|
||||||
HTTP_PROXY = None
|
HTTP_PROXY = None
|
||||||
|
|
||||||
|
# badly named; means no retries, and doesn't affect ssl connections
|
||||||
|
httplib2.RETRIES = 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# httplib2 >=0.7.7
|
# httplib2 >=0.7.7
|
||||||
_proxy_info_from_environment = httplib2.proxy_info_from_environment
|
_proxy_info_from_environment = httplib2.proxy_info_from_environment
|
||||||
|
@ -105,6 +108,7 @@ class Syncer(object):
|
||||||
# step 1: login & metadata
|
# step 1: login & metadata
|
||||||
runHook("sync", "login")
|
runHook("sync", "login")
|
||||||
meta = self.server.meta()
|
meta = self.server.meta()
|
||||||
|
self.col.log("rmeta", meta)
|
||||||
if not meta:
|
if not meta:
|
||||||
return "badAuth"
|
return "badAuth"
|
||||||
rscm = meta['scm']
|
rscm = meta['scm']
|
||||||
|
@ -125,19 +129,24 @@ class Syncer(object):
|
||||||
# and require confirmation if it's non-empty
|
# and require confirmation if it's non-empty
|
||||||
pass
|
pass
|
||||||
meta = self.meta()
|
meta = self.meta()
|
||||||
|
self.col.log("lmeta", meta)
|
||||||
self.lmod = meta['mod']
|
self.lmod = meta['mod']
|
||||||
self.minUsn = meta['usn']
|
self.minUsn = meta['usn']
|
||||||
lscm = meta['scm']
|
lscm = meta['scm']
|
||||||
lts = meta['ts']
|
lts = meta['ts']
|
||||||
if abs(rts - lts) > 300:
|
if abs(rts - lts) > 300:
|
||||||
|
self.col.log("clock off")
|
||||||
return "clockOff"
|
return "clockOff"
|
||||||
if self.lmod == self.rmod:
|
if self.lmod == self.rmod:
|
||||||
|
self.col.log("no changes")
|
||||||
return "noChanges"
|
return "noChanges"
|
||||||
elif lscm != rscm:
|
elif lscm != rscm:
|
||||||
|
self.col.log("schema diff")
|
||||||
return "fullSync"
|
return "fullSync"
|
||||||
self.lnewer = self.lmod > self.rmod
|
self.lnewer = self.lmod > self.rmod
|
||||||
# step 1.5: check collection is valid
|
# step 1.5: check collection is valid
|
||||||
if not self.col.basicCheck():
|
if not self.col.basicCheck():
|
||||||
|
self.col.log("basic check")
|
||||||
return "basicCheckFailed"
|
return "basicCheckFailed"
|
||||||
# step 2: deletions
|
# step 2: deletions
|
||||||
runHook("sync", "meta")
|
runHook("sync", "meta")
|
||||||
|
@ -154,6 +163,7 @@ class Syncer(object):
|
||||||
while 1:
|
while 1:
|
||||||
runHook("sync", "stream")
|
runHook("sync", "stream")
|
||||||
chunk = self.server.chunk()
|
chunk = self.server.chunk()
|
||||||
|
self.col.log("server chunk", chunk)
|
||||||
self.applyChunk(chunk=chunk)
|
self.applyChunk(chunk=chunk)
|
||||||
if chunk['done']:
|
if chunk['done']:
|
||||||
break
|
break
|
||||||
|
@ -162,6 +172,7 @@ class Syncer(object):
|
||||||
while 1:
|
while 1:
|
||||||
runHook("sync", "stream")
|
runHook("sync", "stream")
|
||||||
chunk = self.chunk()
|
chunk = self.chunk()
|
||||||
|
self.col.log("client chunk", chunk)
|
||||||
self.server.applyChunk(chunk=chunk)
|
self.server.applyChunk(chunk=chunk)
|
||||||
if chunk['done']:
|
if chunk['done']:
|
||||||
break
|
break
|
||||||
|
@ -478,6 +489,7 @@ from notes where %s""" % d)
|
||||||
for r in data:
|
for r in data:
|
||||||
if r[0] not in lmods or lmods[r[0]] < r[modIdx]:
|
if r[0] not in lmods or lmods[r[0]] < r[modIdx]:
|
||||||
update.append(r)
|
update.append(r)
|
||||||
|
self.col.log(table, data)
|
||||||
return update
|
return update
|
||||||
|
|
||||||
def mergeCards(self, cards):
|
def mergeCards(self, cards):
|
||||||
|
|
|
@ -45,7 +45,7 @@ class DataModel(QAbstractTableModel):
|
||||||
def getCard(self, index):
|
def getCard(self, index):
|
||||||
id = self.cards[index.row()]
|
id = self.cards[index.row()]
|
||||||
if not id in self.cardObjs:
|
if not id in self.cardObjs:
|
||||||
self.cardObjs[id] = self.col.getCard(id)
|
self.cardObjs[id] = self.col.getCard(id, log=False)
|
||||||
return self.cardObjs[id]
|
return self.cardObjs[id]
|
||||||
|
|
||||||
def refreshNote(self, note):
|
def refreshNote(self, note):
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
from anki.consts import NEW_CARDS_RANDOM
|
from operator import itemgetter
|
||||||
|
|
||||||
|
from anki.consts import NEW_CARDS_RANDOM
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
import aqt
|
import aqt
|
||||||
from aqt.utils import showInfo, showWarning, openHelp, getOnlyText, askUser, \
|
from aqt.utils import showInfo, showWarning, openHelp, getOnlyText, askUser, \
|
||||||
tooltip
|
tooltip
|
||||||
from operator import itemgetter
|
|
||||||
|
|
||||||
class DeckConf(QDialog):
|
class DeckConf(QDialog):
|
||||||
def __init__(self, mw, deck):
|
def __init__(self, mw, deck):
|
||||||
|
@ -189,6 +189,7 @@ class DeckConf(QDialog):
|
||||||
f.fi1.setValue(c['ivlFct']*100)
|
f.fi1.setValue(c['ivlFct']*100)
|
||||||
f.maxIvl.setValue(c['maxIvl'])
|
f.maxIvl.setValue(c['maxIvl'])
|
||||||
f.revplim.setText(self.parentLimText('rev'))
|
f.revplim.setText(self.parentLimText('rev'))
|
||||||
|
f.buryRev.setChecked(c.get("bury", True))
|
||||||
# lapse
|
# lapse
|
||||||
c = self.conf['lapse']
|
c = self.conf['lapse']
|
||||||
f.lapSteps.setText(self.listToUser(c['delays']))
|
f.lapSteps.setText(self.listToUser(c['delays']))
|
||||||
|
@ -270,6 +271,7 @@ class DeckConf(QDialog):
|
||||||
c['ease4'] = f.easyBonus.value()/100.0
|
c['ease4'] = f.easyBonus.value()/100.0
|
||||||
c['ivlFct'] = f.fi1.value()/100.0
|
c['ivlFct'] = f.fi1.value()/100.0
|
||||||
c['maxIvl'] = f.maxIvl.value()
|
c['maxIvl'] = f.maxIvl.value()
|
||||||
|
c['bury'] = f.buryRev.isChecked()
|
||||||
# lapse
|
# lapse
|
||||||
c = self.conf['lapse']
|
c = self.conf['lapse']
|
||||||
self.updateList(c, 'delays', f.lapSteps, minSize=0)
|
self.updateList(c, 'delays', f.lapSteps, minSize=0)
|
||||||
|
|
46
aqt/main.py
46
aqt/main.py
|
@ -2,20 +2,30 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import os, sys, re, traceback, signal
|
import os
|
||||||
|
import pprint
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
import signal
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from send2trash import send2trash
|
from send2trash import send2trash
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
|
||||||
from anki import Collection
|
from anki import Collection
|
||||||
from anki.utils import isWin, isMac, intTime, splitFields, ids2str
|
from anki.utils import isWin, isMac, intTime, splitFields, ids2str
|
||||||
from anki.hooks import runHook, addHook
|
|
||||||
|
|
||||||
import aqt, aqt.progress, aqt.webview, aqt.toolbar, aqt.stats
|
from anki.hooks import runHook, addHook
|
||||||
|
import aqt
|
||||||
|
import aqt.progress
|
||||||
|
import aqt.webview
|
||||||
|
import aqt.toolbar
|
||||||
|
import aqt.stats
|
||||||
from aqt.utils import restoreGeom, showInfo, showWarning,\
|
from aqt.utils import restoreGeom, showInfo, showWarning,\
|
||||||
restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \
|
restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \
|
||||||
openHelp, openLink, checkInvalidFilename
|
openHelp, openLink, checkInvalidFilename
|
||||||
|
|
||||||
|
|
||||||
class AnkiQt(QMainWindow):
|
class AnkiQt(QMainWindow):
|
||||||
def __init__(self, app, profileManager, args):
|
def __init__(self, app, profileManager, args):
|
||||||
QMainWindow.__init__(self)
|
QMainWindow.__init__(self)
|
||||||
|
@ -159,7 +169,6 @@ class AnkiQt(QMainWindow):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def profileNameOk(self, str):
|
def profileNameOk(self, str):
|
||||||
from anki.utils import invalidFilename, invalidFilenameChars
|
|
||||||
return not checkInvalidFilename(str)
|
return not checkInvalidFilename(str)
|
||||||
|
|
||||||
def onAddProfile(self):
|
def onAddProfile(self):
|
||||||
|
@ -261,7 +270,8 @@ To import into a password protected profile, please open the profile before atte
|
||||||
showWarning("""\
|
showWarning("""\
|
||||||
Your collection is corrupt. Please see the manual for \
|
Your collection is corrupt. Please see the manual for \
|
||||||
how to restore from a backup.""")
|
how to restore from a backup.""")
|
||||||
return self.unloadProfile()
|
self.unloadProfile()
|
||||||
|
raise
|
||||||
self.hideSchemaMsg = False
|
self.hideSchemaMsg = False
|
||||||
self.progress.setupDB(self.col.db)
|
self.progress.setupDB(self.col.db)
|
||||||
self.maybeEnableUndo()
|
self.maybeEnableUndo()
|
||||||
|
@ -829,6 +839,7 @@ the problem and restart Anki.""")
|
||||||
def setupHooks(self):
|
def setupHooks(self):
|
||||||
addHook("modSchema", self.onSchemaMod)
|
addHook("modSchema", self.onSchemaMod)
|
||||||
addHook("remNotes", self.onRemNotes)
|
addHook("remNotes", self.onRemNotes)
|
||||||
|
addHook("log", self.onLog)
|
||||||
|
|
||||||
# Log note deletion
|
# Log note deletion
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -846,6 +857,23 @@ the problem and restart Anki.""")
|
||||||
f.write(("\t".join([str(id), str(mid)] + fields)).encode("utf8"))
|
f.write(("\t".join([str(id), str(mid)] + fields)).encode("utf8"))
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
|
|
||||||
|
# Debug logging
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def onLog(self, args, kwargs):
|
||||||
|
def customRepr(x):
|
||||||
|
if isinstance(x, basestring):
|
||||||
|
return x
|
||||||
|
return pprint.pformat(x)
|
||||||
|
path, num, fn, y = traceback.extract_stack(
|
||||||
|
limit=4+kwargs.get("stack", 0))[0]
|
||||||
|
buf = u"[%s] %s:%s(): %s" % (intTime(), os.path.basename(path), fn,
|
||||||
|
", ".join([customRepr(x) for x in args]))
|
||||||
|
lpath = re.sub("\.anki2$", ".log", self.pm.collectionPath())
|
||||||
|
open(lpath, "ab").write(buf.encode("utf8") + "\n")
|
||||||
|
if os.environ.get("LOG"):
|
||||||
|
print buf
|
||||||
|
|
||||||
# Schema modifications
|
# Schema modifications
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -1051,6 +1079,8 @@ will be lost. Continue?"""))
|
||||||
elif isWin:
|
elif isWin:
|
||||||
# make sure ctypes is bundled
|
# make sure ctypes is bundled
|
||||||
from ctypes import windll, wintypes
|
from ctypes import windll, wintypes
|
||||||
|
_dummy = windll
|
||||||
|
_dummy = wintypes
|
||||||
|
|
||||||
def maybeHideAccelerators(self, tgt=None):
|
def maybeHideAccelerators(self, tgt=None):
|
||||||
if not self.hideMenuAccels:
|
if not self.hideMenuAccels:
|
||||||
|
@ -1076,6 +1106,10 @@ will be lost. Continue?"""))
|
||||||
self.connect(self.app, SIGNAL("appMsg"), self.onAppMsg)
|
self.connect(self.app, SIGNAL("appMsg"), self.onAppMsg)
|
||||||
|
|
||||||
def onAppMsg(self, buf):
|
def onAppMsg(self, buf):
|
||||||
|
if not isinstance(buf, unicode):
|
||||||
|
# even though we're sending this as unicode up above,
|
||||||
|
# a bug report still came in that we were receiving a qbytearray
|
||||||
|
buf = unicode(buf, "utf8", "ignore")
|
||||||
if self.state == "startup":
|
if self.state == "startup":
|
||||||
# try again in a second
|
# try again in a second
|
||||||
return self.progress.timer(1000, lambda: self.onAppMsg(buf), False)
|
return self.progress.timer(1000, lambda: self.onAppMsg(buf), False)
|
||||||
|
|
|
@ -271,13 +271,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
|
||||||
<widget class="QLabel" name="revplim">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="label_33">
|
<widget class="QLabel" name="label_33">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -292,25 +285,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="QDoubleSpinBox" name="fi1">
|
|
||||||
<property name="decimals">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="minimum">
|
|
||||||
<double>0.000000000000000</double>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<double>999.000000000000000</double>
|
|
||||||
</property>
|
|
||||||
<property name="singleStep">
|
|
||||||
<double>1.000000000000000</double>
|
|
||||||
</property>
|
|
||||||
<property name="value">
|
|
||||||
<double>100.000000000000000</double>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -335,6 +309,39 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QLabel" name="revplim">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QDoubleSpinBox" name="fi1">
|
||||||
|
<property name="decimals">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>999.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>100.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="3">
|
||||||
|
<widget class="QCheckBox" name="buryRev">
|
||||||
|
<property name="text">
|
||||||
|
<string>Bury related reviews until the next day</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -616,6 +623,7 @@
|
||||||
<tabstop>easyBonus</tabstop>
|
<tabstop>easyBonus</tabstop>
|
||||||
<tabstop>fi1</tabstop>
|
<tabstop>fi1</tabstop>
|
||||||
<tabstop>maxIvl</tabstop>
|
<tabstop>maxIvl</tabstop>
|
||||||
|
<tabstop>buryRev</tabstop>
|
||||||
<tabstop>lapSteps</tabstop>
|
<tabstop>lapSteps</tabstop>
|
||||||
<tabstop>lapMult</tabstop>
|
<tabstop>lapMult</tabstop>
|
||||||
<tabstop>lapMinInt</tabstop>
|
<tabstop>lapMinInt</tabstop>
|
||||||
|
|
Loading…
Reference in a new issue