diff --git a/anki/importing/anki2.py b/anki/importing/anki2.py index f4d3bfd47..758e8fe51 100644 --- a/anki/importing/anki2.py +++ b/anki/importing/anki2.py @@ -9,8 +9,9 @@ from anki.importing.base import Importer from anki.lang import _ from anki.lang import ngettext -MID = 2 GUID = 1 +MID = 2 +MOD = 3 class Anki2Importer(Importer): @@ -63,6 +64,7 @@ class Anki2Importer(Importer): self._changedGuids = {} # iterate over source collection add = [] + update = [] dirty = [] usn = self.dst.usn() dupes = 0 @@ -85,22 +87,35 @@ class Anki2Importer(Importer): # note we have the added the guid self._notes[note[GUID]] = (note[0], note[3], note[MID]) else: + # a duplicate or changed schema - safe to update? dupes += 1 - ## update existing note - not yet tested; for post 2.0 - # newer = note[3] > mod - # if self.allowUpdate and self._mid(mid) == mid and newer: - # localNid = self._notes[guid][0] - # note[0] = localNid - # note[4] = usn - # add.append(note) - # dirty.append(note[0]) + if self.allowUpdate and note[GUID] in self._notes: + oldNid, oldMod, oldMid = self._notes[note[GUID]] + # safe if note types identical + if oldMid == note[MID]: + # will update if incoming note more recent + if oldMod < note[MOD]: + # incoming note should use existing id + note[0] = oldNid + note[4] = usn + note[6] = self._mungeMedia(note[MID], note[6]) + update.append(note) + dirty.append(note[0]) if dupes: - self.log.append(_("Already in collection: %s.") % (ngettext( - "%d note", "%d notes", dupes) % dupes)) + up = len(update) + self.log.append(_("Updated %(a)d of %(b)d existing notes.") % dict( + a=len(update), b=dupes)) + # export info for calling code + self.dupes = dupes + self.added = len(add) + self.updated = len(update) # add to col self.dst.db.executemany( "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", add) + self.dst.db.executemany( + "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", + update) self.dst.updateFieldCache(dirty) self.dst.tags.registerNotes(dirty) diff --git a/tests/support/update1.apkg b/tests/support/update1.apkg new file mode 100644 index 000000000..5a0643b67 Binary files /dev/null and b/tests/support/update1.apkg differ diff --git a/tests/support/update2.apkg b/tests/support/update2.apkg new file mode 100644 index 000000000..5310095ce Binary files /dev/null and b/tests/support/update2.apkg differ diff --git a/tests/test_importing.py b/tests/test_importing.py index 075ad86d9..940d3642f 100644 --- a/tests/test_importing.py +++ b/tests/test_importing.py @@ -163,7 +163,6 @@ def test_anki1_diffmodels(): assert after == before + 1 assert beforeModels == len(dst.models.all()) - def test_suspended(): # create a new empty deck dst = getEmptyDeck() @@ -205,6 +204,33 @@ def test_anki2_diffmodels(): assert after == before + 1 assert dst.cardCount() == 3 +def test_anki2_updates(): + # create a new empty deck + dst = getEmptyDeck() + tmp = getUpgradeDeckPath("update1.apkg") + imp = AnkiPackageImporter(dst, tmp) + imp.run() + assert imp.dupes == 0 + assert imp.added == 1 + assert imp.updated == 0 + # importing again should be idempotent + imp = AnkiPackageImporter(dst, tmp) + imp.run() + assert imp.dupes == 1 + assert imp.added == 0 + assert imp.updated == 0 + # importing a newer note should update + assert dst.noteCount() == 1 + assert dst.db.scalar("select flds from notes").startswith("hello") + tmp = getUpgradeDeckPath("update2.apkg") + imp = AnkiPackageImporter(dst, tmp) + imp.run() + assert imp.dupes == 1 + assert imp.added == 0 + assert imp.updated == 1 + assert dst.noteCount() == 1 + assert dst.db.scalar("select flds from notes").startswith("goodbye") + def test_csv(): deck = getEmptyDeck() file = unicode(os.path.join(testDir, "support/text-2fields.txt"))