2013-10-20 03:57:42 +02:00
|
|
|
# coding=utf-8
|
2012-12-21 08:51:59 +01:00
|
|
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
2013-10-20 03:57:42 +02:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import traceback
|
|
|
|
import zipfile
|
|
|
|
import json
|
2017-09-30 11:29:21 +02:00
|
|
|
import unicodedata
|
2018-02-05 06:30:57 +01:00
|
|
|
import shutil
|
2013-10-20 03:57:42 +02:00
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
from aqt.qt import *
|
|
|
|
import anki.importing as importing
|
2018-09-27 03:35:21 +02:00
|
|
|
from aqt.utils import getOnlyText, getFile, showText, showWarning, openHelp, \
|
|
|
|
askUser, tooltip, showInfo
|
2012-12-21 08:51:59 +01:00
|
|
|
from anki.hooks import addHook, remHook
|
2013-10-20 03:57:42 +02:00
|
|
|
import aqt.forms
|
|
|
|
import aqt.modelchooser
|
|
|
|
import aqt.deckchooser
|
2016-02-26 01:01:46 +01:00
|
|
|
from anki.lang import ngettext
|
|
|
|
|
2013-10-20 03:57:42 +02:00
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
class ChangeMap(QDialog):
|
|
|
|
def __init__(self, mw, model, current):
|
|
|
|
QDialog.__init__(self, mw, Qt.Window)
|
|
|
|
self.mw = mw
|
|
|
|
self.model = model
|
|
|
|
self.frm = aqt.forms.changemap.Ui_ChangeMap()
|
|
|
|
self.frm.setupUi(self)
|
|
|
|
n = 0
|
|
|
|
setCurrent = False
|
|
|
|
for field in self.model['flds']:
|
|
|
|
item = QListWidgetItem(_("Map to %s") % field['name'])
|
|
|
|
self.frm.fields.addItem(item)
|
|
|
|
if current == field['name']:
|
|
|
|
setCurrent = True
|
|
|
|
self.frm.fields.setCurrentRow(n)
|
|
|
|
n += 1
|
|
|
|
self.frm.fields.addItem(QListWidgetItem(_("Map to Tags")))
|
2014-08-11 17:31:48 +02:00
|
|
|
self.frm.fields.addItem(QListWidgetItem(_("Ignore field")))
|
2012-12-21 08:51:59 +01:00
|
|
|
if not setCurrent:
|
|
|
|
if current == "_tags":
|
|
|
|
self.frm.fields.setCurrentRow(n)
|
|
|
|
else:
|
|
|
|
self.frm.fields.setCurrentRow(n+1)
|
|
|
|
self.field = None
|
|
|
|
|
|
|
|
def getField(self):
|
|
|
|
self.exec_()
|
|
|
|
return self.field
|
|
|
|
|
|
|
|
def accept(self):
|
|
|
|
row = self.frm.fields.currentRow()
|
|
|
|
if row < len(self.model['flds']):
|
|
|
|
self.field = self.model['flds'][row]['name']
|
|
|
|
elif row == self.frm.fields.count() - 2:
|
|
|
|
self.field = "_tags"
|
|
|
|
else:
|
|
|
|
self.field = None
|
|
|
|
QDialog.accept(self)
|
|
|
|
|
|
|
|
def reject(self):
|
|
|
|
self.accept()
|
|
|
|
|
|
|
|
class ImportDialog(QDialog):
|
|
|
|
|
|
|
|
def __init__(self, mw, importer):
|
|
|
|
QDialog.__init__(self, mw, Qt.Window)
|
|
|
|
self.mw = mw
|
|
|
|
self.importer = importer
|
|
|
|
self.frm = aqt.forms.importing.Ui_ImportDialog()
|
|
|
|
self.frm.setupUi(self)
|
2016-05-31 10:51:40 +02:00
|
|
|
self.frm.buttonBox.button(QDialogButtonBox.Help).clicked.connect(
|
|
|
|
self.helpRequested)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.setupMappingFrame()
|
|
|
|
self.setupOptions()
|
|
|
|
self.modelChanged()
|
2013-04-15 06:46:07 +02:00
|
|
|
self.frm.autoDetect.setVisible(self.importer.needDelimiter)
|
2012-12-21 08:51:59 +01:00
|
|
|
addHook("currentModelChanged", self.modelChanged)
|
2016-05-31 10:51:40 +02:00
|
|
|
self.frm.autoDetect.clicked.connect(self.onDelimiter)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.updateDelimiterButtonText()
|
2013-06-10 08:03:26 +02:00
|
|
|
self.frm.allowHTML.setChecked(self.mw.pm.profile.get('allowHTML', True))
|
2013-11-26 10:19:54 +01:00
|
|
|
self.frm.importMode.setCurrentIndex(self.mw.pm.profile.get('importMode', 1))
|
2014-02-22 11:30:32 +01:00
|
|
|
# import button
|
|
|
|
b = QPushButton(_("Import"))
|
|
|
|
self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.exec_()
|
|
|
|
|
|
|
|
def setupOptions(self):
|
|
|
|
self.model = self.mw.col.models.current()
|
|
|
|
self.modelChooser = aqt.modelchooser.ModelChooser(
|
|
|
|
self.mw, self.frm.modelArea, label=False)
|
|
|
|
self.deck = aqt.deckchooser.DeckChooser(
|
|
|
|
self.mw, self.frm.deckArea, label=False)
|
|
|
|
|
|
|
|
def modelChanged(self):
|
|
|
|
self.importer.model = self.mw.col.models.current()
|
|
|
|
self.importer.initMapping()
|
|
|
|
self.showMapping()
|
|
|
|
if self.mw.col.conf.get("addToCur", True):
|
|
|
|
did = self.mw.col.conf['curDeck']
|
|
|
|
if self.mw.col.decks.isDyn(did):
|
|
|
|
did = 1
|
|
|
|
else:
|
|
|
|
did = self.importer.model['did']
|
|
|
|
#self.deck.setText(self.mw.col.decks.name(did))
|
|
|
|
|
|
|
|
def onDelimiter(self):
|
|
|
|
str = getOnlyText(_("""\
|
|
|
|
By default, Anki will detect the character between fields, such as
|
|
|
|
a tab, comma, and so on. If Anki is detecting the character incorrectly,
|
|
|
|
you can enter it here. Use \\t to represent tab."""),
|
|
|
|
self, help="importing") or "\t"
|
|
|
|
str = str.replace("\\t", "\t")
|
2017-02-13 13:12:19 +01:00
|
|
|
if len(str) > 1:
|
|
|
|
showWarning(_(
|
|
|
|
"Multi-character separators are not supported. "
|
|
|
|
"Please enter one character only."))
|
|
|
|
return
|
2012-12-21 08:51:59 +01:00
|
|
|
self.hideMapping()
|
|
|
|
def updateDelim():
|
|
|
|
self.importer.delimiter = str
|
|
|
|
self.importer.updateDelimiter()
|
|
|
|
self.showMapping(hook=updateDelim)
|
|
|
|
self.updateDelimiterButtonText()
|
|
|
|
|
|
|
|
def updateDelimiterButtonText(self):
|
|
|
|
if not self.importer.needDelimiter:
|
|
|
|
return
|
|
|
|
if self.importer.delimiter:
|
|
|
|
d = self.importer.delimiter
|
|
|
|
else:
|
|
|
|
d = self.importer.dialect.delimiter
|
|
|
|
if d == "\t":
|
|
|
|
d = _("Tab")
|
|
|
|
elif d == ",":
|
|
|
|
d = _("Comma")
|
|
|
|
elif d == " ":
|
|
|
|
d = _("Space")
|
|
|
|
elif d == ";":
|
|
|
|
d = _("Semicolon")
|
|
|
|
elif d == ":":
|
|
|
|
d = _("Colon")
|
|
|
|
else:
|
2016-05-12 06:45:35 +02:00
|
|
|
d = repr(d)
|
2012-12-21 08:51:59 +01:00
|
|
|
txt = _("Fields separated by: %s") % d
|
|
|
|
self.frm.autoDetect.setText(txt)
|
2017-02-13 13:12:19 +01:00
|
|
|
|
2014-02-22 11:30:32 +01:00
|
|
|
def accept(self):
|
2012-12-21 08:51:59 +01:00
|
|
|
self.importer.mapping = self.mapping
|
|
|
|
if not self.importer.mappingOk():
|
|
|
|
showWarning(
|
|
|
|
_("The first field of the note type must be mapped."))
|
|
|
|
return
|
|
|
|
self.importer.importMode = self.frm.importMode.currentIndex()
|
2013-01-08 02:32:41 +01:00
|
|
|
self.mw.pm.profile['importMode'] = self.importer.importMode
|
2012-12-21 08:51:59 +01:00
|
|
|
self.importer.allowHTML = self.frm.allowHTML.isChecked()
|
2012-12-22 05:18:28 +01:00
|
|
|
self.mw.pm.profile['allowHTML'] = self.importer.allowHTML
|
2012-12-21 08:51:59 +01:00
|
|
|
did = self.deck.selectedId()
|
|
|
|
if did != self.importer.model['did']:
|
|
|
|
self.importer.model['did'] = did
|
|
|
|
self.mw.col.models.save(self.importer.model)
|
2013-09-03 20:13:02 +02:00
|
|
|
self.mw.col.decks.select(did)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.mw.progress.start(immediate=True)
|
|
|
|
self.mw.checkpoint(_("Import"))
|
|
|
|
try:
|
|
|
|
self.importer.run()
|
2013-01-14 23:10:26 +01:00
|
|
|
except UnicodeDecodeError:
|
2013-05-03 14:29:25 +02:00
|
|
|
showUnicodeWarning()
|
2013-01-14 23:10:26 +01:00
|
|
|
return
|
2016-05-12 06:45:35 +02:00
|
|
|
except Exception as e:
|
2012-12-21 08:51:59 +01:00
|
|
|
msg = _("Import failed.\n")
|
2013-10-20 03:57:42 +02:00
|
|
|
err = repr(str(e))
|
2012-12-21 08:51:59 +01:00
|
|
|
if "1-character string" in err:
|
|
|
|
msg += err
|
2013-11-07 13:57:23 +01:00
|
|
|
elif "invalidTempFolder" in err:
|
|
|
|
msg += self.mw.errorHandler.tempFolderMsg()
|
2012-12-21 08:51:59 +01:00
|
|
|
else:
|
2016-05-12 06:45:35 +02:00
|
|
|
msg += str(traceback.format_exc(), "ascii", "replace")
|
2012-12-21 08:51:59 +01:00
|
|
|
showText(msg)
|
|
|
|
return
|
|
|
|
finally:
|
|
|
|
self.mw.progress.finish()
|
|
|
|
txt = _("Importing complete.") + "\n"
|
|
|
|
if self.importer.log:
|
|
|
|
txt += "\n".join(self.importer.log)
|
|
|
|
self.close()
|
|
|
|
showText(txt)
|
|
|
|
self.mw.reset()
|
|
|
|
|
|
|
|
def setupMappingFrame(self):
|
|
|
|
# qt seems to have a bug with adding/removing from a grid, so we add
|
|
|
|
# to a separate object and add/remove that instead
|
|
|
|
self.frame = QFrame(self.frm.mappingArea)
|
|
|
|
self.frm.mappingArea.setWidget(self.frame)
|
|
|
|
self.mapbox = QVBoxLayout(self.frame)
|
|
|
|
self.mapbox.setContentsMargins(0,0,0,0)
|
|
|
|
self.mapwidget = None
|
|
|
|
|
|
|
|
def hideMapping(self):
|
|
|
|
self.frm.mappingGroup.hide()
|
|
|
|
|
|
|
|
def showMapping(self, keepMapping=False, hook=None):
|
|
|
|
if hook:
|
|
|
|
hook()
|
|
|
|
if not keepMapping:
|
|
|
|
self.mapping = self.importer.mapping
|
|
|
|
self.frm.mappingGroup.show()
|
|
|
|
assert self.importer.fields()
|
|
|
|
# set up the mapping grid
|
|
|
|
if self.mapwidget:
|
|
|
|
self.mapbox.removeWidget(self.mapwidget)
|
|
|
|
self.mapwidget.deleteLater()
|
|
|
|
self.mapwidget = QWidget()
|
|
|
|
self.mapbox.addWidget(self.mapwidget)
|
|
|
|
self.grid = QGridLayout(self.mapwidget)
|
|
|
|
self.mapwidget.setLayout(self.grid)
|
2016-05-31 10:51:40 +02:00
|
|
|
self.grid.setContentsMargins(3,3,3,3)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.grid.setSpacing(6)
|
|
|
|
fields = self.importer.fields()
|
|
|
|
for num in range(len(self.mapping)):
|
|
|
|
text = _("Field <b>%d</b> of file is:") % (num + 1)
|
|
|
|
self.grid.addWidget(QLabel(text), num, 0)
|
|
|
|
if self.mapping[num] == "_tags":
|
|
|
|
text = _("mapped to <b>Tags</b>")
|
|
|
|
elif self.mapping[num]:
|
|
|
|
text = _("mapped to <b>%s</b>") % self.mapping[num]
|
|
|
|
else:
|
|
|
|
text = _("<ignored>")
|
|
|
|
self.grid.addWidget(QLabel(text), num, 1)
|
|
|
|
button = QPushButton(_("Change"))
|
|
|
|
self.grid.addWidget(button, num, 2)
|
2016-10-19 20:32:27 +02:00
|
|
|
button.clicked.connect(lambda _, s=self,n=num: s.changeMappingNum(n))
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
def changeMappingNum(self, n):
|
|
|
|
f = ChangeMap(self.mw, self.importer.model, self.mapping[n]).getField()
|
|
|
|
try:
|
|
|
|
# make sure we don't have it twice
|
|
|
|
index = self.mapping.index(f)
|
|
|
|
self.mapping[index] = None
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
self.mapping[n] = f
|
|
|
|
if getattr(self.importer, "delimiter", False):
|
|
|
|
self.savedDelimiter = self.importer.delimiter
|
|
|
|
def updateDelim():
|
|
|
|
self.importer.delimiter = self.savedDelimiter
|
|
|
|
self.showMapping(hook=updateDelim, keepMapping=True)
|
|
|
|
else:
|
|
|
|
self.showMapping(keepMapping=True)
|
|
|
|
|
|
|
|
def reject(self):
|
|
|
|
self.modelChooser.cleanup()
|
2018-03-01 05:20:30 +01:00
|
|
|
self.deck.cleanup()
|
2012-12-21 08:51:59 +01:00
|
|
|
remHook("currentModelChanged", self.modelChanged)
|
|
|
|
QDialog.reject(self)
|
|
|
|
|
|
|
|
def helpRequested(self):
|
2014-02-22 11:30:32 +01:00
|
|
|
openHelp("importing")
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2013-05-03 14:29:25 +02:00
|
|
|
|
|
|
|
def showUnicodeWarning():
|
|
|
|
"""Shorthand to show a standard warning."""
|
|
|
|
showWarning(_(
|
|
|
|
"Selected file was not in UTF-8 format. Please see the "
|
|
|
|
"importing section of the manual."))
|
|
|
|
|
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
def onImport(mw):
|
|
|
|
filt = ";;".join([x[0] for x in importing.Importers])
|
|
|
|
file = getFile(mw, _("Import"), None, key="import",
|
|
|
|
filter=filt)
|
|
|
|
if not file:
|
|
|
|
return
|
2016-05-12 06:45:35 +02:00
|
|
|
file = str(file)
|
2018-09-27 03:35:21 +02:00
|
|
|
|
|
|
|
head, ext = os.path.splitext(file)
|
|
|
|
ext = ext.lower()
|
|
|
|
if ext == ".anki":
|
|
|
|
showInfo(_(".anki files are from a very old version of Anki. You can import them with Anki 2.0, available on the Anki website."))
|
|
|
|
return
|
|
|
|
elif ext == ".anki2":
|
|
|
|
showInfo(_(".anki2 files are not directly importable - please import the .apkg or .zip file you have received instead."))
|
|
|
|
return
|
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
importFile(mw, file)
|
|
|
|
|
|
|
|
def importFile(mw, file):
|
|
|
|
importer = None
|
|
|
|
done = False
|
|
|
|
for i in importing.Importers:
|
|
|
|
if done:
|
|
|
|
break
|
|
|
|
for mext in re.findall("[( ]?\*\.(.+?)[) ]", i[0]):
|
2013-02-24 02:09:03 +01:00
|
|
|
if file.endswith("." + mext):
|
2012-12-21 08:51:59 +01:00
|
|
|
importer = i[1]
|
|
|
|
done = True
|
|
|
|
break
|
|
|
|
if not importer:
|
|
|
|
# if no matches, assume TSV
|
|
|
|
importer = importing.Importers[0][1]
|
|
|
|
importer = importer(mw.col, file)
|
|
|
|
# need to show import dialog?
|
|
|
|
if importer.needMapper:
|
|
|
|
# make sure we can load the file first
|
|
|
|
mw.progress.start(immediate=True)
|
|
|
|
try:
|
|
|
|
importer.open()
|
|
|
|
except UnicodeDecodeError:
|
2013-05-03 14:29:25 +02:00
|
|
|
showUnicodeWarning()
|
2012-12-21 08:51:59 +01:00
|
|
|
return
|
2016-05-12 06:45:35 +02:00
|
|
|
except Exception as e:
|
2013-10-20 03:57:42 +02:00
|
|
|
msg = repr(str(e))
|
2014-06-17 15:31:38 +02:00
|
|
|
if msg == "'unknownFormat'":
|
2018-09-27 03:35:21 +02:00
|
|
|
showWarning(_("Unknown file format."))
|
2012-12-21 08:51:59 +01:00
|
|
|
else:
|
|
|
|
msg = _("Import failed. Debugging info:\n")
|
2016-05-12 06:45:35 +02:00
|
|
|
msg += str(traceback.format_exc())
|
2012-12-21 08:51:59 +01:00
|
|
|
showText(msg)
|
|
|
|
return
|
|
|
|
finally:
|
|
|
|
mw.progress.finish()
|
|
|
|
diag = ImportDialog(mw, importer)
|
|
|
|
else:
|
2013-09-11 08:56:59 +02:00
|
|
|
# if it's an apkg/zip, first test it's a valid file
|
2012-12-21 08:51:59 +01:00
|
|
|
if importer.__class__.__name__ == "AnkiPackageImporter":
|
2013-09-11 08:56:59 +02:00
|
|
|
try:
|
2014-09-27 17:19:43 +02:00
|
|
|
z = zipfile.ZipFile(importer.file)
|
2013-09-11 08:56:59 +02:00
|
|
|
z.getinfo("collection.anki2")
|
|
|
|
except:
|
2014-06-28 22:55:01 +02:00
|
|
|
showWarning(invalidZipMsg())
|
2013-09-11 08:56:59 +02:00
|
|
|
return
|
|
|
|
# we need to ask whether to import/replace
|
2012-12-21 08:51:59 +01:00
|
|
|
if not setupApkgImport(mw, importer):
|
|
|
|
return
|
|
|
|
mw.progress.start(immediate=True)
|
|
|
|
try:
|
2018-12-01 04:37:26 +01:00
|
|
|
try:
|
|
|
|
importer.run()
|
|
|
|
finally:
|
|
|
|
mw.progress.finish()
|
2013-05-17 07:22:49 +02:00
|
|
|
except zipfile.BadZipfile:
|
2014-06-28 22:55:01 +02:00
|
|
|
showWarning(invalidZipMsg())
|
2016-05-12 06:45:35 +02:00
|
|
|
except Exception as e:
|
2013-10-20 03:57:42 +02:00
|
|
|
err = repr(str(e))
|
|
|
|
if "invalidFile" in err:
|
2012-12-21 08:51:59 +01:00
|
|
|
msg = _("""\
|
2013-01-08 01:04:35 +01:00
|
|
|
Invalid file. Please restore from backup.""")
|
2012-12-21 08:51:59 +01:00
|
|
|
showWarning(msg)
|
2014-02-02 17:51:51 +01:00
|
|
|
elif "invalidTempFolder" in err:
|
|
|
|
showWarning(mw.errorHandler.tempFolderMsg())
|
2013-10-20 03:57:42 +02:00
|
|
|
elif "readonly" in err:
|
2012-12-21 08:51:59 +01:00
|
|
|
showWarning(_("""\
|
|
|
|
Unable to import from a read-only file."""))
|
|
|
|
else:
|
|
|
|
msg = _("Import failed.\n")
|
2016-05-12 06:45:35 +02:00
|
|
|
msg += str(traceback.format_exc())
|
2012-12-21 08:51:59 +01:00
|
|
|
showText(msg)
|
|
|
|
else:
|
|
|
|
log = "\n".join(importer.log)
|
|
|
|
if "\n" not in log:
|
|
|
|
tooltip(log)
|
|
|
|
else:
|
|
|
|
showText(log)
|
|
|
|
mw.reset()
|
|
|
|
|
2014-06-28 22:55:01 +02:00
|
|
|
def invalidZipMsg():
|
|
|
|
return _("""\
|
|
|
|
This file does not appear to be a valid .apkg file. If you're getting this \
|
|
|
|
error from a file downloaded from AnkiWeb, chances are that your download \
|
|
|
|
failed. Please try again, and if the problem persists, please try again \
|
|
|
|
with a different browser.""")
|
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
def setupApkgImport(mw, importer):
|
|
|
|
base = os.path.basename(importer.file).lower()
|
2017-09-10 08:58:55 +02:00
|
|
|
full = ((base == "collection.apkg") or
|
|
|
|
re.match("backup-.*\\.apkg", base) or
|
|
|
|
base.endswith(".colpkg"))
|
2012-12-21 08:51:59 +01:00
|
|
|
if not full:
|
|
|
|
# adding
|
|
|
|
return True
|
2012-12-22 00:34:11 +01:00
|
|
|
backup = re.match("backup-.*\\.apkg", base)
|
2017-08-16 11:45:39 +02:00
|
|
|
if not mw.restoringBackup and not askUser(_("""\
|
2012-12-21 08:51:59 +01:00
|
|
|
This will delete your existing collection and replace it with the data in \
|
2017-09-10 08:58:55 +02:00
|
|
|
the file you're importing. Are you sure?"""), msgfunc=QMessageBox.warning,
|
|
|
|
defaultno=True):
|
2012-12-21 08:51:59 +01:00
|
|
|
return False
|
|
|
|
# schedule replacement; don't do it immediately as we may have been
|
|
|
|
# called as part of the startup routine
|
|
|
|
mw.progress.timer(
|
2012-12-22 00:34:11 +01:00
|
|
|
100, lambda mw=mw, f=importer.file: replaceWithApkg(mw, f, backup), False)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2012-12-22 00:34:11 +01:00
|
|
|
def replaceWithApkg(mw, file, backup):
|
2017-08-16 11:45:39 +02:00
|
|
|
mw.unloadCollection(lambda: _replaceWithApkg(mw, file, backup))
|
|
|
|
|
|
|
|
def _replaceWithApkg(mw, file, backup):
|
|
|
|
mw.progress.start(immediate=True)
|
2018-01-29 05:12:04 +01:00
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
z = zipfile.ZipFile(file)
|
2018-01-29 05:12:04 +01:00
|
|
|
|
|
|
|
# v2 scheduler?
|
|
|
|
colname = "collection.anki21"
|
|
|
|
try:
|
|
|
|
z.getinfo(colname)
|
|
|
|
except KeyError:
|
|
|
|
colname = "collection.anki2"
|
|
|
|
|
2014-06-17 23:55:55 +02:00
|
|
|
try:
|
2018-02-05 06:30:57 +01:00
|
|
|
with z.open(colname) as source, \
|
|
|
|
open(mw.pm.collectionPath(), "wb") as target:
|
|
|
|
shutil.copyfileobj(source, target)
|
2014-06-17 23:55:55 +02:00
|
|
|
except:
|
2017-08-16 11:45:39 +02:00
|
|
|
mw.progress.finish()
|
2014-06-17 23:55:55 +02:00
|
|
|
showWarning(_("The provided file is not a valid .apkg file."))
|
|
|
|
return
|
2012-12-21 08:51:59 +01:00
|
|
|
# because users don't have a backup of media, it's safer to import new
|
|
|
|
# data and rely on them running a media db check to get rid of any
|
|
|
|
# unwanted media. in the future we might also want to deduplicate this
|
|
|
|
# step
|
|
|
|
d = os.path.join(mw.pm.profileFolder(), "collection.media")
|
2016-07-11 06:56:58 +02:00
|
|
|
for n, (cStr, file) in enumerate(
|
|
|
|
json.loads(z.read("media").decode("utf8")).items()):
|
2016-02-26 01:01:46 +01:00
|
|
|
mw.progress.update(ngettext("Processed %d media file",
|
|
|
|
"Processed %d media files", n) % n)
|
|
|
|
size = z.getinfo(cStr).file_size
|
2017-09-30 11:29:21 +02:00
|
|
|
dest = os.path.join(d, unicodedata.normalize("NFC", file))
|
2016-02-26 01:01:46 +01:00
|
|
|
# if we have a matching file size
|
|
|
|
if os.path.exists(dest) and size == os.stat(dest).st_size:
|
|
|
|
continue
|
|
|
|
data = z.read(cStr)
|
|
|
|
open(dest, "wb").write(data)
|
2012-12-21 08:51:59 +01:00
|
|
|
z.close()
|
|
|
|
# reload
|
2017-08-16 11:45:39 +02:00
|
|
|
if not mw.loadCollection():
|
|
|
|
mw.progress.finish()
|
|
|
|
return
|
2012-12-22 00:34:11 +01:00
|
|
|
if backup:
|
|
|
|
mw.col.modSchema(check=False)
|
2012-12-21 08:51:59 +01:00
|
|
|
mw.progress.finish()
|