Addon Manager: Prep Addon for refactoring

This commit is contained in:
Chris Hennes
2023-02-10 13:56:48 -08:00
committed by Chris Hennes
parent 2aba1be25e
commit 64c03a0603

View File

@@ -1,22 +1,22 @@
# ***************************************************************************
# * *
# * Copyright (c) 2022 FreeCAD Project Association *
# * Copyright (c) 2022-2023 FreeCAD Project Association *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * This file is part of FreeCAD. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
@@ -26,7 +26,7 @@ import os
from urllib.parse import urlparse
from typing import Dict, Set, List
from threading import Lock
from enum import IntEnum
from enum import IntEnum, auto
import FreeCAD
@@ -39,22 +39,23 @@ from addonmanager_utilities import construct_git_url
translate = FreeCAD.Qt.translate
INTERNAL_WORKBENCHES = {}
INTERNAL_WORKBENCHES["arch"] = "Arch"
INTERNAL_WORKBENCHES["draft"] = "Draft"
INTERNAL_WORKBENCHES["fem"] = "FEM"
INTERNAL_WORKBENCHES["mesh"] = "Mesh"
INTERNAL_WORKBENCHES["openscad"] = "OpenSCAD"
INTERNAL_WORKBENCHES["part"] = "Part"
INTERNAL_WORKBENCHES["partdesign"] = "PartDesign"
INTERNAL_WORKBENCHES["path"] = "Path"
INTERNAL_WORKBENCHES["plot"] = "Plot"
INTERNAL_WORKBENCHES["points"] = "Points"
INTERNAL_WORKBENCHES["raytracing"] = "Raytracing"
INTERNAL_WORKBENCHES["robot"] = "Robot"
INTERNAL_WORKBENCHES["sketcher"] = "Sketcher"
INTERNAL_WORKBENCHES["spreadsheet"] = "Spreadsheet"
INTERNAL_WORKBENCHES["techdraw"] = "TechDraw"
INTERNAL_WORKBENCHES = {
"arch": "Arch",
"draft": "Draft",
"fem": "FEM",
"mesh": "Mesh",
"openscad": "OpenSCAD",
"part": "Part",
"partdesign": "PartDesign",
"path": "Path",
"plot": "Plot",
"points": "Points",
"raytracing": "Raytracing",
"robot": "Robot",
"sketcher": "Sketcher",
"spreadsheet": "Spreadsheet",
"techdraw": "TechDraw",
}
class Addon:
@@ -85,6 +86,7 @@ class Addon:
UPDATE_AVAILABLE = 3
PENDING_RESTART = 4
CANNOT_CHECK = 5 # If we don't have git, etc.
UNKNOWN = 100
def __lt__(self, other):
if self.__class__ is other.__class__:
@@ -92,7 +94,6 @@ class Addon:
return NotImplemented
def __str__(self) -> str:
result = ""
if self.value == 0:
result = "Not installed"
elif self.value == 1:
@@ -121,6 +122,16 @@ class Addon:
self.python_optional: Set[str] = set()
self.python_min_version = {"major": 3, "minor": 0}
class DependencyType(IntEnum):
"""Several types of dependency information is stored"""
INTERNAL_WORKBENCH = auto()
REQUIRED_ADDON = auto()
BLOCKED_ADDON = auto()
REPLACED_ADDON = auto()
REQUIRED_PYTHON = auto()
OPTIONAL_PYTHON = auto()
class ResolutionFailed(RuntimeError):
"""An exception type for dependency resolution failure."""
@@ -130,7 +141,13 @@ class Addon:
# The location of the Mod directory: overridden by testing code
mod_directory = os.path.join(FreeCAD.getUserAppDataDir(), "Mod")
def __init__(self, name: str, url: str, status: Status, branch: str):
def __init__(
self,
name: str,
url: str = "",
status: Status = Status.UNKNOWN,
branch: str = "",
):
self.name = name.strip()
self.display_name = self.name
self.url = url.strip()
@@ -141,13 +158,14 @@ class Addon:
self.repo_type = Addon.Kind.WORKBENCH
self.description = None
self.tags = set() # Just a cache, loaded from Metadata
self.last_updated = None
# To prevent multiple threads from running git actions on this repo at the same time
self.git_lock = Lock()
# To prevent multiple threads from accessing the status at the same time
self.status_lock = Lock()
self.set_status(status)
self.update_status = status
# The url should never end in ".git", so strip it if it's there
parsed_url = urlparse(self.url)
@@ -165,8 +183,9 @@ class Addon:
else:
self.metadata_url = None
self.metadata = None
self.icon = None
self.cached_icon_filename = ""
self.icon = None # Relative path to remote icon file
self.icon_file: str = "" # Absolute local path to cached icon file
self.best_icon_relative_path = ""
self.macro = None # Bridge to Gaël Écorchard's macro management class
self.updated_timestamp = None
self.installed_version = None
@@ -181,6 +200,8 @@ class Addon:
self.python_optional: Set[str] = set()
self.python_min_version = {"major": 3, "minor": 0}
self._icon_file = None
def __str__(self) -> str:
result = f"FreeCAD {self.repo_type}\n"
result += f"Name: {self.name}\n"
@@ -208,7 +229,8 @@ class Addon:
@classmethod
def from_cache(cls, cache_dict: Dict):
"""Load basic data from cached dict data. Does not include Macro or Metadata information, which must be populated separately."""
"""Load basic data from cached dict data. Does not include Macro or Metadata
information, which must be populated separately."""
mod_dir = os.path.join(cls.mod_directory, cache_dict["name"])
if os.path.isdir(mod_dir):
@@ -242,7 +264,8 @@ class Addon:
return instance
def to_cache(self) -> Dict:
"""Returns a dictionary with cache information that can be used later with from_cache to recreate this object."""
"""Returns a dictionary with cache information that can be used later with
from_cache to recreate this object."""
return {
"name": self.name,
@@ -252,6 +275,7 @@ class Addon:
"repo_type": int(self.repo_type),
"description": self.description,
"cached_icon_filename": self.get_cached_icon_filename(),
"best_icon_relative_path": self.get_best_icon_relative_path(),
"python2": self.python2,
"obsolete": self.obsolete,
"rejected": self.rejected,
@@ -272,7 +296,8 @@ class Addon:
def set_metadata(self, metadata: FreeCAD.Metadata) -> None:
"""Set the given metadata object as this object's metadata, updating the object's display name
and package type information to match, as well as updating any dependency information, etc."""
and package type information to match, as well as updating any dependency information, etc.
"""
self.metadata = metadata
self.display_name = metadata.Name
@@ -345,7 +370,8 @@ class Addon:
self.python_min_version["major"] = int(split_version_string[0])
self.python_min_version["minor"] = int(split_version_string[1])
FreeCAD.Console.PrintLog(
f"Package {self.name}: Requires Python {split_version_string[0]}.{split_version_string[1]} or greater\n"
f"Package {self.name}: Requires Python "
f"{split_version_string[0]}.{split_version_string[1]} or greater\n"
)
except ValueError:
FreeCAD.Console.PrintWarning(
@@ -473,10 +499,38 @@ class Addon:
return "preferencepack" in content
return False
def get_cached_icon_filename(self) -> str:
"""Get the filename for the locally-cached copy of the icon"""
def get_best_icon_relative_path(self) -> str:
"""Get the path within the repo the addon's icon. Usually specified by top-level metadata,
but some authors omit it and specify only icons for the contents. Find the first one of
those, in such cases."""
if self.cached_icon_filename:
if self.best_icon_relative_path:
return self.best_icon_relative_path
if not self.metadata:
return ""
real_icon = self.metadata.Icon
if not real_icon:
# If there is no icon set for the entire package, see if there are any workbenches, which
# are required to have icons, and grab the first one we find:
content = self.metadata.Content
if "workbench" in content:
wb = content["workbench"][0]
if wb.Icon:
if wb.Subdirectory:
subdir = wb.Subdirectory
else:
subdir = wb.Name
real_icon = subdir + wb.Icon
self.best_icon_relative_path = real_icon
return self.best_icon_relative_path
def get_cached_icon_filename(self) -> str:
"""NOTE: This function is deprecated and will be removed in a coming update."""
if hasattr(self, "cached_icon_filename") and self.cached_icon_filename:
return self.cached_icon_filename
if not self.metadata:
@@ -530,7 +584,7 @@ class Addon:
for dep in self.requires:
if dep in all_repos:
if not dep in deps.required_external_addons:
if dep not in deps.required_external_addons:
deps.required_external_addons.append(all_repos[dep])
all_repos[dep].walk_dependency_tree(all_repos, deps)
else:
@@ -597,7 +651,6 @@ class MissingDependencies:
"""
def __init__(self, repo: Addon, all_repos: List[Addon]):
deps = Addon.Dependencies()
repo_name_dict = {}
for r in all_repos: