mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
implement anki1 importer
This commit is contained in:
parent
b242b06052
commit
119217290e
7 changed files with 100 additions and 110 deletions
|
@ -2,74 +2,30 @@
|
||||||
# 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
|
||||||
|
|
||||||
from anki import Deck
|
import traceback
|
||||||
from anki.importing.base import Importer
|
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.utils import ids2str
|
from anki.upgrade import Upgrader
|
||||||
#from anki.deck import NEW_CARDS_RANDOM
|
from anki.importing.anki2 import Anki2Importer
|
||||||
import time
|
|
||||||
|
|
||||||
class Anki1Importer(Importer):
|
class Anki1Importer(Anki2Importer):
|
||||||
|
|
||||||
needMapper = False
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"Import."
|
u = Upgrader()
|
||||||
random = self.deck.newCardOrder == NEW_CARDS_RANDOM
|
# check
|
||||||
num = 4
|
if not u.check(self.file):
|
||||||
if random:
|
self.log.append(_(
|
||||||
num += 1
|
"File is damaged; please run Tools>Advanced>Check DB "
|
||||||
src = DeckStorage.Deck(self.file, backup=False)
|
"in Anki 1.2 first."))
|
||||||
client = SyncClient(self.deck)
|
return
|
||||||
server = SyncServer(src)
|
# upgrade
|
||||||
client.setServer(server)
|
try:
|
||||||
# if there is a conflict, sync local -> src
|
deck = u.upgrade(self.file)
|
||||||
client.localTime = self.deck.modified
|
except:
|
||||||
client.remoteTime = 0
|
self.log.append(traceback.format_exc())
|
||||||
src.s.execute("update facts set modified = 1")
|
return
|
||||||
src.s.execute("update models set modified = 1")
|
# merge
|
||||||
src.s.execute("update cards set modified = 1")
|
deck.close()
|
||||||
src.s.execute("update media set created = 1")
|
mdir = self.file.replace(".anki", ".media")
|
||||||
self.deck.db.flush()
|
self.file = deck.path
|
||||||
# set up a custom change list and sync
|
Anki2Importer.run(self, mdir)
|
||||||
lsum = client.summary(0)
|
|
||||||
self._clearDeleted(lsum)
|
|
||||||
rsum = server.summary(0)
|
|
||||||
self._clearDeleted(rsum)
|
|
||||||
payload = client.genPayload((lsum, rsum))
|
|
||||||
# no need to add anything to src
|
|
||||||
payload['added-models'] = []
|
|
||||||
payload['added-cards'] = []
|
|
||||||
payload['added-facts'] = {'facts': [], 'fields': []}
|
|
||||||
assert payload['deleted-facts'] == []
|
|
||||||
assert payload['deleted-cards'] == []
|
|
||||||
assert payload['deleted-models'] == []
|
|
||||||
res = server.applyPayload(payload)
|
|
||||||
client.applyPayloadReply(res)
|
|
||||||
copyLocalMedia(server.deck, client.deck)
|
|
||||||
# add tags
|
|
||||||
fids = [f[0] for f in res['added-facts']['facts']]
|
|
||||||
self.deck.tags.add(fids, self.tagsToAdd)
|
|
||||||
# mark import material as newly added
|
|
||||||
self.deck.db.execute(
|
|
||||||
"update cards set modified = :t where id in %s" %
|
|
||||||
ids2str([x[0] for x in res['added-cards']]), t=time.time())
|
|
||||||
self.deck.db.execute(
|
|
||||||
"update facts set modified = :t where id in %s" %
|
|
||||||
ids2str([x[0] for x in res['added-facts']['facts']]), t=time.time())
|
|
||||||
self.deck.db.execute(
|
|
||||||
"update models set modified = :t where id in %s" %
|
|
||||||
ids2str([x['id'] for x in res['added-models']]), t=time.time())
|
|
||||||
# update total and refresh
|
|
||||||
self.total = len(res['added-facts']['facts'])
|
|
||||||
src.s.rollback()
|
|
||||||
src.engine.dispose()
|
|
||||||
# randomize?
|
|
||||||
if random:
|
|
||||||
self.deck.randomizeNewCards([x[0] for x in res['added-cards']])
|
|
||||||
self.deck.flushMod()
|
|
||||||
|
|
||||||
def _clearDeleted(self, sum):
|
|
||||||
sum['delcards'] = []
|
|
||||||
sum['delfacts'] = []
|
|
||||||
sum['delmodels'] = []
|
|
||||||
|
|
|
@ -16,18 +16,18 @@ from anki.importing.base import Importer
|
||||||
# - compare cards by fact guid + ordinal
|
# - compare cards by fact guid + ordinal
|
||||||
# - compare groups by name
|
# - compare groups by name
|
||||||
#
|
#
|
||||||
#
|
|
||||||
# When importing facts
|
|
||||||
|
|
||||||
class Anki2Importer(Importer):
|
class Anki2Importer(Importer):
|
||||||
|
|
||||||
needMapper = False
|
needMapper = False
|
||||||
groupPrefix = None
|
groupPrefix = None
|
||||||
|
|
||||||
def run(self):
|
def run(self, media=None):
|
||||||
"Import."
|
|
||||||
self.dst = self.deck
|
self.dst = self.deck
|
||||||
self.src = Deck(self.file, queue=False)
|
self.src = Deck(self.file, queue=False)
|
||||||
|
if media is not None:
|
||||||
|
# Anki1 importer has provided us with a custom media folder
|
||||||
|
self.src.media._dir = media
|
||||||
try:
|
try:
|
||||||
self._import()
|
self._import()
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -163,6 +163,8 @@ If the same name exists, compare checksums."""
|
||||||
|
|
||||||
# Copying on import
|
# Copying on import
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
# FIXME: check if the files are actually identical, and rewrite references
|
||||||
|
# if necessary
|
||||||
|
|
||||||
def copyTo(self, rdir):
|
def copyTo(self, rdir):
|
||||||
ldir = self.dir()
|
ldir = self.dir()
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
import os, time, simplejson, re, datetime, shutil
|
import os, time, simplejson, re, datetime, shutil
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.utils import intTime, namedtmp
|
from anki.utils import intTime, tmpfile
|
||||||
from anki.db import DB
|
from anki.db import DB
|
||||||
from anki.deck import _Deck
|
from anki.deck import _Deck
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
|
@ -108,7 +108,7 @@ class Upgrader(object):
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def _openDB(self, path):
|
def _openDB(self, path):
|
||||||
self.tmppath = namedtmp(os.path.basename(path))
|
(fd, self.tmppath) = tmpfile(suffix=".anki2")
|
||||||
shutil.copy(path, self.tmppath)
|
shutil.copy(path, self.tmppath)
|
||||||
self.db = DB(self.tmppath)
|
self.db = DB(self.tmppath)
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ def tmpdir():
|
||||||
os.mkdir(_tmpdir)
|
os.mkdir(_tmpdir)
|
||||||
return _tmpdir
|
return _tmpdir
|
||||||
|
|
||||||
def tmpfile(prefix=None, suffix=None):
|
def tmpfile(prefix="", suffix=""):
|
||||||
return tempfile.mkstemp(dir=tmpdir(), prefix=prefix, suffix=suffix)
|
return tempfile.mkstemp(dir=tmpdir(), prefix=prefix, suffix=suffix)
|
||||||
|
|
||||||
def namedtmp(name):
|
def namedtmp(name):
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
import nose, os, shutil
|
import nose, os, shutil
|
||||||
from tests.shared import assertException
|
from tests.shared import assertException, getUpgradeDeckPath, getEmptyDeck
|
||||||
|
from anki.upgrade import Upgrader
|
||||||
|
from anki.utils import ids2str
|
||||||
from anki.errors import *
|
from anki.errors import *
|
||||||
from anki import Deck
|
from anki import Deck
|
||||||
from anki.importing import Anki1Importer, Anki2Importer, TextImporter, \
|
from anki.importing import Anki1Importer, Anki2Importer, TextImporter, \
|
||||||
|
@ -13,6 +14,68 @@ from anki.db import *
|
||||||
|
|
||||||
testDir = os.path.dirname(__file__)
|
testDir = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
srcFacts=None
|
||||||
|
srcCards=None
|
||||||
|
|
||||||
|
def test_anki2():
|
||||||
|
global srcFacts, srcCards
|
||||||
|
# get the deck to import
|
||||||
|
tmp = getUpgradeDeckPath()
|
||||||
|
u = Upgrader()
|
||||||
|
src = u.upgrade(tmp)
|
||||||
|
srcpath = src.path
|
||||||
|
srcFacts = src.factCount()
|
||||||
|
srcCards = src.cardCount()
|
||||||
|
srcRev = src.db.scalar("select count() from revlog")
|
||||||
|
# add a media file for testing
|
||||||
|
open(os.path.join(src.media.dir(), "foo.jpg"), "w").write("foo")
|
||||||
|
src.close()
|
||||||
|
# create a new empty deck
|
||||||
|
dst = getEmptyDeck()
|
||||||
|
# import src into dst
|
||||||
|
imp = Anki2Importer(dst, srcpath)
|
||||||
|
imp.run()
|
||||||
|
def check():
|
||||||
|
assert dst.factCount() == srcFacts
|
||||||
|
assert dst.cardCount() == srcCards
|
||||||
|
assert srcRev == dst.db.scalar("select count() from revlog")
|
||||||
|
mids = [int(x) for x in dst.models.models.keys()]
|
||||||
|
assert not dst.db.scalar(
|
||||||
|
"select count() from facts where mid not in "+ids2str(mids))
|
||||||
|
assert not dst.db.scalar(
|
||||||
|
"select count() from cards where fid not in (select id from facts)")
|
||||||
|
assert not dst.db.scalar(
|
||||||
|
"select count() from revlog where cid not in (select id from cards)")
|
||||||
|
check()
|
||||||
|
# importing should be idempotent
|
||||||
|
imp.run()
|
||||||
|
check()
|
||||||
|
assert len(os.listdir(dst.media.dir())) == 1
|
||||||
|
print dst.path
|
||||||
|
|
||||||
|
def test_anki1():
|
||||||
|
# get the deck path to import
|
||||||
|
tmp = getUpgradeDeckPath()
|
||||||
|
# make sure media is imported properly through the upgrade
|
||||||
|
mdir = tmp.replace(".anki", ".media")
|
||||||
|
if not os.path.exists(mdir):
|
||||||
|
os.mkdir(mdir)
|
||||||
|
open(os.path.join(mdir, "foo.jpg"), "w").write("foo")
|
||||||
|
# create a new empty deck
|
||||||
|
dst = getEmptyDeck()
|
||||||
|
# import src into dst
|
||||||
|
imp = Anki1Importer(dst, tmp)
|
||||||
|
imp.run()
|
||||||
|
def check():
|
||||||
|
assert dst.factCount() == srcFacts
|
||||||
|
assert dst.cardCount() == srcCards
|
||||||
|
assert len(os.listdir(dst.media.dir())) == 1
|
||||||
|
check()
|
||||||
|
# importing should be idempotent
|
||||||
|
imp = Anki1Importer(dst, tmp)
|
||||||
|
imp.run()
|
||||||
|
check()
|
||||||
|
|
||||||
def test_csv():
|
def test_csv():
|
||||||
print "disabled"; return
|
print "disabled"; return
|
||||||
deck = Deck()
|
deck = Deck()
|
||||||
|
|
|
@ -5,7 +5,7 @@ from anki.consts import *
|
||||||
from shared import getUpgradeDeckPath, getEmptyDeck
|
from shared import getUpgradeDeckPath, getEmptyDeck
|
||||||
from anki.upgrade import Upgrader
|
from anki.upgrade import Upgrader
|
||||||
from anki.importing import Anki2Importer
|
from anki.importing import Anki2Importer
|
||||||
from anki.utils import ids2str
|
from anki.utils import ids2str, checksum
|
||||||
|
|
||||||
def test_check():
|
def test_check():
|
||||||
dst = getUpgradeDeckPath()
|
dst = getUpgradeDeckPath()
|
||||||
|
@ -17,9 +17,12 @@ def test_check():
|
||||||
|
|
||||||
def test_upgrade():
|
def test_upgrade():
|
||||||
dst = getUpgradeDeckPath()
|
dst = getUpgradeDeckPath()
|
||||||
|
csum = checksum(open(dst).read())
|
||||||
u = Upgrader()
|
u = Upgrader()
|
||||||
print "upgrade to", dst
|
print "upgrade to", dst
|
||||||
deck = u.upgrade(dst)
|
deck = u.upgrade(dst)
|
||||||
|
# src file must not have changed
|
||||||
|
assert csum == checksum(open(dst).read())
|
||||||
# creation time should have been adjusted
|
# creation time should have been adjusted
|
||||||
d = datetime.datetime.fromtimestamp(deck.crt)
|
d = datetime.datetime.fromtimestamp(deck.crt)
|
||||||
assert d.hour == 4 and d.minute == 0
|
assert d.hour == 4 and d.minute == 0
|
||||||
|
@ -29,37 +32,3 @@ def test_upgrade():
|
||||||
assert deck.sched.cardCounts() == (3,2,1)
|
assert deck.sched.cardCounts() == (3,2,1)
|
||||||
# now's a good time to test the integrity check too
|
# now's a good time to test the integrity check too
|
||||||
deck.fixIntegrity()
|
deck.fixIntegrity()
|
||||||
|
|
||||||
def test_import():
|
|
||||||
# get the deck to import
|
|
||||||
tmp = getUpgradeDeckPath()
|
|
||||||
u = Upgrader()
|
|
||||||
src = u.upgrade(tmp)
|
|
||||||
srcpath = src.path
|
|
||||||
srcFacts = src.factCount()
|
|
||||||
srcCards = src.cardCount()
|
|
||||||
srcRev = src.db.scalar("select count() from revlog")
|
|
||||||
# add a media file for testing
|
|
||||||
open(os.path.join(src.media.dir(), "foo.jpg"), "w").write("foo")
|
|
||||||
src.close()
|
|
||||||
# create a new empty deck
|
|
||||||
dst = getEmptyDeck()
|
|
||||||
# import src into dst
|
|
||||||
imp = Anki2Importer(dst, srcpath)
|
|
||||||
imp.run()
|
|
||||||
def check():
|
|
||||||
assert dst.factCount() == srcFacts
|
|
||||||
assert dst.cardCount() == srcCards
|
|
||||||
assert srcRev == dst.db.scalar("select count() from revlog")
|
|
||||||
mids = [int(x) for x in dst.models.models.keys()]
|
|
||||||
assert not dst.db.scalar(
|
|
||||||
"select count() from facts where mid not in "+ids2str(mids))
|
|
||||||
assert not dst.db.scalar(
|
|
||||||
"select count() from cards where fid not in (select id from facts)")
|
|
||||||
assert not dst.db.scalar(
|
|
||||||
"select count() from revlog where cid not in (select id from cards)")
|
|
||||||
check()
|
|
||||||
# importing should be idempotent
|
|
||||||
imp.run()
|
|
||||||
check()
|
|
||||||
print dst.path
|
|
||||||
|
|
Loading…
Reference in a new issue