Addon Manager: Add fetching of 'score' database
This commit is contained in:
@@ -168,6 +168,7 @@ class Addon:
|
||||
self.tags = set() # Just a cache, loaded from Metadata
|
||||
self.last_updated = None
|
||||
self.stats = AddonStats()
|
||||
self.score = 0
|
||||
|
||||
# To prevent multiple threads from running git actions on this repo at the
|
||||
# same time
|
||||
@@ -260,7 +261,6 @@ class Addon:
|
||||
|
||||
def _process_date_string_to_python_datetime(self, date_string: str) -> datetime:
|
||||
split_result = re.split(r"[ ./-]+", date_string.strip())
|
||||
print(f"{self.display_name} - {split_result}")
|
||||
if len(split_result) != 3:
|
||||
raise SyntaxError(
|
||||
f"In macro {self.name}, unrecognized date string '{date_string}' (expected YYYY-MM-DD)"
|
||||
|
||||
@@ -43,6 +43,7 @@ from addonmanager_workers_startup import (
|
||||
CheckWorkbenchesForUpdatesWorker,
|
||||
CacheMacroCodeWorker,
|
||||
GetBasicAddonStatsWorker,
|
||||
GetAddonScoreWorker,
|
||||
)
|
||||
from addonmanager_workers_installation import (
|
||||
UpdateMetadataCacheWorker,
|
||||
@@ -119,6 +120,7 @@ class CommandAddonManager:
|
||||
"update_all_worker",
|
||||
"check_for_python_package_updates_worker",
|
||||
"get_basic_addon_stats_worker",
|
||||
"get_addon_score_worker",
|
||||
]
|
||||
|
||||
lock = threading.Lock()
|
||||
@@ -202,17 +204,17 @@ class CommandAddonManager:
|
||||
self.button_bar.update_all_addons.hide()
|
||||
|
||||
# Set up the listing of packages using the model-view-controller architecture
|
||||
self.packageList = PackageList(self.dialog)
|
||||
self.package_list = PackageList(self.dialog)
|
||||
self.item_model = PackageListItemModel()
|
||||
self.packageList.setModel(self.item_model)
|
||||
self.dialog.layout().addWidget(self.packageList)
|
||||
self.package_list.setModel(self.item_model)
|
||||
self.dialog.layout().addWidget(self.package_list)
|
||||
self.dialog.layout().addWidget(self.button_bar)
|
||||
|
||||
# Package details start out hidden
|
||||
self.packageDetails = PackageDetailsView(self.dialog)
|
||||
self.package_details_controller = PackageDetailsController(self.packageDetails)
|
||||
self.packageDetails.hide()
|
||||
index = self.dialog.layout().indexOf(self.packageList)
|
||||
index = self.dialog.layout().indexOf(self.package_list)
|
||||
self.dialog.layout().insertWidget(index, self.packageDetails)
|
||||
|
||||
# set nice icons to everything, by theme with fallback to FreeCAD icons
|
||||
@@ -241,9 +243,9 @@ class CommandAddonManager:
|
||||
)
|
||||
self.button_bar.python_dependencies.clicked.connect(self.show_python_updates_dialog)
|
||||
self.button_bar.developer_tools.clicked.connect(self.show_developer_tools)
|
||||
self.packageList.ui.progressBar.stop_clicked.connect(self.stop_update)
|
||||
self.packageList.itemSelected.connect(self.table_row_activated)
|
||||
self.packageList.setEnabled(False)
|
||||
self.package_list.ui.progressBar.stop_clicked.connect(self.stop_update)
|
||||
self.package_list.itemSelected.connect(self.table_row_activated)
|
||||
self.package_list.setEnabled(False)
|
||||
self.package_details_controller.execute.connect(self.executemacro)
|
||||
self.package_details_controller.install.connect(self.launch_installer_gui)
|
||||
self.package_details_controller.uninstall.connect(self.remove)
|
||||
@@ -397,6 +399,7 @@ class CommandAddonManager:
|
||||
self.check_updates,
|
||||
self.check_python_updates,
|
||||
self.fetch_addon_stats,
|
||||
self.fetch_addon_score,
|
||||
]
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
if pref.GetBool("DownloadMacros", False):
|
||||
@@ -425,7 +428,7 @@ class CommandAddonManager:
|
||||
)
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
pref.SetString("LastCacheUpdate", date.today().isoformat())
|
||||
self.packageList.item_filter.invalidateFilter()
|
||||
self.package_list.item_filter.invalidateFilter()
|
||||
|
||||
def populate_packages_table(self) -> None:
|
||||
self.item_model.clear()
|
||||
@@ -478,8 +481,8 @@ class CommandAddonManager:
|
||||
f.write(json.dumps(self.package_cache, indent=" "))
|
||||
|
||||
def activate_table_widgets(self) -> None:
|
||||
self.packageList.setEnabled(True)
|
||||
self.packageList.ui.view_bar.search.setFocus()
|
||||
self.package_list.setEnabled(True)
|
||||
self.package_list.ui.view_bar.search.setFocus()
|
||||
self.do_next_startup_phase()
|
||||
|
||||
def populate_macros(self) -> None:
|
||||
@@ -682,6 +685,28 @@ class CommandAddonManager:
|
||||
def update_addon_stats(self, addon: Addon):
|
||||
self.item_model.reload_item(addon)
|
||||
|
||||
def fetch_addon_score(self) -> None:
|
||||
"""Fetch the Addon score JSON data from a URL"""
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
url = pref.GetString("AddonsScoreURL", "NONE")
|
||||
if url and url != "NONE":
|
||||
self.get_addon_score_worker = GetAddonScoreWorker(
|
||||
url, self.item_model.repos, self.dialog
|
||||
)
|
||||
self.get_addon_score_worker.finished.connect(self.score_fetched_successfully)
|
||||
self.get_addon_score_worker.finished.connect(self.do_next_startup_phase)
|
||||
self.get_addon_score_worker.update_addon_score.connect(self.update_addon_score)
|
||||
self.get_addon_score_worker.start()
|
||||
else:
|
||||
self.package_list.ui.view_bar.set_rankings_available(False)
|
||||
self.do_next_startup_phase()
|
||||
|
||||
def update_addon_score(self, addon: Addon):
|
||||
self.item_model.reload_item(addon)
|
||||
|
||||
def score_fetched_successfully(self):
|
||||
self.package_list.ui.view_bar.set_rankings_available(True)
|
||||
|
||||
def show_developer_tools(self) -> None:
|
||||
"""Display the developer tools dialog"""
|
||||
if not self.developer_mode:
|
||||
@@ -758,24 +783,24 @@ class CommandAddonManager:
|
||||
def table_row_activated(self, selected_repo: Addon) -> None:
|
||||
"""a row was activated, show the relevant data"""
|
||||
|
||||
self.packageList.hide()
|
||||
self.package_list.hide()
|
||||
self.packageDetails.show()
|
||||
self.package_details_controller.show_repo(selected_repo)
|
||||
|
||||
def show_information(self, message: str) -> None:
|
||||
"""shows generic text in the information pane"""
|
||||
|
||||
self.packageList.ui.progressBar.set_status(message)
|
||||
self.packageList.ui.progressBar.repaint()
|
||||
self.package_list.ui.progressBar.set_status(message)
|
||||
self.package_list.ui.progressBar.repaint()
|
||||
|
||||
def show_workbench(self, repo: Addon) -> None:
|
||||
self.packageList.hide()
|
||||
self.package_list.hide()
|
||||
self.packageDetails.show()
|
||||
self.package_details_controller.show_repo(repo)
|
||||
|
||||
def on_buttonBack_clicked(self) -> None:
|
||||
self.packageDetails.hide()
|
||||
self.packageList.show()
|
||||
self.package_list.show()
|
||||
|
||||
def append_to_repos_list(self, repo: Addon) -> None:
|
||||
"""this function allows threads to update the main list of workbenches"""
|
||||
@@ -836,12 +861,12 @@ class CommandAddonManager:
|
||||
def hide_progress_widgets(self) -> None:
|
||||
"""hides the progress bar and related widgets"""
|
||||
|
||||
self.packageList.ui.progressBar.hide()
|
||||
self.packageList.ui.view_bar.search.setFocus()
|
||||
self.package_list.ui.progressBar.hide()
|
||||
self.package_list.ui.view_bar.search.setFocus()
|
||||
|
||||
def show_progress_widgets(self) -> None:
|
||||
if self.packageList.ui.progressBar.isHidden():
|
||||
self.packageList.ui.progressBar.show()
|
||||
if self.package_list.ui.progressBar.isHidden():
|
||||
self.package_list.ui.progressBar.show()
|
||||
|
||||
def update_progress_bar(self, current_value: int, max_value: int) -> None:
|
||||
"""Update the progress bar, showing it if it's hidden"""
|
||||
@@ -858,10 +883,10 @@ class CommandAddonManager:
|
||||
completed_region_portion = (self.current_progress_region - 1) * region_size
|
||||
current_region_portion = (float(current_value) / float(max_value)) * region_size
|
||||
value = completed_region_portion + current_region_portion
|
||||
self.packageList.ui.progressBar.set_value(
|
||||
self.package_list.ui.progressBar.set_value(
|
||||
value * 10
|
||||
) # Out of 1000 segments, so it moves sort of smoothly
|
||||
self.packageList.ui.progressBar.repaint()
|
||||
self.package_list.ui.progressBar.repaint()
|
||||
|
||||
def stop_update(self) -> None:
|
||||
self.cleanup_workers()
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
|
||||
from enum import IntEnum, auto
|
||||
|
||||
try:
|
||||
import FreeCAD
|
||||
except ImportError:
|
||||
FreeCAD = None
|
||||
|
||||
# Get whatever version of PySide we can
|
||||
try:
|
||||
import PySide # Use the FreeCAD wrapper
|
||||
@@ -52,7 +57,7 @@ class SortOptions(IntEnum):
|
||||
LastUpdated = QtCore.Qt.UserRole + _SortRoleOffset + 1
|
||||
DateAdded = QtCore.Qt.UserRole + _SortRoleOffset + 2
|
||||
Stars = QtCore.Qt.UserRole + _SortRoleOffset + 3
|
||||
Rank = QtCore.Qt.UserRole + _SortRoleOffset + 4
|
||||
Score = QtCore.Qt.UserRole + _SortRoleOffset + 4
|
||||
|
||||
|
||||
default_sort_order = {
|
||||
@@ -60,7 +65,7 @@ default_sort_order = {
|
||||
SortOptions.LastUpdated: QtCore.Qt.DescendingOrder,
|
||||
SortOptions.DateAdded: QtCore.Qt.DescendingOrder,
|
||||
SortOptions.Stars: QtCore.Qt.DescendingOrder,
|
||||
SortOptions.Rank: QtCore.Qt.DescendingOrder,
|
||||
SortOptions.Score: QtCore.Qt.DescendingOrder,
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +80,7 @@ class WidgetViewControlBar(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent: QtWidgets.QWidget = None):
|
||||
super().__init__(parent)
|
||||
self.has_rankings = False
|
||||
self._setup_ui()
|
||||
self._setup_connections()
|
||||
self.retranslateUi(None)
|
||||
@@ -124,6 +130,10 @@ class WidgetViewControlBar(QtWidgets.QWidget):
|
||||
)
|
||||
)
|
||||
|
||||
def set_rankings_available(self, rankings_available: bool) -> None:
|
||||
self.has_rankings = rankings_available
|
||||
self.retranslateUi(None)
|
||||
|
||||
def _setup_connections(self):
|
||||
self.view_selector.view_changed.connect(self.view_changed.emit)
|
||||
self.filter_selector.filter_changed.connect(self.filter_changed.emit)
|
||||
@@ -133,6 +143,8 @@ class WidgetViewControlBar(QtWidgets.QWidget):
|
||||
|
||||
def _sort_changed(self, index: int):
|
||||
sort_role = self.sort_selector.itemData(index)
|
||||
if sort_role is None:
|
||||
sort_role = SortOptions.Alphabetical
|
||||
self.set_sort_order(default_sort_order[sort_role])
|
||||
self.sort_changed.emit(sort_role)
|
||||
self.sort_order_changed.emit(self.sort_order)
|
||||
@@ -151,5 +163,7 @@ class WidgetViewControlBar(QtWidgets.QWidget):
|
||||
self.sort_selector.addItem(
|
||||
translate("AddonsInstaller", "GitHub Stars", "Sort order"), SortOptions.Stars
|
||||
)
|
||||
# self.sort_selector.addItem(translate("AddonsInstaller", "Rank", "Sort order"),
|
||||
# SortOptions.Rank)
|
||||
if self.has_rankings:
|
||||
self.sort_selector.addItem(
|
||||
translate("AddonsInstaller", "Score", "Sort order"), SortOptions.Score
|
||||
)
|
||||
|
||||
@@ -945,3 +945,50 @@ class GetBasicAddonStatsWorker(QtCore.QThread):
|
||||
if addon.url in json_result:
|
||||
addon.stats = AddonStats.from_json(json_result[addon.url])
|
||||
self.update_addon_stats.emit(addon)
|
||||
|
||||
|
||||
class GetAddonScoreWorker(QtCore.QThread):
|
||||
"""Fetch data from an addon score file."""
|
||||
|
||||
update_addon_score = QtCore.Signal(Addon)
|
||||
|
||||
def __init__(self, url: str, addons: List[Addon], parent: QtCore.QObject = None):
|
||||
super().__init__(parent)
|
||||
self.url = url
|
||||
self.addons = addons
|
||||
|
||||
def run(self):
|
||||
"""Fetch the remote data and load it into the addons"""
|
||||
|
||||
if self.url != "TEST":
|
||||
fetch_result = NetworkManager.AM_NETWORK_MANAGER.blocking_get(self.url, 5000)
|
||||
if fetch_result is None:
|
||||
FreeCAD.Console.PrintError(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Failed to get Addon score from {} -- sorting by score will fail\n",
|
||||
).format(self.url)
|
||||
)
|
||||
return
|
||||
text_result = fetch_result.data().decode("utf8")
|
||||
json_result = json.loads(text_result)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning("Running score generation in TEST mode...\n")
|
||||
json_result = {}
|
||||
for addon in self.addons:
|
||||
json_result[addon.url] = len(addon.display_name)
|
||||
|
||||
for addon in self.addons:
|
||||
score = None
|
||||
if addon.url in json_result:
|
||||
score = json_result[addon.url]
|
||||
elif addon.name in json_result:
|
||||
score = json_result[addon.name]
|
||||
if score is not None:
|
||||
try:
|
||||
addon.score = int(score)
|
||||
self.update_addon_score.emit(addon)
|
||||
except (ValueError, OverflowError):
|
||||
FreeCAD.Console.PrintLog(
|
||||
f"Failed to convert score value '{score}' to an integer for addon {addon.name}"
|
||||
)
|
||||
|
||||
@@ -196,8 +196,8 @@ class PackageListItemModel(QtCore.QAbstractListModel):
|
||||
if self.repos[row].stats and self.repos[row].stats.stars:
|
||||
return self.repos[row].stats.stars
|
||||
return 0
|
||||
if role == SortOptions.Rank:
|
||||
return len(self.repos[row].display_name)
|
||||
if role == SortOptions.Score:
|
||||
return self.repos[row].score
|
||||
|
||||
def headerData(self, _unused1, _unused2, _role=QtCore.Qt.DisplayRole):
|
||||
"""No header in this implementation: always returns None."""
|
||||
@@ -386,8 +386,8 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
time_string = QtCore.QLocale().toString(qdt, QtCore.QLocale.ShortFormat)
|
||||
return translate("AddonsInstaller", "Updated ") + time_string
|
||||
return ""
|
||||
elif self.sort_order == SortOptions.Rank:
|
||||
return translate("AddonsInstaller", "Rank: ") + str(len(addon.display_name))
|
||||
elif self.sort_order == SortOptions.Score:
|
||||
return translate("AddonsInstaller", "Score: ") + str(len(addon.display_name))
|
||||
return ""
|
||||
|
||||
def _set_sort_string_expanded(self, addon: Addon, label: QtWidgets.QLabel) -> None:
|
||||
|
||||
Reference in New Issue
Block a user