def downloadRemote(deck): mdir = deck.mediaDir(create=True) refs = {} deck.startProgress() for (question, answer) in deck.s.all( "select question, answer from cards"): for txt in (question, answer): for f in mediaFiles(txt, remote=True): refs[f] = True tmpdir = tempfile.mkdtemp(prefix="oldanki") failed = [] passed = [] for c, link in enumerate(refs.keys()): try: path = os.path.join(tmpdir, os.path.basename(link)) url = urllib.request.urlopen(link) open(path, "wb").write(url.read()) newpath = copyToMedia(deck, path) passed.append([link, newpath]) except: failed.append(link) deck.updateProgress(label=_("Download %d...") % c) for (url, name) in passed: deck.s.statement( "update fields set value = replace(value, :url, :name)", url=url, name=name) deck.updateProgress(label=_("Updating references...")) deck.updateProgress(label=_("Updating cards...")) # rebuild entire q/a cache for m in deck.models: deck.updateCardsFromModel(m, dirty=True) deck.finishProgress() deck.flushMod() return (passed, failed)
def downloadRemote(deck): mdir = deck.mediaDir(create=True) refs = {} deck.startProgress() for (question, answer) in deck.s.all( "select question, answer from cards"): for txt in (question, answer): for f in mediaFiles(txt, remote=True): refs[f] = True tmpdir = tempfile.mkdtemp(prefix="oldanki") failed = [] passed = [] for c, link in enumerate(refs.keys()): try: path = os.path.join(tmpdir, os.path.basename(link)) url = urllib2.urlopen(link) open(path, "wb").write(url.read()) newpath = copyToMedia(deck, path) passed.append([link, newpath]) except: failed.append(link) deck.updateProgress(label=_("Download %d...") % c) for (url, name) in passed: deck.s.statement( "update fields set value = replace(value, :url, :name)", url=url, name=name) deck.updateProgress(label=_("Updating references...")) deck.updateProgress(label=_("Updating cards...")) # rebuild entire q/a cache for m in deck.models: deck.updateCardsFromModel(m, dirty=True) deck.finishProgress() deck.flushMod() return (passed, failed)
def errmsg(type): msg = _("Error executing %s.\n") % type try: log = open(os.path.join(tmpdir, "latex_log.txt")).read() msg += "<small><pre>" + cgi.escape(log) + "</pre></small>" except: msg += _("Have you installed latex and dvipng?") pass return msg
def payloadChanges(self, payload): h = { "lf": len(payload["added-facts"]["facts"]), "rf": len(payload["missing-facts"]), "lc": len(payload["added-cards"]), "rc": len(payload["missing-cards"]), "lm": len(payload["added-models"]), "rm": len(payload["missing-models"]), } if self.localTime > self.remoteTime: h["ls"] = _("all") h["rs"] = 0 else: h["ls"] = 0 h["rs"] = _("all") return h
def doExport(self, file): ids = self.cardIds() strids = ids2str(ids) self.deck.startProgress((len(ids) + 1) / 50) self.deck.updateProgress(_("Exporting...")) cards = self.deck.s.all(""" select cards.question, cards.answer, cards.id from cards where cards.id in %s order by cards.created""" % strids) self.deck.updateProgress() if self.includeTags: self.cardTags = dict(self.deck.s.all(""" select cards.id, facts.tags from cards, facts where cards.factId = facts.id and cards.id in %s order by cards.created""" % strids)) out = u"\n".join(["%s\t%s%s" % ( self.escapeText(c[0], removeFields=True), self.escapeText(c[1], removeFields=True), self.tags(c[2])) for c in cards]) if out: out += "\n" file.write(out.encode("utf-8")) self.deck.finishProgress()
def downloadMissing(deck): urlbase = deck.getVar("mediaURL") if not urlbase: return None mdir = deck.mediaDir(create=True) deck.startProgress() missing = 0 grabbed = 0 for c, (f, sum) in enumerate(deck.s.all( "select filename, originalPath from media")): path = os.path.join(mdir, f) if not os.path.exists(path): try: rpath = urlbase + f url = urllib2.urlopen(rpath) open(f, "wb").write(url.read()) grabbed += 1 except: if sum: # the file is supposed to exist deck.finishProgress() return (False, rpath) else: # ignore and keep going missing += 1 deck.updateProgress(label=_("File %d...") % (grabbed+missing)) deck.finishProgress() return (True, grabbed, missing)
def payloadChanges(self, payload): h = { 'lf': len(payload['added-facts']['facts']), 'rf': len(payload['missing-facts']), 'lc': len(payload['added-cards']), 'rc': len(payload['missing-cards']), 'lm': len(payload['added-models']), 'rm': len(payload['missing-models']), } if self.localTime > self.remoteTime: h['ls'] = _('all') h['rs'] = 0 else: h['ls'] = 0 h['rs'] = _('all') return h
def downloadMissing(deck): urlbase = deck.getVar("mediaURL") if not urlbase: return None mdir = deck.mediaDir(create=True) deck.startProgress() missing = 0 grabbed = 0 for c, (f, sum) in enumerate(deck.s.all( "select filename, originalPath from media")): path = os.path.join(mdir, f) if not os.path.exists(path): try: rpath = urlbase + f url = urllib.request.urlopen(rpath) open(f, "wb").write(url.read()) grabbed += 1 except: if sum: # the file is supposed to exist deck.finishProgress() return (False, rpath) else: # ignore and keep going missing += 1 deck.updateProgress(label=_("File %d...") % (grabbed+missing)) deck.finishProgress() return (True, grabbed, missing)
def doExport(self, file): ids = self.cardIds() strids = ids2str(ids) self.deck.startProgress((len(ids) + 1) / 50) self.deck.updateProgress(_("Exporting...")) cards = self.deck.s.all(""" select cards.question, cards.answer, cards.id from cards where cards.id in %s order by cards.created""" % strids) self.deck.updateProgress() if self.includeTags: self.cardTags = dict( self.deck.s.all(""" select cards.id, facts.tags from cards, facts where cards.factId = facts.id and cards.id in %s order by cards.created""" % strids)) out = u"\n".join([ "%s\t%s%s" % (self.escapeText(c[0], removeFields=True), self.escapeText(c[1], removeFields=True), self.tags(c[2])) for c in cards ]) if out: out += "\n" file.write(out.encode("utf-8")) self.deck.finishProgress()
def doExport(self, file): cardIds = self.cardIds() self.deck.startProgress() self.deck.updateProgress(_("Exporting...")) facts = self.deck.s.all(""" select factId, value, facts.created from facts, fields where facts.id in (select distinct factId from cards where cards.id in %s) and facts.id = fields.factId order by factId, ordinal""" % ids2str(cardIds)) txt = "" self.deck.updateProgress() if self.includeTags: self.factTags = dict( self.deck.s.all("select id, tags from facts where id in %s" % ids2str([fact[0] for fact in facts]))) groups = itertools.groupby(facts, itemgetter(0)) groups = [[x for x in y[1]] for y in groups] groups = [(group[0][2], "\t".join([self.escapeText(x[1]) for x in group]) + self.tags(group[0][0])) for group in groups] self.deck.updateProgress() groups.sort(key=itemgetter(0)) out = [ret[1] for ret in groups] self.count = len(out) out = "\n".join(out) file.write(out.encode("utf-8")) self.deck.finishProgress()
def doExport(self, file): cardIds = self.cardIds() self.deck.startProgress() self.deck.updateProgress(_("Exporting...")) facts = self.deck.s.all(""" select factId, value, facts.created from facts, fields where facts.id in (select distinct factId from cards where cards.id in %s) and facts.id = fields.factId order by factId, ordinal""" % ids2str(cardIds)) txt = "" self.deck.updateProgress() if self.includeTags: self.factTags = dict(self.deck.s.all( "select id, tags from facts where id in %s" % ids2str([fact[0] for fact in facts]))) groups = itertools.groupby(facts, itemgetter(0)) groups = [[x for x in y[1]] for y in groups] groups = [(group[0][2], "\t".join([self.escapeText(x[1]) for x in group]) + self.tags(group[0][0])) for group in groups] self.deck.updateProgress() groups.sort(key=itemgetter(0)) out = [ret[1] for ret in groups] self.count = len(out) out = "\n".join(out) file.write(out.encode("utf-8")) self.deck.finishProgress()
def RecoveryModel(): m = Model(_('Recovery')) m.addFieldModel(FieldModel(u'Question', False, False)) m.addFieldModel(FieldModel(u'Answer', False, False)) m.addCardModel(CardModel(u'Single', u'{{{Question}}}', u'{{{Answer}}}')) m.tags = u"Recovery" return m
def BasicModel(): m = Model(_('Basic')) m.addFieldModel(FieldModel('Front', True, True)) m.addFieldModel(FieldModel('Back', False, False)) m.addCardModel(CardModel('Forward', '%(Front)s', '%(Back)s')) m.addCardModel(CardModel('Reverse', '%(Back)s', '%(Front)s', active=False)) m.tags = "Basic" return m
def BasicModel(): m = Model(_('Basic')) m.addFieldModel(FieldModel(u'Front', True, True)) m.addFieldModel(FieldModel(u'Back', False, False)) m.addCardModel(CardModel(u'Forward', u'%(Front)s', u'%(Back)s')) m.addCardModel(CardModel(u'Reverse', u'%(Back)s', u'%(Front)s', active=False)) m.tags = u"Basic" return m
def payloadChangeReport(self, payload): p = self.payloadChanges(payload) return _("""\ <table> <tr><td><b>Added/Changed </b></td> <td><b>Here </b></td><td><b>Server</b></td></tr> <tr><td>Cards</td><td>%(lc)d</td><td>%(rc)d</td></tr> <tr><td>Facts</td><td>%(lf)d</td><td>%(rf)d</td></tr> <tr><td>Models</td><td>%(lm)d</td><td>%(rm)d</td></tr> <tr><td>Stats</td><td>%(ls)s</td><td>%(rs)s</td></tr> </table>""") % p
def payloadChangeReport(self, payload): p = self.payloadChanges(payload) return ( _( """\ <table> <tr><td><b>Added/Changed </b></td> <td><b>Here </b></td><td><b>Server</b></td></tr> <tr><td>Cards</td><td>%(lc)d</td><td>%(rc)d</td></tr> <tr><td>Facts</td><td>%(lf)d</td><td>%(rf)d</td></tr> <tr><td>Models</td><td>%(lm)d</td><td>%(rm)d</td></tr> <tr><td>Stats</td><td>%(ls)s</td><td>%(rs)s</td></tr> </table>""" ) % p )
class TextCardExporter(Exporter): key = _("Text files (*.txt)") ext = ".txt" def __init__(self, deck): Exporter.__init__(self, deck) self.includeTags = False def doExport(self, file): ids = self.cardIds() strids = ids2str(ids) self.deck.startProgress((len(ids) + 1) / 50) self.deck.updateProgress(_("Exporting...")) cards = self.deck.s.all(""" select cards.question, cards.answer, cards.id from cards where cards.id in %s order by cards.created""" % strids) self.deck.updateProgress() if self.includeTags: self.cardTags = dict( self.deck.s.all(""" select cards.id, facts.tags from cards, facts where cards.factId = facts.id and cards.id in %s order by cards.created""" % strids)) out = u"\n".join([ "%s\t%s%s" % (self.escapeText(c[0], removeFields=True), self.escapeText(c[1], removeFields=True), self.tags(c[2])) for c in cards ]) if out: out += "\n" file.write(out.encode("utf-8")) self.deck.finishProgress() def tags(self, id): if self.includeTags: return "\t" + ", ".join(parseTags(self.cardTags[id])) return ""
def alignmentLabels(): return { 0: _("Center"), 1: _("Left"), 2: _("Right"), }
def exporters(): return ( (_("Anki Deck (*.oldanki)"), AnkiExporter), (_("Cards in tab-separated text file (*.txt)"), TextCardExporter), (_("Facts in tab-separated text file (*.txt)"), TextFactExporter))
def report(self): "Return an HTML string with a report." fmtPerc = oldanki.utils.fmtPercentage fmtFloat = oldanki.utils.fmtFloat if self.deck.isEmpty(): return _("Please add some cards first.") + "<p/>" d = self.deck html = "<h1>" + _("Deck Statistics") + "</h1>" html += _("Deck created: <b>%s</b> ago<br>") % self.createdTimeStr() total = d.cardCount new = d.newCountAll() young = d.youngCardCount() old = d.matureCardCount() newP = new / float(total) * 100 youngP = young / float(total) * 100 oldP = old / float(total) * 100 stats = d.getStats() (stats["new"], stats["newP"]) = (new, newP) (stats["old"], stats["oldP"]) = (old, oldP) (stats["young"], stats["youngP"]) = (young, youngP) html += _("Total number of cards:") + " <b>%d</b><br>" % total html += _( "Total number of facts:") + " <b>%d</b><br><br>" % d.factCount html += "<b>" + _("Card Maturity") + "</b><br>" html += _("Mature cards: <!--card count-->" ) + " <b>%(old)d</b> (%(oldP)s)<br>" % { 'old': stats['old'], 'oldP': fmtPerc(stats['oldP']) } html += _("Young cards: <!--card count-->" ) + " <b>%(young)d</b> (%(youngP)s)<br>" % { 'young': stats['young'], 'youngP': fmtPerc(stats['youngP']) } html += _("Unseen cards:") + " <b>%(new)d</b> (%(newP)s)<br>" % { 'new': stats['new'], 'newP': fmtPerc(stats['newP']) } avgInt = self.getAverageInterval() if avgInt: html += _("Average interval: ") + ( "<b>%s</b> ") % fmtFloat(avgInt) + _("days") html += "<br>" html += "<br>" html += "<b>" + _("Correct Answers") + "</b><br>" html += _("Mature cards: <!--correct answers-->") + " <b>" + fmtPerc( stats['gMatureYes%']) + ("</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf': stats['gMatureYes'], 'totalSum': stats['gMatureTotal'] } + "<br>") html += _("Young cards: <!--correct answers-->") + " <b>" + fmtPerc( stats['gYoungYes%']) + ("</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf': stats['gYoungYes'], 'totalSum': stats['gYoungTotal'] } + "<br>") html += _("First-seen cards:") + " <b>" + fmtPerc( stats['gNewYes%']) + ("</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf': stats['gNewYes'], 'totalSum': stats['gNewTotal'] } + "<br><br>") # average pending time existing = d.cardCount - d.newCountToday def tr(a, b): return "<tr><td>%s</td><td align=right>%s</td></tr>" % (a, b) def repsPerDay(reps, days): retval = ("<b>%d</b> " % reps) + ngettext("rep", "reps", reps) retval += ("/<b>%d</b> " % days) + ngettext("day", "days", days) return retval if existing and avgInt: html += "<b>" + _("Recent Work") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr( _("In last week"), repsPerDay(self.getRepsDone(-7, 0), self.getDaysReviewed(-7, 0))) html += tr( _("In last month"), repsPerDay(self.getRepsDone(-30, 0), self.getDaysReviewed(-30, 0))) html += tr( _("In last 3 months"), repsPerDay(self.getRepsDone(-92, 0), self.getDaysReviewed(-92, 0))) html += tr( _("In last 6 months"), repsPerDay(self.getRepsDone(-182, 0), self.getDaysReviewed(-182, 0))) html += tr( _("In last year"), repsPerDay(self.getRepsDone(-365, 0), self.getDaysReviewed(-365, 0))) html += tr( _("Deck life"), repsPerDay(self.getRepsDone(-13000, 0), self.getDaysReviewed(-13000, 0))) html += "</table>" html += "<br><br><b>" + _("Average Daily Reviews") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr(_("Deck life"), ("<b>%s</b> ") % (fmtFloat(self.getSumInverseRoundInterval())) + _("cards/day")) html += tr(_("In next week"), ("<b>%s</b> ") % (fmtFloat(self.getWorkloadPeriod(7))) + _("cards/day")) html += tr(_("In next month"), ("<b>%s</b> ") % (fmtFloat(self.getWorkloadPeriod(30))) + _("cards/day")) html += tr(_("In last week"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(7))) + _("cards/day")) html += tr(_("In last month"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(30))) + _("cards/day")) html += tr(_("In last 3 months"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(92))) + _("cards/day")) html += tr(_("In last 6 months"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(182))) + _("cards/day")) html += tr(_("In last year"), ("<b>%s</b> ") % (fmtFloat(self.getPastWorkloadPeriod(365))) + _("cards/day")) html += "</table>" html += "<br><br><b>" + _("Average Added") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr( _("Deck life"), _("<b>%(a)s</b>/day, <b>%(b)s</b>/mon") % { 'a': fmtFloat(self.newAverage()), 'b': fmtFloat(self.newAverage() * 30) }) np = self.getNewPeriod(7) html += tr( _("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(7)) })) np = self.getNewPeriod(30) html += tr( _("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(30)) })) np = self.getNewPeriod(92) html += tr( _("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(92)) })) np = self.getNewPeriod(182) html += tr( _("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(182)) })) np = self.getNewPeriod(365) html += tr( _("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(365)) })) html += "</table>" html += "<br><br><b>" + _("Average New Seen") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" np = self.getFirstPeriod(7) html += tr( _("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(7)) })) np = self.getFirstPeriod(30) html += tr( _("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(30)) })) np = self.getFirstPeriod(92) html += tr( _("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(92)) })) np = self.getFirstPeriod(182) html += tr( _("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(182)) })) np = self.getFirstPeriod(365) html += tr( _("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ({ 'a': np, 'b': fmtFloat(np / float(365)) })) html += "</table>" html += "<br><br><b>" + _("Card Ease") + "</b><br>" html += _("Lowest factor: %.2f") % d.s.scalar( "select min(factor) from cards") + "<br>" html += _("Average factor: %.2f") % d.s.scalar( "select avg(factor) from cards") + "<br>" html += _("Highest factor: %.2f") % d.s.scalar( "select max(factor) from cards") + "<br>" html = runFilter("deckStats", html) return html
def strTime(self, tm): s = oldanki.utils.fmtTimeSpan(time.time() - tm) return _("%s ago") % s
def report(self): c = self.card fmt = oldanki.utils.fmtTimeSpan fmtFloat = oldanki.utils.fmtFloat self.txt = "<table>" self.addLine(_("Added"), self.strTime(c.created)) if c.firstAnswered: self.addLine(_("First Review"), self.strTime(c.firstAnswered)) self.addLine(_("Changed"), self.strTime(c.modified)) if c.reps: next = time.time() - c.combinedDue if next > 0: next = _("%s ago") % fmt(next) else: next = _("in %s") % fmt(abs(next)) self.addLine(_("Due"), next) self.addLine(_("Interval"), fmt(c.interval * 86400)) self.addLine(_("Ease"), fmtFloat(c.factor, point=2)) if c.lastDue: last = _("%s ago") % fmt(time.time() - c.lastDue) self.addLine(_("Last Due"), last) if c.interval != c.lastInterval: # don't show the last interval if it hasn't been updated yet self.addLine(_("Last Interval"), fmt(c.lastInterval * 86400)) self.addLine(_("Last Ease"), fmtFloat(c.lastFactor, point=2)) if c.reps: self.addLine(_("Reviews"), "%d/%d (s=%d)" % (c.yesCount, c.reps, c.successive)) avg = fmt(c.averageTime, point=2) self.addLine(_("Average Time"), avg) total = fmt(c.reviewTime, point=2) self.addLine(_("Total Time"), total) self.addLine(_("Model Tags"), c.fact.model.tags) self.addLine(_("Card Template") + " " * 5, c.cardModel.name) self.txt += "</table>" return self.txt
"hours": lambda n: ngettext("%s hour", "%s hours", n), "minutes": lambda n: ngettext("%s minute", "%s minutes", n), "seconds": lambda n: ngettext("%s second", "%s seconds", n), } afterTimeTable = { "years": lambda n: ngettext("%s year<!--after-->", "%s years<!--after-->", n), "months": lambda n: ngettext("%s month<!--after-->", "%s months<!--after-->", n), "days": lambda n: ngettext("%s day<!--after-->", "%s days<!--after-->", n), "hours": lambda n: ngettext("%s hour<!--after-->", "%s hours<!--after-->", n), "minutes": lambda n: ngettext("%s minute<!--after-->", "%s minutes<!--after-->", n), "seconds": lambda n: ngettext("%s second<!--after-->", "%s seconds<!--after-->", n), } shortTimeTable = { "years": _("%sy"), "months": _("%sm"), "days": _("%sd"), "hours": _("%sh"), "minutes": _("%sm"), "seconds": _("%ss"), } def fmtTimeSpan(time, pad=0, point=0, short=False, after=False): "Return a string representing a time span (eg '2 days')." (type, point) = optimalPeriod(time, point) time = convertSecondsTo(time, type) if not point: time = math.floor(time) if short:
def rebuildMediaDir(deck, delete=False, dirty=True): mdir = deck.mediaDir() if not mdir: return (0, 0) deck.startProgress(title=_("Check Media DB")) # set all ref counts to 0 deck.s.statement("update media set size = 0") # look through cards for media references refs = {} normrefs = {} def norm(s): if isinstance(s, str): return unicodedata.normalize('NFD', s) return s for (question, answer) in deck.s.all( "select question, answer from cards"): for txt in (question, answer): for f in mediaFiles(txt): if f in refs: refs[f] += 1 else: refs[f] = 1 normrefs[norm(f)] = True # update ref counts for (file, count) in list(refs.items()): updateMediaCount(deck, file, count) # find unused media unused = [] for file in os.listdir(mdir): path = os.path.join(mdir, file) if not os.path.isfile(path): # ignore directories continue nfile = norm(file) if nfile not in normrefs: unused.append(file) # optionally delete if delete: for f in unused: path = os.path.join(mdir, f) os.unlink(path) # remove entries in db for unused media removeUnusedMedia(deck) # check md5s are up to date update = [] for (file, created, md5) in deck.s.all( "select filename, created, originalPath from media"): path = os.path.join(mdir, file) if not os.path.exists(path): if md5: update.append({'f':file, 'sum':"", 'c':time.time()}) else: sum = str( checksum(open(os.path.join(mdir, file), "rb").read())) if md5 != sum: update.append({'f':file, 'sum':sum, 'c':time.time()}) if update: deck.s.statements(""" update media set originalPath = :sum, created = :c where filename = :f""", update) # update deck and get return info if dirty: deck.flushMod() nohave = deck.s.column0("select filename from media where originalPath = ''") deck.finishProgress() return (nohave, unused)
def report(self): c = self.card fmt = oldanki.utils.fmtTimeSpan fmtFloat = oldanki.utils.fmtFloat self.txt = "<table>" self.addLine(_("Added"), self.strTime(c.created)) if c.firstAnswered: self.addLine(_("First Review"), self.strTime(c.firstAnswered)) self.addLine(_("Changed"), self.strTime(c.modified)) if c.reps: next = time.time() - c.combinedDue if next > 0: next = _("%s ago") % fmt(next) else: next = _("in %s") % fmt(abs(next)) self.addLine(_("Due"), next) self.addLine(_("Interval"), fmt(c.interval * 86400)) self.addLine(_("Ease"), fmtFloat(c.factor, point=2)) if c.lastDue: last = _("%s ago") % fmt(time.time() - c.lastDue) self.addLine(_("Last Due"), last) if c.interval != c.lastInterval: # don't show the last interval if it hasn't been updated yet self.addLine(_("Last Interval"), fmt(c.lastInterval * 86400)) self.addLine(_("Last Ease"), fmtFloat(c.lastFactor, point=2)) if c.reps: self.addLine(_("Reviews"), "%d/%d (s=%d)" % ( c.yesCount, c.reps, c.successive)) avg = fmt(c.averageTime, point=2) self.addLine(_("Average Time"),avg) total = fmt(c.reviewTime, point=2) self.addLine(_("Total Time"), total) self.addLine(_("Model Tags"), c.fact.model.tags) self.addLine(_("Card Template") + " "*5, c.cardModel.name) self.txt += "</table>" return self.txt
class AnkiExporter(Exporter): key = _("Anki Deck (*.oldanki)") ext = ".oldanki" def __init__(self, deck): Exporter.__init__(self, deck) self.includeSchedulingInfo = False self.includeMedia = True def exportInto(self, path): n = 3 if not self.includeSchedulingInfo: n += 1 self.deck.startProgress(n) self.deck.updateProgress(_("Exporting...")) try: os.unlink(path) except (IOError, OSError): pass self.newDeck = DeckStorage.Deck(path) client = SyncClient(self.deck) server = SyncServer(self.newDeck) client.setServer(server) client.localTime = self.deck.modified client.remoteTime = 0 self.deck.s.flush() # set up a custom change list and sync lsum = self.localSummary() rsum = server.summary(0) self.deck.updateProgress() payload = client.genPayload((lsum, rsum)) self.deck.updateProgress() res = server.applyPayload(payload) if not self.includeSchedulingInfo: self.deck.updateProgress() self.newDeck.s.statement(""" delete from reviewHistory""") self.newDeck.s.statement(""" update cards set interval = 0, lastInterval = 0, due = created, lastDue = 0, factor = 2.5, firstAnswered = 0, reps = 0, successive = 0, averageTime = 0, reviewTime = 0, youngEase0 = 0, youngEase1 = 0, youngEase2 = 0, youngEase3 = 0, youngEase4 = 0, matureEase0 = 0, matureEase1 = 0, matureEase2 = 0, matureEase3 = 0, matureEase4 = 0, yesCount = 0, noCount = 0, spaceUntil = 0, type = 2, relativeDelay = 2, combinedDue = created, modified = :now """, now=time.time()) self.newDeck.s.statement(""" delete from stats""") # media if self.includeMedia: server.deck.mediaPrefix = "" copyLocalMedia(client.deck, server.deck) # need to save manually self.newDeck.rebuildCounts() self.newDeck.updateAllPriorities() self.exportedCards = self.newDeck.cardCount self.newDeck.utcOffset = -1 self.newDeck.s.commit() self.newDeck.close() self.deck.finishProgress() def localSummary(self): cardIds = self.cardIds() cStrIds = ids2str(cardIds) cards = self.deck.s.all(""" select id, modified from cards where id in %s""" % cStrIds) facts = self.deck.s.all(""" select facts.id, facts.modified from cards, facts where facts.id = cards.factId and cards.id in %s""" % cStrIds) models = self.deck.s.all(""" select models.id, models.modified from models, facts where facts.modelId = models.id and facts.id in %s""" % ids2str([f[0] for f in facts])) media = self.deck.s.all(""" select id, created from media""") return { # cards "cards": cards, "delcards": [], # facts "facts": facts, "delfacts": [], # models "models": models, "delmodels": [], # media "media": media, "delmedia": [], }
"hours": lambda n: ngettext("%s hour", "%s hours", n), "minutes": lambda n: ngettext("%s minute", "%s minutes", n), "seconds": lambda n: ngettext("%s second", "%s seconds", n), } afterTimeTable = { "years": lambda n: ngettext("%s year<!--after-->", "%s years<!--after-->", n), "months": lambda n: ngettext("%s month<!--after-->", "%s months<!--after-->", n), "days": lambda n: ngettext("%s day<!--after-->", "%s days<!--after-->", n), "hours": lambda n: ngettext("%s hour<!--after-->", "%s hours<!--after-->", n), "minutes": lambda n: ngettext("%s minute<!--after-->", "%s minutes<!--after-->", n), "seconds": lambda n: ngettext("%s second<!--after-->", "%s seconds<!--after-->", n), } shortTimeTable = { "years": _("%sy"), "months": _("%sm"), "days": _("%sd"), "hours": _("%sh"), "minutes": _("%sm"), "seconds": _("%ss"), } def fmtTimeSpan(time, pad=0, point=0, short=False, after=False): "Return a string representing a time span (eg '2 days')." (type, point) = optimalPeriod(time, point) time = convertSecondsTo(time, type) if not point: time = math.floor(time) if short: fmt = shortTimeTable[type]
def exportInto(self, path): n = 3 if not self.includeSchedulingInfo: n += 1 self.deck.startProgress(n) self.deck.updateProgress(_("Exporting...")) try: os.unlink(path) except (IOError, OSError): pass self.newDeck = DeckStorage.Deck(path) client = SyncClient(self.deck) server = SyncServer(self.newDeck) client.setServer(server) client.localTime = self.deck.modified client.remoteTime = 0 self.deck.s.flush() # set up a custom change list and sync lsum = self.localSummary() rsum = server.summary(0) self.deck.updateProgress() payload = client.genPayload((lsum, rsum)) self.deck.updateProgress() res = server.applyPayload(payload) if not self.includeSchedulingInfo: self.deck.updateProgress() self.newDeck.s.statement(""" delete from reviewHistory""") self.newDeck.s.statement(""" update cards set interval = 0, lastInterval = 0, due = created, lastDue = 0, factor = 2.5, firstAnswered = 0, reps = 0, successive = 0, averageTime = 0, reviewTime = 0, youngEase0 = 0, youngEase1 = 0, youngEase2 = 0, youngEase3 = 0, youngEase4 = 0, matureEase0 = 0, matureEase1 = 0, matureEase2 = 0, matureEase3 = 0, matureEase4 = 0, yesCount = 0, noCount = 0, spaceUntil = 0, type = 2, relativeDelay = 2, combinedDue = created, modified = :now """, now=time.time()) self.newDeck.s.statement(""" delete from stats""") # media if self.includeMedia: server.deck.mediaPrefix = "" copyLocalMedia(client.deck, server.deck) # need to save manually self.newDeck.rebuildCounts() self.newDeck.updateAllPriorities() self.exportedCards = self.newDeck.cardCount self.newDeck.utcOffset = -1 self.newDeck.s.commit() self.newDeck.close() self.deck.finishProgress()
def exporters(): return ((_("Anki Deck (*.oldanki)"), AnkiExporter), (_("Cards in tab-separated text file (*.txt)"), TextCardExporter), (_("Facts in tab-separated text file (*.txt)"), TextFactExporter))
def report(self): "Return an HTML string with a report." fmtPerc = oldanki.utils.fmtPercentage fmtFloat = oldanki.utils.fmtFloat if self.deck.isEmpty(): return _("Please add some cards first.") + "<p/>" d = self.deck html="<h1>" + _("Deck Statistics") + "</h1>" html += _("Deck created: <b>%s</b> ago<br>") % self.createdTimeStr() total = d.cardCount new = d.newCountAll() young = d.youngCardCount() old = d.matureCardCount() newP = new / float(total) * 100 youngP = young / float(total) * 100 oldP = old / float(total) * 100 stats = d.getStats() (stats["new"], stats["newP"]) = (new, newP) (stats["old"], stats["oldP"]) = (old, oldP) (stats["young"], stats["youngP"]) = (young, youngP) html += _("Total number of cards:") + " <b>%d</b><br>" % total html += _("Total number of facts:") + " <b>%d</b><br><br>" % d.factCount html += "<b>" + _("Card Maturity") + "</b><br>" html += _("Mature cards: <!--card count-->") + " <b>%(old)d</b> (%(oldP)s)<br>" % { 'old': stats['old'], 'oldP' : fmtPerc(stats['oldP'])} html += _("Young cards: <!--card count-->") + " <b>%(young)d</b> (%(youngP)s)<br>" % { 'young': stats['young'], 'youngP' : fmtPerc(stats['youngP'])} html += _("Unseen cards:") + " <b>%(new)d</b> (%(newP)s)<br>" % { 'new': stats['new'], 'newP' : fmtPerc(stats['newP'])} avgInt = self.getAverageInterval() if avgInt: html += _("Average interval: ") + ("<b>%s</b> ") % fmtFloat(avgInt) + _("days") html += "<br>" html += "<br>" html += "<b>" + _("Correct Answers") + "</b><br>" html += _("Mature cards: <!--correct answers-->") + " <b>" + fmtPerc(stats['gMatureYes%']) + ( "</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf' : stats['gMatureYes'], 'totalSum' : stats['gMatureTotal'] } + "<br>") html += _("Young cards: <!--correct answers-->") + " <b>" + fmtPerc(stats['gYoungYes%']) + ( "</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf' : stats['gYoungYes'], 'totalSum' : stats['gYoungTotal'] } + "<br>") html += _("First-seen cards:") + " <b>" + fmtPerc(stats['gNewYes%']) + ( "</b> " + _("(%(partOf)d of %(totalSum)d)") % { 'partOf' : stats['gNewYes'], 'totalSum' : stats['gNewTotal'] } + "<br><br>") # average pending time existing = d.cardCount - d.newCountToday def tr(a, b): return "<tr><td>%s</td><td align=right>%s</td></tr>" % (a, b) def repsPerDay(reps,days): retval = ("<b>%d</b> " % reps) + ngettext("rep", "reps", reps) retval += ("/<b>%d</b> " % days) + ngettext("day", "days", days) return retval if existing and avgInt: html += "<b>" + _("Recent Work") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr(_("In last week"), repsPerDay( self.getRepsDone(-7, 0), self.getDaysReviewed(-7, 0))) html += tr(_("In last month"), repsPerDay( self.getRepsDone(-30, 0), self.getDaysReviewed(-30, 0))) html += tr(_("In last 3 months"), repsPerDay( self.getRepsDone(-92, 0), self.getDaysReviewed(-92, 0))) html += tr(_("In last 6 months"), repsPerDay( self.getRepsDone(-182, 0), self.getDaysReviewed(-182, 0))) html += tr(_("In last year"), repsPerDay( self.getRepsDone(-365, 0), self.getDaysReviewed(-365, 0))) html += tr(_("Deck life"), repsPerDay( self.getRepsDone(-13000, 0), self.getDaysReviewed(-13000, 0))) html += "</table>" html += "<br><br><b>" + _("Average Daily Reviews") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr(_("Deck life"), ("<b>%s</b> ") % ( fmtFloat(self.getSumInverseRoundInterval())) + _("cards/day")) html += tr(_("In next week"), ("<b>%s</b> ") % ( fmtFloat(self.getWorkloadPeriod(7))) + _("cards/day")) html += tr(_("In next month"), ("<b>%s</b> ") % ( fmtFloat(self.getWorkloadPeriod(30))) + _("cards/day")) html += tr(_("In last week"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(7))) + _("cards/day")) html += tr(_("In last month"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(30))) + _("cards/day")) html += tr(_("In last 3 months"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(92))) + _("cards/day")) html += tr(_("In last 6 months"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(182))) + _("cards/day")) html += tr(_("In last year"), ("<b>%s</b> ") % ( fmtFloat(self.getPastWorkloadPeriod(365))) + _("cards/day")) html += "</table>" html += "<br><br><b>" + _("Average Added") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" html += tr(_("Deck life"), _("<b>%(a)s</b>/day, <b>%(b)s</b>/mon") % { 'a': fmtFloat(self.newAverage()), 'b': fmtFloat(self.newAverage()*30)}) np = self.getNewPeriod(7) html += tr(_("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(7))})) np = self.getNewPeriod(30) html += tr(_("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) np = self.getNewPeriod(92) html += tr(_("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(92))})) np = self.getNewPeriod(182) html += tr(_("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(182))})) np = self.getNewPeriod(365) html += tr(_("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(365))})) html += "</table>" html += "<br><br><b>" + _("Average New Seen") + "</b>" if sys.platform.startswith("darwin"): html += "<table width=250>" else: html += "<table width=200>" np = self.getFirstPeriod(7) html += tr(_("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(7))})) np = self.getFirstPeriod(30) html += tr(_("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(30))})) np = self.getFirstPeriod(92) html += tr(_("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(92))})) np = self.getFirstPeriod(182) html += tr(_("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(182))})) np = self.getFirstPeriod(365) html += tr(_("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % ( {'a': np, 'b': fmtFloat(np / float(365))})) html += "</table>" html += "<br><br><b>" + _("Card Ease") + "</b><br>" html += _("Lowest factor: %.2f") % d.s.scalar( "select min(factor) from cards") + "<br>" html += _("Average factor: %.2f") % d.s.scalar( "select avg(factor) from cards") + "<br>" html += _("Highest factor: %.2f") % d.s.scalar( "select max(factor) from cards") + "<br>" html = runFilter("deckStats", html) return html
def rebuildMediaDir(deck, delete=False, dirty=True): mdir = deck.mediaDir() if not mdir: return (0, 0) deck.startProgress(title=_("Check Media DB")) # set all ref counts to 0 deck.s.statement("update media set size = 0") # look through cards for media references refs = {} normrefs = {} def norm(s): if isinstance(s, unicode): return unicodedata.normalize('NFD', s) return s for (question, answer) in deck.s.all( "select question, answer from cards"): for txt in (question, answer): for f in mediaFiles(txt): if f in refs: refs[f] += 1 else: refs[f] = 1 normrefs[norm(f)] = True # update ref counts for (file, count) in refs.items(): updateMediaCount(deck, file, count) # find unused media unused = [] for file in os.listdir(mdir): path = os.path.join(mdir, file) if not os.path.isfile(path): # ignore directories continue nfile = norm(file) if nfile not in normrefs: unused.append(file) # optionally delete if delete: for f in unused: path = os.path.join(mdir, f) os.unlink(path) # remove entries in db for unused media removeUnusedMedia(deck) # check md5s are up to date update = [] for (file, created, md5) in deck.s.all( "select filename, created, originalPath from media"): path = os.path.join(mdir, file) if not os.path.exists(path): if md5: update.append({'f':file, 'sum':u"", 'c':time.time()}) else: sum = unicode( checksum(open(os.path.join(mdir, file), "rb").read())) if md5 != sum: update.append({'f':file, 'sum':sum, 'c':time.time()}) if update: deck.s.statements(""" update media set originalPath = :sum, created = :c where filename = :f""", update) # update deck and get return info if dirty: deck.flushMod() nohave = deck.s.column0("select filename from media where originalPath = ''") deck.finishProgress() return (nohave, unused)