Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from addonmanager_metadata import (
Metadata,
MetadataReader,
License,
UrlType,
Version,
DependencyType,
Expand Down Expand Up @@ -220,7 +221,7 @@ def __init__(
self.python_min_version = Version(from_list=[3, 0, 0])

self._icon_file = None
self._cached_license: str = ""
self._cached_license: str | List[License | str] = ""
self._cached_update_date = None

def __eq__(self, other):
Expand Down
20 changes: 18 additions & 2 deletions Widgets/addonmanager_widget_package_details_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
# ***************************************************************************

from dataclasses import dataclass
from typing import Optional, Dict
from enum import Enum, auto
import os
from typing import Optional

from addonmanager_freecad_interface import translate
from addonmanager_readme_controller import TabView

from PySideWrapper import QtCore, QtWidgets

Expand Down Expand Up @@ -71,6 +72,7 @@ class PackageDetailsView(QtWidgets.QWidget):
def __init__(self, parent: QtWidgets.QWidget = None):
super().__init__(parent)
self.button_bar = None
self.license_browser = None
self.readme_browser = None
self.message_label = None
self.location_label = None
Expand All @@ -83,12 +85,26 @@ def __init__(self, parent: QtWidgets.QWidget = None):
self.installed_branch = None
self.installed_timestamp = None
self.can_disable = True
self.tabs: Dict[TabView, int] = {}
self._setup_ui()

def _setup_ui(self):
self.vertical_layout = QtWidgets.QVBoxLayout(self)
self.button_bar = WidgetAddonButtons(self)

self.license_browser = WidgetReadmeBrowser(self)
self.readme_browser = WidgetReadmeBrowser(self)

self.tabs_widget = QtWidgets.QTabWidget(self)

self.tabs[TabView.Readme] = self.tabs_widget.addTab(
self.readme_browser, translate("AddonsInstaller", "Overview")
)

self.tabs[TabView.License] = self.tabs_widget.addTab(
self.license_browser, translate("AddonsInstaller", "License")
)

self.message_label = QtWidgets.QLabel(self)
self.location_label = QtWidgets.QLabel(self)
self.url_label = QtWidgets.QLabel(self)
Expand All @@ -98,7 +114,7 @@ def _setup_ui(self):
self.vertical_layout.addWidget(self.message_label)
self.vertical_layout.addWidget(self.location_label)
self.vertical_layout.addWidget(self.url_label)
self.vertical_layout.addWidget(self.readme_browser)
self.vertical_layout.addWidget(self.tabs_widget)
self.button_bar.hide() # Start with no bar

def set_location(self, location: Optional[str]):
Expand Down
27 changes: 23 additions & 4 deletions addonmanager_package_details_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@
get_branch_from_metadata,
get_repo_url_from_metadata,
)
from Widgets.addonmanager_widget_package_details_view import (
PackageDetailsView,
UpdateInformation,
WarningFlags,
)
from addonmanager_readme_controller import ReadmeController, TabView
from addonmanager_workers_startup import CheckSingleUpdateWorker
from addonmanager_git import GitManager, NoGitFound
from Addon import Addon
from addonmanager_readme_controller import ReadmeController
from Widgets.addonmanager_widget_package_details_view import UpdateInformation, WarningFlags

translate = fci.translate

Expand All @@ -52,10 +56,13 @@ class PackageDetailsController(QtCore.QObject):
execute = QtCore.Signal(Addon)
update_status = QtCore.Signal(Addon)

def __init__(self, widget=None):
def __init__(self, widget: PackageDetailsView):
super().__init__()
self.ui = widget

self.license_controller = ReadmeController(self.ui.license_browser)
self.readme_controller = ReadmeController(self.ui.readme_browser)

self.worker = None
self.addon = None
self.update_check_thread = None
Expand All @@ -79,9 +86,21 @@ def __init__(self, widget=None):
def show_addon(self, addon: Addon) -> None:
"""The main entry point for this class shows the package details and related buttons
for the provided repo."""

self.addon = addon
self.readme_controller.set_addon(addon)

has_license = bool(self.addon.license)

self.ui.tabs_widget.setTabVisible(self.ui.tabs[TabView.License], has_license)

if has_license:
self.license_controller.set_addon(addon, TabView.License)

self.readme_controller.set_addon(addon, TabView.Readme)
self.ui.tabs_widget.setCurrentIndex(TabView.Readme)

self.original_disabled_state = self.addon.is_disabled()

if addon is not None:
self.ui.button_bar.show()
if addon.repo_type == Addon.Kind.MACRO:
Expand Down
195 changes: 146 additions & 49 deletions addonmanager_readme_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
import addonmanager_utilities as utils
import addonmanager_freecad_interface as fci

from enum import IntEnum
from typing import Optional
from typing import TypedDict, Optional, Dict
from enum import IntEnum, auto

import NetworkManager
from addonmanager_metadata import UrlType
from Widgets.addonmanager_widget_readme_browser import WidgetReadmeBrowser
from addonmanager_metadata import UrlType, License
from PySideWrapper import QtWidgets

translate = fci.translate

Expand All @@ -44,17 +46,31 @@ class ReadmeDataType(IntEnum):
Html = 2


class TabView(IntEnum):
License = auto()
Readme = 0


class LicenseRequest(TypedDict):
license: License
text: QtWidgets.QTextEdit


class ReadmeController(QtCore.QObject):
"""A class that can provide README data from an Addon, possibly loading external resources such
as images"""

def __init__(self, widget):
def __init__(self, widget: WidgetReadmeBrowser):
super().__init__()
NetworkManager.InitializeNetworkManager()
NetworkManager.AM_NETWORK_MANAGER.completed.connect(self._download_completed)
self.readme_request_index = 0

self.resource_requests = {}
self.resource_failures = []

self.license_requests: Dict[int, LicenseRequest] = {}

self.url = ""
self.readme_data = None
self.readme_data_type = None
Expand All @@ -64,16 +80,16 @@ def __init__(self, widget):
self.widget.load_resource.connect(self.loadResource)
self.widget.follow_link.connect(self.follow_link)

def set_addon(self, repo: Addon):
def set_addon(self, addon: Addon, view: TabView):
"""Set which Addon's information is displayed"""

self.addon = repo
self.addon = addon
self.stop = False
self.readme_data = None
if self.addon.repo_type == Addon.Kind.MACRO:
self._create_wiki_display()
else:
self._create_non_wiki_display()
self._create_non_wiki_display(view)

def _download_completed(self, index: int, code: int, data: QtCore.QByteArray) -> None:
"""Callback for handling a completed README file download."""
Expand Down Expand Up @@ -103,7 +119,29 @@ def _download_completed(self, index: int, code: int, data: QtCore.QByteArray) ->
else:
self.widget.setText(self.readme_data)
else:
self.set_addon(self.addon) # Trigger a reload of the page now with resources
self.set_addon(
self.addon, TabView.Readme
) # Trigger a reload of the page now with resources

elif index in self.license_requests:

entry = self.license_requests.get(index)

if not entry:
return

text: None | str = None

if code == 200:
text = data.data().decode("utf-8")
else:
fci.Console.PrintLog(
f'Failed to fetch license. Name : "{ entry["license"].name }" , File : "{ entry["license"].file }"'
)

text = text or entry["license"].name

entry["text"].setText(text)

def _process_package_download(self, data: str):
self.readme_data = data
Expand All @@ -114,6 +152,16 @@ def _process_resource_download(self, resource_name: str, resource_data: bytes):
image = QtGui.QImage.fromData(resource_data)
self.widget.set_resource(resource_name, image)

def loadLicense(self, license: License, text: QtWidgets.QTextEdit):

url = utils.construct_git_url(self.addon, license.file)

manager = NetworkManager.AM_NETWORK_MANAGER

index = manager.submit_unmonitored_get(url)

self.license_requests[index] = {"license": license, "text": text}

def loadResource(self, full_url: str):
if full_url not in self.resource_failures:
index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(full_url)
Expand Down Expand Up @@ -182,47 +230,96 @@ def _create_wiki_display(self):
self.readme_data_type = ReadmeDataType.Markdown
self.widget.setMarkdown(markdown)

def _create_non_wiki_display(self):
self.url = utils.get_readme_url(self.addon)
if self.addon.metadata and self.addon.metadata.url:
for url in self.addon.metadata.url:
if url.type == UrlType.readme:
if self.url != url.location:
fci.Console.PrintLog("README url does not match expected location\n")
fci.Console.PrintLog(f"Expected: {self.url}\n")
fci.Console.PrintLog(f"package.xml contents: {url.location}\n")
fci.Console.PrintLog(
"Note to addon devs: package.xml now expects a"
" url to the raw MD data, now that Qt can render"
" it without having it transformed to HTML.\n"
)
self.url = url.location
if "/blob/" in self.url:
fci.Console.PrintLog("Attempting to replace 'blob' with 'raw'...\n")
self.url = self.url.replace("/blob/", "/raw/")
elif "/src/" in self.url and "codeberg" in self.url:
fci.Console.PrintLog(
"Attempting to replace 'src' with 'raw' in codeberg URL..."
)
self.url = self.url.replace("/src/", "/raw/")

self.widget.setUrl(self.url)

self.widget.setText(
translate("AddonsInstaller", "Loading page for {} from {}...").format(
self.addon.display_name, self.url
)
)
def _create_non_wiki_display(self, view: TabView):

if self.url[0] == "/":
if self.url.lower().endswith(".md"):
self.readme_data_type = ReadmeDataType.Markdown
elif self.url.lower().endswith(".html"):
self.readme_data_type = ReadmeDataType.Html
if view == TabView.License:

with open(self.url, "r") as fd:
self._process_package_download("".join(fd.readlines()))
else:
self.readme_request_index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(
self.url
addon = self.addon

if not addon:
return

licenses = addon.license

layout = self.widget.layout()

if not layout:

layout = QtWidgets.QVBoxLayout()

self.widget.setLayout(layout)

while layout.count():

item = layout.takeAt(0)
widget = item.widget()
widget.setParent(None)

if type(licenses) is list:

for license in licenses:

text = QtWidgets.QTextEdit()

text.setText(translate("AddonsInstaller", "Loading license.."))

layout.addWidget(text)

if type(license) is License:
self.loadLicense(license, text)
elif type(license) is str:
text.setText(license)

elif type(licenses) is str:

browser = QtWidgets.QTextBrowser()
browser.setText(licenses)

layout.addWidget(browser)

elif view == TabView.Readme:

self.url = utils.get_readme_url(self.addon)

if self.addon.metadata and self.addon.metadata.url:
for url in self.addon.metadata.url:
if url.type == UrlType.readme:
if self.url != url.location:
fci.Console.PrintLog("README url does not match expected location\n")
fci.Console.PrintLog(f"Expected: {self.url}\n")
fci.Console.PrintLog(f"package.xml contents: {url.location}\n")
fci.Console.PrintLog(
"Note to addon devs: package.xml now expects a"
" url to the raw MD data, now that Qt can render"
" it without having it transformed to HTML.\n"
)
self.url = url.location
if "/blob/" in self.url:
fci.Console.PrintLog("Attempting to replace 'blob' with 'raw'...\n")
self.url = self.url.replace("/blob/", "/raw/")
elif "/src/" in self.url and "codeberg" in self.url:
fci.Console.PrintLog(
"Attempting to replace 'src' with 'raw' in codeberg URL..."
)
self.url = self.url.replace("/src/", "/raw/")

self.widget.setUrl(self.url)

self.widget.setText(
translate("AddonsInstaller", "Loading page for {} from {}...").format(
self.addon.display_name, self.url
)
)

if self.url[0] == "/":
if self.url.lower().endswith(".md"):
self.readme_data_type = ReadmeDataType.Markdown
elif self.url.lower().endswith(".html"):
self.readme_data_type = ReadmeDataType.Html

with open(self.url, "r") as fd:
self._process_package_download("".join(fd.readlines()))
else:
self.readme_request_index = (
NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(self.url)
)