mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 06:52:21 -04:00
Refactor: Add manifest schema, unify install paths, use context manager
Sets the foundation for more elaborate additions to the manifest. Manifest files are still only being read for local imports, but with this commit that could be easily changed in the future.
This commit is contained in:
parent
2ed61c9c99
commit
8fceccf4b7
1 changed files with 57 additions and 39 deletions
|
@ -22,6 +22,12 @@ from anki.sync import AnkiRequestsClient
|
||||||
class AddonManager:
|
class AddonManager:
|
||||||
|
|
||||||
ext = ".ankiaddon"
|
ext = ".ankiaddon"
|
||||||
|
# todo?: use jsonschema package
|
||||||
|
_manifest_schema = {
|
||||||
|
"package": {"type": str, "req": True, "meta": False},
|
||||||
|
"name": {"type": str, "req": True, "meta": True},
|
||||||
|
"mod": {"type": int, "req": False, "meta": True}
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, mw):
|
def __init__(self, mw):
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
|
@ -102,42 +108,49 @@ When loading '%(name)s':
|
||||||
# Installing and deleting add-ons
|
# Installing and deleting add-ons
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def installDownload(self, sid, data, name):
|
def _readManifestFile(self, zfile):
|
||||||
try:
|
|
||||||
zfile = ZipFile(io.BytesIO(data))
|
|
||||||
except zipfile.BadZipfile:
|
|
||||||
showWarning(_("The download was corrupt. Please try again."))
|
|
||||||
return False
|
|
||||||
|
|
||||||
mod = intTime()
|
|
||||||
|
|
||||||
self.install(sid, zfile, name, mod)
|
|
||||||
|
|
||||||
def installLocal(self, path):
|
|
||||||
try:
|
|
||||||
zfile = ZipFile(path)
|
|
||||||
except zipfile.BadZipfile:
|
|
||||||
return False, _("Corrupt add-on archive.")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with zfile.open("manifest.json") as f:
|
with zfile.open("manifest.json") as f:
|
||||||
info = json.loads(f.read())
|
data = json.loads(f.read())
|
||||||
package = info["package"]
|
manifest = {} # build new manifest from recognized keys
|
||||||
name = info["name"]
|
for key, attrs in self._manifest_schema.items():
|
||||||
mod = info.get("mod", None)
|
if not attrs["req"] and key not in data:
|
||||||
assert isinstance(package, str) and isinstance(name, str)
|
continue
|
||||||
assert isinstance(mod, int) or mod is None
|
val = data[key]
|
||||||
|
assert isinstance(val, attrs["type"])
|
||||||
|
manifest[key] = val
|
||||||
except (KeyError, json.decoder.JSONDecodeError, AssertionError):
|
except (KeyError, json.decoder.JSONDecodeError, AssertionError):
|
||||||
# raised for missing manifest, invalid json, missing/invalid keys
|
# raised for missing manifest, invalid json, missing/invalid keys
|
||||||
return False, _("Invalid add-on manifest.")
|
return {}
|
||||||
|
return manifest
|
||||||
|
|
||||||
self.install(package, zfile, name, mod)
|
def install(self, file, manifest=None):
|
||||||
|
"""Install add-on from path or file-like object. Metadata is read
|
||||||
|
from the manifest file by default, but this may me bypassed
|
||||||
|
by supplying a 'manifest' dictionary"""
|
||||||
|
try:
|
||||||
|
zfile = ZipFile(file)
|
||||||
|
except zipfile.BadZipfile:
|
||||||
|
return False, "zip"
|
||||||
|
|
||||||
return name, None
|
with zfile:
|
||||||
|
manifest = manifest or self._readManifestFile(zfile)
|
||||||
|
if not manifest:
|
||||||
|
return False, "manifest"
|
||||||
|
package = manifest["package"]
|
||||||
|
meta = self.addonMeta(package)
|
||||||
|
self._install(package, zfile)
|
||||||
|
|
||||||
def install(self, dir, zfile, name, mod):
|
schema = self._manifest_schema
|
||||||
|
manifest_meta = {k: v for k, v in manifest.items()
|
||||||
|
if k in schema and schema[k]["meta"]}
|
||||||
|
meta.update(manifest_meta)
|
||||||
|
self.writeAddonMeta(package, meta)
|
||||||
|
|
||||||
|
return True, meta["name"]
|
||||||
|
|
||||||
|
def _install(self, dir, zfile):
|
||||||
# previously installed?
|
# previously installed?
|
||||||
meta = self.addonMeta(dir)
|
|
||||||
base = self.addonsFolder(dir)
|
base = self.addonsFolder(dir)
|
||||||
if os.path.exists(base):
|
if os.path.exists(base):
|
||||||
self.backupUserFiles(dir)
|
self.backupUserFiles(dir)
|
||||||
|
@ -158,12 +171,6 @@ When loading '%(name)s':
|
||||||
continue
|
continue
|
||||||
zfile.extract(n, base)
|
zfile.extract(n, base)
|
||||||
|
|
||||||
# update metadata
|
|
||||||
meta['name'] = name
|
|
||||||
if mod is not None: # allow packages to skip updating mod
|
|
||||||
meta['mod'] = mod
|
|
||||||
self.writeAddonMeta(dir, meta)
|
|
||||||
|
|
||||||
def deleteAddon(self, dir):
|
def deleteAddon(self, dir):
|
||||||
send2trash(self.addonsFolder(dir))
|
send2trash(self.addonsFolder(dir))
|
||||||
|
|
||||||
|
@ -176,12 +183,16 @@ When loading '%(name)s':
|
||||||
self.mw.progress.start(immediate=True)
|
self.mw.progress.start(immediate=True)
|
||||||
for path in paths:
|
for path in paths:
|
||||||
base = os.path.basename(path)
|
base = os.path.basename(path)
|
||||||
name, error = self.installLocal(path)
|
ret = self.install(path)
|
||||||
if error:
|
if ret[0] is False:
|
||||||
|
if ret[1] == "zip":
|
||||||
|
msg = _("Corrupt add-on file.")
|
||||||
|
elif ret[1] == "manifest":
|
||||||
|
msg = _("Invalid add-on manifest.")
|
||||||
errs.append(_("Error installing <i>%(base)s</i>: %(error)s"
|
errs.append(_("Error installing <i>%(base)s</i>: %(error)s"
|
||||||
% dict(base=base, error=error)))
|
% dict(base=base, error=msg)))
|
||||||
else:
|
else:
|
||||||
log.append(_("Installed %(name)s" % dict(name=name)))
|
log.append(_("Installed %(name)s" % dict(name=ret[1])))
|
||||||
self.mw.progress.finish()
|
self.mw.progress.finish()
|
||||||
return log, errs
|
return log, errs
|
||||||
|
|
||||||
|
@ -200,7 +211,14 @@ When loading '%(name)s':
|
||||||
data, fname = ret
|
data, fname = ret
|
||||||
fname = fname.replace("_", " ")
|
fname = fname.replace("_", " ")
|
||||||
name = os.path.splitext(fname)[0]
|
name = os.path.splitext(fname)[0]
|
||||||
self.installDownload(str(n), data, name)
|
ret = self.install(io.BytesIO(data),
|
||||||
|
manifest={"package": str(n), "name": name,
|
||||||
|
"mod": intTime()})
|
||||||
|
if ret[0] is False:
|
||||||
|
if ret[1] == "zip":
|
||||||
|
showWarning(_("The download was corrupt. Please try again."))
|
||||||
|
elif ret[1] == "manifest":
|
||||||
|
showWarning(_("Invalid add-on manifest."))
|
||||||
log.append(_("Downloaded %(fname)s" % dict(fname=name)))
|
log.append(_("Downloaded %(fname)s" % dict(fname=name)))
|
||||||
self.mw.progress.finish()
|
self.mw.progress.finish()
|
||||||
return log, errs
|
return log, errs
|
||||||
|
|
Loading…
Reference in a new issue