add dialog to choose addons to update
This commit is contained in:
parent
5d578cab03
commit
69006b5872
@ -64,3 +64,4 @@ addons-delete-the-numd-selected-addon =
|
|||||||
[one] Delete the { $count } selected add-on?
|
[one] Delete the { $count } selected add-on?
|
||||||
*[other] Delete the { $count } selected add-ons?
|
*[other] Delete the { $count } selected add-ons?
|
||||||
}
|
}
|
||||||
|
addons-choose-update-window-title = Update Add-ons
|
||||||
|
147
qt/aqt/addons.py
147
qt/aqt/addons.py
@ -10,6 +10,7 @@ import zipfile
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
from typing import IO, Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
from typing import IO, Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
@ -551,19 +552,19 @@ class AddonManager:
|
|||||||
if updated:
|
if updated:
|
||||||
self.write_addon_meta(addon)
|
self.write_addon_meta(addon)
|
||||||
|
|
||||||
def updates_required(self, items: List[UpdateInfo]) -> List[int]:
|
def updates_required(self, items: List[UpdateInfo]) -> List[UpdateInfo]:
|
||||||
"""Return ids of add-ons requiring an update."""
|
"""Return ids of add-ons requiring an update."""
|
||||||
need_update = []
|
need_update = []
|
||||||
for item in items:
|
for item in items:
|
||||||
addon = self.addon_meta(str(item.id))
|
addon = self.addon_meta(str(item.id))
|
||||||
# update if server mtime is newer
|
# update if server mtime is newer
|
||||||
if not addon.is_latest(item.suitable_branch_last_modified):
|
if not addon.is_latest(item.suitable_branch_last_modified):
|
||||||
need_update.append(item.id)
|
need_update.append(item)
|
||||||
elif not addon.compatible() and item.suitable_branch_last_modified > 0:
|
elif not addon.compatible() and item.suitable_branch_last_modified > 0:
|
||||||
# Addon is currently disabled, and a suitable branch was found on the
|
# Addon is currently disabled, and a suitable branch was found on the
|
||||||
# server. Ignore our stored mtime (which may have been set incorrectly
|
# server. Ignore our stored mtime (which may have been set incorrectly
|
||||||
# in the past) and require an update.
|
# in the past) and require an update.
|
||||||
need_update.append(item.id)
|
need_update.append(item)
|
||||||
|
|
||||||
return need_update
|
return need_update
|
||||||
|
|
||||||
@ -1132,6 +1133,128 @@ def download_addons(
|
|||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class ChooseAddonsToUpdateList(QListWidget):
|
||||||
|
ADDON_ID_ROLE = 101
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent: QWidget,
|
||||||
|
mgr: AddonManager,
|
||||||
|
updated_addons: List[UpdateInfo],
|
||||||
|
) -> None:
|
||||||
|
QListWidget.__init__(self, parent)
|
||||||
|
self.mgr = mgr
|
||||||
|
self.updated_addons = sorted(
|
||||||
|
updated_addons, key=lambda addon: addon.suitable_branch_last_modified
|
||||||
|
)
|
||||||
|
self.setup()
|
||||||
|
qconnect(self.itemClicked, self.toggle_check)
|
||||||
|
qconnect(self.itemDoubleClicked, self.double_click)
|
||||||
|
|
||||||
|
def setup(self) -> None:
|
||||||
|
check_state = Qt.Unchecked
|
||||||
|
header_item = QListWidgetItem("", self)
|
||||||
|
header_item.setFlags(Qt.ItemFlag(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled))
|
||||||
|
header_item.setBackground(Qt.lightGray)
|
||||||
|
header_item.setCheckState(check_state)
|
||||||
|
self.header_item = header_item
|
||||||
|
for update_info in self.updated_addons:
|
||||||
|
addon_id = update_info.id
|
||||||
|
addon_name = self.mgr.addon_meta(str(addon_id)).human_name()
|
||||||
|
update_timestamp = update_info.suitable_branch_last_modified
|
||||||
|
update_time = datetime.fromtimestamp(update_timestamp)
|
||||||
|
|
||||||
|
addon_label = f"{update_time:%Y-%m-%d} {addon_name}"
|
||||||
|
item = QListWidgetItem(addon_label, self)
|
||||||
|
# Not user checkable because it overlaps with itemClicked signal
|
||||||
|
item.setFlags(Qt.ItemFlag(Qt.ItemIsEnabled))
|
||||||
|
item.setCheckState(check_state)
|
||||||
|
item.setData(self.ADDON_ID_ROLE, addon_id)
|
||||||
|
|
||||||
|
def toggle_check(self, item: QListWidgetItem) -> None:
|
||||||
|
if item == self.header_item:
|
||||||
|
if item.checkState() == Qt.Checked:
|
||||||
|
check = Qt.Checked
|
||||||
|
else:
|
||||||
|
check = Qt.Unchecked
|
||||||
|
self.check_all_items(check)
|
||||||
|
return
|
||||||
|
# Normal Item
|
||||||
|
if item.checkState() == Qt.Checked:
|
||||||
|
item.setCheckState(Qt.Unchecked)
|
||||||
|
self.header_item.setCheckState(Qt.Unchecked)
|
||||||
|
else:
|
||||||
|
item.setCheckState(Qt.Checked)
|
||||||
|
if self.every_item_is_checked():
|
||||||
|
self.header_item.setCheckState(Qt.Checked)
|
||||||
|
|
||||||
|
def double_click(self, item: QListWidgetItem) -> None:
|
||||||
|
if item == self.header_item:
|
||||||
|
if item.checkState() == Qt.Checked:
|
||||||
|
check = Qt.Unchecked
|
||||||
|
else:
|
||||||
|
check = Qt.Checked
|
||||||
|
self.header_item.setCheckState(check)
|
||||||
|
self.check_all_items(check)
|
||||||
|
|
||||||
|
def check_all_items(self, check: Qt.CheckState = Qt.Checked) -> None:
|
||||||
|
for i in range(1, self.count()):
|
||||||
|
self.item(i).setCheckState(check)
|
||||||
|
|
||||||
|
def every_item_is_checked(self) -> bool:
|
||||||
|
for i in range(1, self.count()):
|
||||||
|
item = self.item(i)
|
||||||
|
if item.checkState() == Qt.Unchecked:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_selected_addon_ids(self) -> List[int]:
|
||||||
|
addon_ids = []
|
||||||
|
for i in range(1, self.count()):
|
||||||
|
item = self.item(i)
|
||||||
|
if item.checkState() == Qt.Checked:
|
||||||
|
addon_ids.append(item.data(self.ADDON_ID_ROLE))
|
||||||
|
return addon_ids
|
||||||
|
|
||||||
|
|
||||||
|
class ChooseAddonsToUpdateDialog(QDialog):
|
||||||
|
def __init__(
|
||||||
|
self, parent: QWidget, mgr: AddonManager, updated_addons: List[UpdateInfo]
|
||||||
|
) -> None:
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setWindowTitle(tr(TR.ADDONS_CHOOSE_UPDATE_WINDOW_TITLE))
|
||||||
|
self.setWindowModality(Qt.WindowModal)
|
||||||
|
self.mgr = mgr
|
||||||
|
self.updated_addons = updated_addons
|
||||||
|
self.setup()
|
||||||
|
restoreGeom(self, "addonsChooseUpdate")
|
||||||
|
|
||||||
|
def setup(self) -> None:
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
label = QLabel(tr(TR.ADDONS_THE_FOLLOWING_ADDONS_HAVE_UPDATES_AVAILABLE))
|
||||||
|
layout.addWidget(label)
|
||||||
|
addons_list_widget = ChooseAddonsToUpdateList(
|
||||||
|
self, self.mgr, self.updated_addons
|
||||||
|
)
|
||||||
|
layout.addWidget(addons_list_widget)
|
||||||
|
self.addons_list_widget = addons_list_widget
|
||||||
|
|
||||||
|
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) # type: ignore
|
||||||
|
qconnect(button_box.button(QDialogButtonBox.Ok).clicked, self.accept)
|
||||||
|
qconnect(button_box.button(QDialogButtonBox.Cancel).clicked, self.reject)
|
||||||
|
layout.addWidget(button_box)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def ask(self) -> List[int]:
|
||||||
|
"Returns a list of selected addons' ids"
|
||||||
|
ret = self.exec_()
|
||||||
|
saveGeom(self, "addonsChooseUpdate")
|
||||||
|
if ret == QDialog.Accepted:
|
||||||
|
return self.addons_list_widget.get_selected_addon_ids()
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def fetch_update_info(client: HttpClient, ids: List[int]) -> List[Dict]:
|
def fetch_update_info(client: HttpClient, ids: List[int]) -> List[Dict]:
|
||||||
"""Fetch update info from AnkiWeb in one or more batches."""
|
"""Fetch update info from AnkiWeb in one or more batches."""
|
||||||
all_info: List[Dict] = []
|
all_info: List[Dict] = []
|
||||||
@ -1239,31 +1362,25 @@ def handle_update_info(
|
|||||||
update_info = mgr.extract_update_info(items)
|
update_info = mgr.extract_update_info(items)
|
||||||
mgr.update_supported_versions(update_info)
|
mgr.update_supported_versions(update_info)
|
||||||
|
|
||||||
updated_ids = mgr.updates_required(update_info)
|
updated_addons = mgr.updates_required(update_info)
|
||||||
|
|
||||||
if not updated_ids:
|
if not updated_addons:
|
||||||
on_done([])
|
on_done([])
|
||||||
return
|
return
|
||||||
|
|
||||||
prompt_to_update(parent, mgr, client, updated_ids, on_done)
|
prompt_to_update(parent, mgr, client, updated_addons, on_done)
|
||||||
|
|
||||||
|
|
||||||
def prompt_to_update(
|
def prompt_to_update(
|
||||||
parent: QWidget,
|
parent: QWidget,
|
||||||
mgr: AddonManager,
|
mgr: AddonManager,
|
||||||
client: HttpClient,
|
client: HttpClient,
|
||||||
ids: List[int],
|
updated_addons: List[UpdateInfo],
|
||||||
on_done: Callable[[List[DownloadLogEntry]], None],
|
on_done: Callable[[List[DownloadLogEntry]], None],
|
||||||
) -> None:
|
) -> None:
|
||||||
names = map(lambda x: mgr.addonName(str(x)), ids)
|
ids = ChooseAddonsToUpdateDialog(parent, mgr, updated_addons).ask()
|
||||||
if not askUser(
|
if not ids:
|
||||||
tr(TR.ADDONS_THE_FOLLOWING_ADDONS_HAVE_UPDATES_AVAILABLE)
|
|
||||||
+ "\n\n"
|
|
||||||
+ "\n".join(names)
|
|
||||||
):
|
|
||||||
# on_done is not called if the user cancels
|
|
||||||
return
|
return
|
||||||
|
|
||||||
download_addons(parent, mgr, ids, on_done, client)
|
download_addons(parent, mgr, ids, on_done, client)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user