mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Merge branch 'master' of github.com:dae/libanki into some-fixes
This commit is contained in:
commit
0443d68eea
12 changed files with 82 additions and 34 deletions
|
@ -642,6 +642,9 @@ select id from notes where mid not in """ + ids2str(self.models.ids()))
|
||||||
self.remNotes(ids)
|
self.remNotes(ids)
|
||||||
# cards with invalid ordinal
|
# cards with invalid ordinal
|
||||||
for m in self.models.all():
|
for m in self.models.all():
|
||||||
|
# ignore clozes
|
||||||
|
if m['type'] != MODEL_STD:
|
||||||
|
continue
|
||||||
ids = self.db.list("""
|
ids = self.db.list("""
|
||||||
select id from cards where ord not in %s and nid in (
|
select id from cards where ord not in %s and nid in (
|
||||||
select id from notes where mid = ?)""" %
|
select id from notes where mid = ?)""" %
|
||||||
|
|
|
@ -4,11 +4,10 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from anki import Collection
|
from anki import Collection
|
||||||
from anki.utils import intTime, splitFields, joinFields, checksum
|
from anki.utils import intTime, splitFields, joinFields, checksum, guid64
|
||||||
from anki.importing.base import Importer
|
from anki.importing.base import Importer
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.lang import ngettext
|
from anki.lang import ngettext
|
||||||
from anki.hooks import runFilter
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Import a .anki2 file into the current collection. Used for migration from
|
# Import a .anki2 file into the current collection. Used for migration from
|
||||||
|
@ -43,7 +42,6 @@ class Anki2Importer(Importer):
|
||||||
|
|
||||||
def _import(self):
|
def _import(self):
|
||||||
self._decks = {}
|
self._decks = {}
|
||||||
self._prepareDeckPrefix()
|
|
||||||
if self.deckPrefix:
|
if self.deckPrefix:
|
||||||
id = self.dst.decks.id(self.deckPrefix)
|
id = self.dst.decks.id(self.deckPrefix)
|
||||||
self.dst.decks.select(id)
|
self.dst.decks.select(id)
|
||||||
|
@ -56,24 +54,6 @@ class Anki2Importer(Importer):
|
||||||
self.dst.db.execute("vacuum")
|
self.dst.db.execute("vacuum")
|
||||||
self.dst.db.execute("analyze")
|
self.dst.db.execute("analyze")
|
||||||
|
|
||||||
def _prepareDeckPrefix(self):
|
|
||||||
if self.deckPrefix:
|
|
||||||
return runFilter("prepareImportPrefix", self.deckPrefix)
|
|
||||||
prefix = None
|
|
||||||
for deck in self.src.decks.all():
|
|
||||||
if str(deck['id']) == "1":
|
|
||||||
# we can ignore the default deck if it's empty
|
|
||||||
if not self.src.db.scalar(
|
|
||||||
"select 1 from cards where did = ? limit 1", deck['id']):
|
|
||||||
continue
|
|
||||||
head = deck['name'].split("::")[0]
|
|
||||||
if not prefix:
|
|
||||||
prefix = head
|
|
||||||
else:
|
|
||||||
if prefix != head:
|
|
||||||
return
|
|
||||||
self.deckPrefix = runFilter("prepareImportPrefix", prefix)
|
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
######################################################################
|
######################################################################
|
||||||
# - should note new for wizard
|
# - should note new for wizard
|
||||||
|
@ -86,6 +66,9 @@ class Anki2Importer(Importer):
|
||||||
"select id, guid, mod, mid from notes"):
|
"select id, guid, mod, mid from notes"):
|
||||||
self._notes[guid] = (id, mod, mid)
|
self._notes[guid] = (id, mod, mid)
|
||||||
existing[id] = True
|
existing[id] = True
|
||||||
|
# we may need to rewrite the guid if the model schemas don't match,
|
||||||
|
# so we need to keep track of the changes for the card import stage
|
||||||
|
self._changedGuids = {}
|
||||||
# iterate over source collection
|
# iterate over source collection
|
||||||
add = []
|
add = []
|
||||||
dirty = []
|
dirty = []
|
||||||
|
@ -96,8 +79,22 @@ class Anki2Importer(Importer):
|
||||||
# turn the db result into a mutable list
|
# turn the db result into a mutable list
|
||||||
note = list(note)
|
note = list(note)
|
||||||
guid, mid = note[1:3]
|
guid, mid = note[1:3]
|
||||||
# missing from local col?
|
canUseExisting = False
|
||||||
if guid not in self._notes:
|
alreadyHaveGuid = False
|
||||||
|
# do we have the same guid?
|
||||||
|
if guid in self._notes:
|
||||||
|
alreadyHaveGuid = True
|
||||||
|
# and do they share the same model id?
|
||||||
|
if self._notes[guid][2] == mid:
|
||||||
|
# and do they share the same schema?
|
||||||
|
srcM = self.src.models.get(mid)
|
||||||
|
dstM = self.dst.models.get(self._notes[guid][2])
|
||||||
|
if (self.src.models.scmhash(srcM) ==
|
||||||
|
self.src.models.scmhash(dstM)):
|
||||||
|
# then it's safe to treat as an exact duplicate
|
||||||
|
canUseExisting = True
|
||||||
|
# if we can't reuse an existing one, we'll need to add new
|
||||||
|
if not canUseExisting:
|
||||||
# get corresponding local model
|
# get corresponding local model
|
||||||
lmid = self._mid(mid)
|
lmid = self._mid(mid)
|
||||||
# ensure id is unique
|
# ensure id is unique
|
||||||
|
@ -111,11 +108,16 @@ class Anki2Importer(Importer):
|
||||||
note[6] = self._mungeMedia(mid, note[6])
|
note[6] = self._mungeMedia(mid, note[6])
|
||||||
add.append(note)
|
add.append(note)
|
||||||
dirty.append(note[0])
|
dirty.append(note[0])
|
||||||
|
# if it was originally the same as a note in this deck but the
|
||||||
|
# models have diverged, we need to change the guid
|
||||||
|
if alreadyHaveGuid:
|
||||||
|
guid = guid64()
|
||||||
|
self._changedGuids[note[1]] = guid
|
||||||
|
note[1] = guid
|
||||||
# note we have the added note
|
# note we have the added note
|
||||||
self._notes[guid] = (note[0], note[3], note[2])
|
self._notes[guid] = (note[0], note[3], note[2])
|
||||||
else:
|
else:
|
||||||
dupes += 1
|
dupes += 1
|
||||||
pass
|
|
||||||
## update existing note - not yet tested; for post 2.0
|
## update existing note - not yet tested; for post 2.0
|
||||||
# newer = note[3] > mod
|
# newer = note[3] > mod
|
||||||
# if self.allowUpdate and self._mid(mid) == mid and newer:
|
# if self.allowUpdate and self._mid(mid) == mid and newer:
|
||||||
|
@ -245,6 +247,8 @@ class Anki2Importer(Importer):
|
||||||
"select f.guid, f.mid, c.* from cards c, notes f "
|
"select f.guid, f.mid, c.* from cards c, notes f "
|
||||||
"where c.nid = f.id"):
|
"where c.nid = f.id"):
|
||||||
guid = card[0]
|
guid = card[0]
|
||||||
|
if guid in self._changedGuids:
|
||||||
|
guid = self._changedGuids[guid]
|
||||||
# does the card's note exist in dst col?
|
# does the card's note exist in dst col?
|
||||||
if guid not in self._notes:
|
if guid not in self._notes:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -64,7 +64,7 @@ def _latexFromHtml(col, latex):
|
||||||
# entitydefs defines nbsp as \xa0 instead of a standard space, so we
|
# entitydefs defines nbsp as \xa0 instead of a standard space, so we
|
||||||
# replace it first
|
# replace it first
|
||||||
latex = latex.replace(" ", " ")
|
latex = latex.replace(" ", " ")
|
||||||
latex = re.sub("<br( /)?>|</div>", "\n", latex)
|
latex = re.sub("<br( /)?>|<div>", "\n", latex)
|
||||||
# replace <div> etc with spaces
|
# replace <div> etc with spaces
|
||||||
latex = re.sub("<.+?>", " ", latex)
|
latex = re.sub("<.+?>", " ", latex)
|
||||||
latex = stripHTML(latex)
|
latex = stripHTML(latex)
|
||||||
|
|
|
@ -452,6 +452,8 @@ select id from notes where mid = ?)""" % " ".join(map),
|
||||||
s = ""
|
s = ""
|
||||||
for f in m['flds']:
|
for f in m['flds']:
|
||||||
s += f['name']
|
s += f['name']
|
||||||
|
for t in m['tmpls']:
|
||||||
|
s += t['name']
|
||||||
return fieldChecksum(s)
|
return fieldChecksum(s)
|
||||||
|
|
||||||
# Required field/text cache
|
# Required field/text cache
|
||||||
|
|
|
@ -119,7 +119,7 @@ order by due""" % self._deckLimit(),
|
||||||
if card.odid and card.queue == 2:
|
if card.odid and card.queue == 2:
|
||||||
return 4
|
return 4
|
||||||
conf = self._lapseConf(card)
|
conf = self._lapseConf(card)
|
||||||
if len(conf['delays']) > 1:
|
if card.type == 0 or len(conf['delays']) > 1:
|
||||||
return 3
|
return 3
|
||||||
return 2
|
return 2
|
||||||
elif card.queue == 2:
|
elif card.queue == 2:
|
||||||
|
|
|
@ -139,7 +139,7 @@ body {background-image: url(data:image/png;base64,%s); }
|
||||||
lim = " and " + lim
|
lim = " and " + lim
|
||||||
cards, thetime, failed, lrn, rev, relrn, filt = self.col.db.first("""
|
cards, thetime, failed, lrn, rev, relrn, filt = self.col.db.first("""
|
||||||
select count(), sum(time)/1000,
|
select count(), sum(time)/1000,
|
||||||
sum(case when ease = 0 then 1 else 0 end), /* failed */
|
sum(case when ease = 1 then 1 else 0 end), /* failed */
|
||||||
sum(case when type = 0 then 1 else 0 end), /* learning */
|
sum(case when type = 0 then 1 else 0 end), /* learning */
|
||||||
sum(case when type = 1 then 1 else 0 end), /* review */
|
sum(case when type = 1 then 1 else 0 end), /* review */
|
||||||
sum(case when type = 2 then 1 else 0 end), /* relearn */
|
sum(case when type = 2 then 1 else 0 end), /* relearn */
|
||||||
|
@ -154,7 +154,7 @@ from revlog where id > ? """+lim, (self.col.sched.dayCutoff-86400)*1000)
|
||||||
filt = filt or 0
|
filt = filt or 0
|
||||||
# studied
|
# studied
|
||||||
def bold(s):
|
def bold(s):
|
||||||
return "<b>"+str(s)+"</b>"
|
return "<b>"+unicode(s)+"</b>"
|
||||||
msgp1 = ngettext("%d card", "%d cards", cards) % cards
|
msgp1 = ngettext("%d card", "%d cards", cards) % cards
|
||||||
b += _("Studied %(a)s in %(b)s today.") % dict(
|
b += _("Studied %(a)s in %(b)s today.") % dict(
|
||||||
a=bold(msgp1), b=bold(fmtTimeSpan(thetime, unit=1)))
|
a=bold(msgp1), b=bold(fmtTimeSpan(thetime, unit=1)))
|
||||||
|
@ -828,7 +828,7 @@ $(function () {
|
||||||
def _avgDay(self, tot, num, unit):
|
def _avgDay(self, tot, num, unit):
|
||||||
vals = []
|
vals = []
|
||||||
try:
|
try:
|
||||||
vals.append(_("%(a)d %(b)s/day") % dict(a=tot/float(num), b=unit))
|
vals.append(_("%(a)0.1f %(b)s/day") % dict(a=tot/float(num), b=unit))
|
||||||
return ", ".join(vals)
|
return ", ".join(vals)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -341,11 +341,8 @@ insert or replace into col select id, cast(created as int), :t,
|
||||||
mods = {}
|
mods = {}
|
||||||
for row in db.all(
|
for row in db.all(
|
||||||
"select id, name from models"):
|
"select id, name from models"):
|
||||||
while 1:
|
# use only first 31 bits
|
||||||
t = intTime(1000)
|
t = abs(row[0]) >> 32
|
||||||
if t not in times:
|
|
||||||
times[t] = True
|
|
||||||
break
|
|
||||||
m = anki.models.defaultModel.copy()
|
m = anki.models.defaultModel.copy()
|
||||||
m['id'] = t
|
m['id'] = t
|
||||||
m['name'] = row[1]
|
m['name'] = row[1]
|
||||||
|
|
BIN
tests/support/diffmodels1.anki
Normal file
BIN
tests/support/diffmodels1.anki
Normal file
Binary file not shown.
BIN
tests/support/diffmodels2-1.apkg
Normal file
BIN
tests/support/diffmodels2-1.apkg
Normal file
Binary file not shown.
BIN
tests/support/diffmodels2-2.apkg
Normal file
BIN
tests/support/diffmodels2-2.apkg
Normal file
Binary file not shown.
BIN
tests/support/diffmodels2.anki
Normal file
BIN
tests/support/diffmodels2.anki
Normal file
Binary file not shown.
|
@ -138,6 +138,48 @@ def test_anki1():
|
||||||
imp.run()
|
imp.run()
|
||||||
check()
|
check()
|
||||||
|
|
||||||
|
def test_anki1_diffmodels():
|
||||||
|
# create a new empty deck
|
||||||
|
dst = getEmptyDeck()
|
||||||
|
# import the 1 card version of the model
|
||||||
|
tmp = getUpgradeDeckPath("diffmodels1.anki")
|
||||||
|
imp = Anki1Importer(dst, tmp)
|
||||||
|
imp.run()
|
||||||
|
before = dst.noteCount()
|
||||||
|
# repeating the process should do nothing
|
||||||
|
imp = Anki1Importer(dst, tmp)
|
||||||
|
imp.run()
|
||||||
|
assert before == dst.noteCount()
|
||||||
|
# then the 2 card version
|
||||||
|
tmp = getUpgradeDeckPath("diffmodels2.anki")
|
||||||
|
imp = Anki1Importer(dst, tmp)
|
||||||
|
imp.run()
|
||||||
|
after = dst.noteCount()
|
||||||
|
# as the model schemas differ, should have been imported as new model
|
||||||
|
assert after == before + 1
|
||||||
|
|
||||||
|
def test_anki2_diffmodels():
|
||||||
|
# create a new empty deck
|
||||||
|
dst = getEmptyDeck()
|
||||||
|
# import the 1 card version of the model
|
||||||
|
tmp = getUpgradeDeckPath("diffmodels2-1.apkg")
|
||||||
|
imp = AnkiPackageImporter(dst, tmp)
|
||||||
|
imp.run()
|
||||||
|
before = dst.noteCount()
|
||||||
|
# repeating the process should do nothing
|
||||||
|
imp = AnkiPackageImporter(dst, tmp)
|
||||||
|
imp.run()
|
||||||
|
assert before == dst.noteCount()
|
||||||
|
# then the 2 card version
|
||||||
|
tmp = getUpgradeDeckPath("diffmodels2-2.apkg")
|
||||||
|
imp = AnkiPackageImporter(dst, tmp)
|
||||||
|
imp.run()
|
||||||
|
after = dst.noteCount()
|
||||||
|
# as the model schemas differ, should have been imported as new model
|
||||||
|
assert after == before + 1
|
||||||
|
# and the new model should have both cards
|
||||||
|
assert dst.cardCount() == 3
|
||||||
|
|
||||||
def test_csv():
|
def test_csv():
|
||||||
deck = getEmptyDeck()
|
deck = getEmptyDeck()
|
||||||
file = unicode(os.path.join(testDir, "support/text-2fields.txt"))
|
file = unicode(os.path.join(testDir, "support/text-2fields.txt"))
|
||||||
|
|
Loading…
Reference in a new issue