Fix a number of bugs with add-on conflict resolution (#1780)

* Always enable manually installed add-ons

Ensures that manually installed add-ons are enabled after the installation, even if previously disabled.

Prevents scenarios where users could end up with no active add-on build (e.g. when switching between stable add-on builds distributed via AnkiWeb and betas distributed via GitHub).

* Improve type annotations

* Also enable disabled AnkiWeb add-ons upon interactive installation

Applies to add-ons that users actively install via their AnkiWeb ID. Updates are exempt, preserving whatever status add-ons were in.

* Prevent disabled add-ons from triggering conflicts

* Fix download_addons() not passing on force_enable argument (dae)
This commit is contained in:
Aristotelis 2022-04-09 05:51:59 +02:00 committed by GitHub
parent f3f5a42218
commit 8100e81789
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 13 deletions

View File

@ -360,6 +360,10 @@ class AddonManager:
return all_conflicts
def _disableConflicting(self, module: str, conflicts: list[str] = None) -> set[str]:
if not self.isEnabled(module):
# disabled add-ons should not trigger conflict handling
return set()
conflicts = conflicts or self.addonConflicts(module)
installed = self.allAddons()
@ -388,7 +392,10 @@ class AddonManager:
return manifest
def install(
self, file: IO | str, manifest: dict[str, Any] = None
self,
file: IO | str,
manifest: dict[str, Any] | None = None,
force_enable: bool = False,
) -> InstallOk | InstallError:
"""Install add-on from path or file-like object. Metadata is read
from the manifest file, with keys overriden by supplying a 'manifest'
@ -418,6 +425,10 @@ class AddonManager:
k: v for k, v in manifest.items() if k in schema and schema[k]["meta"]
}
meta.update(manifest_meta)
if force_enable:
meta["disabled"] = False
self.writeAddonMeta(package, meta)
meta2 = self.addon_meta(package)
@ -466,7 +477,10 @@ class AddonManager:
######################################################################
def processPackages(
self, paths: list[str], parent: QWidget = None
self,
paths: list[str],
parent: QWidget | None = None,
force_enable: bool = False,
) -> tuple[list[str], list[str]]:
log = []
@ -476,7 +490,7 @@ class AddonManager:
try:
for path in paths:
base = os.path.basename(path)
result = self.install(path)
result = self.install(path, force_enable=force_enable)
if isinstance(result, InstallError):
errs.extend(
@ -905,7 +919,9 @@ class AddonsDialog(QDialog):
def onGetAddons(self) -> None:
obj = GetAddons(self)
if obj.ids:
download_addons(self, self.mgr, obj.ids, self.after_downloading)
download_addons(
self, self.mgr, obj.ids, self.after_downloading, force_enable=True
)
def after_downloading(self, log: list[DownloadLogEntry]) -> None:
self.redrawAddons()
@ -924,7 +940,7 @@ class AddonsDialog(QDialog):
if not paths:
return False
installAddonPackages(self.mgr, paths, parent=self)
installAddonPackages(self.mgr, paths, parent=self, force_enable=True)
self.redrawAddons()
return None
@ -1078,7 +1094,7 @@ def download_encountered_problem(log: list[DownloadLogEntry]) -> bool:
def download_and_install_addon(
mgr: AddonManager, client: HttpClient, id: int
mgr: AddonManager, client: HttpClient, id: int, force_enable: bool = False
) -> DownloadLogEntry:
"Download and install a single add-on."
result = download_addon(client, id)
@ -1099,7 +1115,9 @@ def download_and_install_addon(
branch_index=result.branch_index,
)
result2 = mgr.install(io.BytesIO(result.data), manifest=manifest)
result2 = mgr.install(
io.BytesIO(result.data), manifest=manifest, force_enable=force_enable
)
return (id, result2)
@ -1119,7 +1137,10 @@ class DownloaderInstaller(QObject):
self.client.progress_hook = bg_thread_progress
def download(
self, ids: list[int], on_done: Callable[[list[DownloadLogEntry]], None]
self,
ids: list[int],
on_done: Callable[[list[DownloadLogEntry]], None],
force_enable: bool = False,
) -> None:
self.ids = ids
self.log: list[DownloadLogEntry] = []
@ -1132,7 +1153,9 @@ class DownloaderInstaller(QObject):
parent = self.parent()
assert isinstance(parent, QWidget)
self.mgr.mw.progress.start(immediate=True, parent=parent)
self.mgr.mw.taskman.run_in_background(self._download_all, self._download_done)
self.mgr.mw.taskman.run_in_background(
lambda: self._download_all(force_enable), self._download_done
)
def _progress_callback(self, up: int, down: int) -> None:
self.dl_bytes += down
@ -1144,9 +1167,13 @@ class DownloaderInstaller(QObject):
)
)
def _download_all(self) -> None:
def _download_all(self, force_enable: bool = False) -> None:
for id in self.ids:
self.log.append(download_and_install_addon(self.mgr, self.client, id))
self.log.append(
download_and_install_addon(
self.mgr, self.client, id, force_enable=force_enable
)
)
def _download_done(self, future: Future) -> None:
self.mgr.mw.progress.finish()
@ -1176,11 +1203,12 @@ def download_addons(
ids: list[int],
on_done: Callable[[list[DownloadLogEntry]], None],
client: HttpClient | None = None,
force_enable: bool = False,
) -> None:
if client is None:
client = HttpClient()
downloader = DownloaderInstaller(parent, mgr, client)
downloader.download(ids, on_done=on_done)
downloader.download(ids, on_done=on_done, force_enable=force_enable)
# Update checking
@ -1619,6 +1647,7 @@ def installAddonPackages(
warn: bool = False,
strictly_modal: bool = False,
advise_restart: bool = False,
force_enable: bool = False,
) -> bool:
if warn:
@ -1639,7 +1668,9 @@ def installAddonPackages(
):
return False
log, errs = addonsManager.processPackages(paths, parent=parent)
log, errs = addonsManager.processPackages(
paths, parent=parent, force_enable=force_enable
)
if log:
log_html = "<br>".join(log)

View File

@ -1207,6 +1207,7 @@ title="{}" {}>{}</button>""".format(
advise_restart=not startup,
strictly_modal=startup,
parent=None if startup else self,
force_enable=True,
)
# Cramming