Example #1
0
    def get_cached_icon_filename(self) -> str:
        """Get the filename for the locally-cached copy of the icon"""

        if self.cached_icon_filename:
            return self.cached_icon_filename

        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

        real_icon = real_icon.replace(
            "/", os.path.sep
        )  # Required path separator in the metadata.xml file to local separator

        _, file_extension = os.path.splitext(real_icon)
        store = os.path.join(FreeCAD.getUserCachePath(), "AddonManager",
                             "PackageMetadata")
        self.cached_icon_filename = os.path.join(
            store, self.name, "cached_icon" + file_extension)

        return self.cached_icon_filename
Example #2
0
 def clean_icon(self):
     if self.icon.startswith("http://") or self.icon.startswith("https://"):
         FreeCAD.Console.PrintLog(
             f"Attempting to fetch macro icon from {self.icon}\n")
         p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(self.icon)
         if p:
             cache_path = FreeCAD.getUserCachePath()
             am_path = os.path.join(cache_path, "AddonManager",
                                    "MacroIcons")
             os.makedirs(am_path, exist_ok=True)
             _, _, filename = self.icon.rpartition("/")
             base, _, extension = filename.rpartition(".")
             if base.lower().startswith("file:"):
                 FreeCAD.Console.PrintMessage(
                     f"Cannot use specified icon for {self.name}, {self.icon} is not a direct download link\n"
                 )
                 self.icon = ""
             else:
                 constructed_name = os.path.join(am_path,
                                                 base + "." + extension)
                 with open(constructed_name, "wb") as f:
                     f.write(p.data())
                 self.icon_source = self.icon
                 self.icon = constructed_name
         else:
             FreeCAD.Console.PrintLog(
                 f"MACRO DEVELOPER WARNING: failed to download icon from {self.icon} for macro {self.name}\n"
             )
             self.icon = ""
Example #3
0
    def from_cache(self, cache_dict: Dict):
        """Load basic data from cached dict data. Does not include Macro or Metadata information, which must be populated separately."""

        mod_dir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod",
                               cache_dict["name"])
        if os.path.isdir(mod_dir):
            status = AddonManagerRepo.UpdateStatus.UNCHECKED
        else:
            status = AddonManagerRepo.UpdateStatus.NOT_INSTALLED
        instance = AddonManagerRepo(cache_dict["name"], cache_dict["url"],
                                    status, cache_dict["branch"])

        for key, value in cache_dict.items():
            instance.__dict__[key] = value

        instance.repo_type = AddonManagerRepo.RepoType(cache_dict["repo_type"])
        if instance.repo_type == AddonManagerRepo.RepoType.PACKAGE:
            # There must be a cached metadata file, too
            cached_package_xml_file = os.path.join(
                FreeCAD.getUserCachePath(),
                "AddonManager",
                "PackageMetadata",
                instance.name,
            )
            if os.path.isfile(cached_package_xml_file):
                instance.load_metadata_file(cached_package_xml_file)

        if "requires" in cache_dict:
            instance.requires = set(cache_dict["requires"])
            instance.blocks = set(cache_dict["blocks"])
            instance.python_requires = set(cache_dict["python_requires"])
            instance.python_optional = set(cache_dict["python_optional"])

        return instance
    def __init__(self, parent, repo: AddonManagerRepo, index: Dict[str, str]):
        """repo is an AddonManagerRepo object, and index is a dictionary of SHA1 hashes of the package.xml files in the cache"""

        super().__init__(parent, repo.metadata_url)
        self.repo = repo
        self.index = index
        self.store = os.path.join(FreeCAD.getUserCachePath(), "AddonManager",
                                  "PackageMetadata")
        self.last_sha1 = ""
    def __init__(self, repos):

        QtCore.QThread.__init__(self)
        self.repos = repos
        self.requests: Dict[int, (Addon, UpdateMetadataCacheWorker.RequestType)] = {}
        NetworkManager.AM_NETWORK_MANAGER.completed.connect(self.download_completed)
        self.requests_completed = 0
        self.total_requests = 0
        self.store = os.path.join(
            FreeCAD.getUserCachePath(), "AddonManager", "PackageMetadata"
        )
        self.updated_repos = set()
    def update_macro(self, repo: Addon):
        """Updating a macro happens in this function, in the current thread"""

        cache_path = os.path.join(
            FreeCAD.getUserCachePath(), "AddonManager", "MacroCache"
        )
        os.makedirs(cache_path, exist_ok=True)
        install_succeeded, _ = repo.macro.install(cache_path)

        if install_succeeded:
            install_succeeded, _ = repo.macro.install(FreeCAD.getUserMacroDir(True))
            utils.update_macro_installation_details(repo)

        if install_succeeded:
            self.success.emit(repo)
        else:
            self.failure.emit(repo)
Example #7
0
    def from_cache(self, data: Dict):
        """Load basic data from cached dict data. Does not include Macro or Metadata information, which must be populated separately."""

        mod_dir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod",
                               data["name"])
        if os.path.isdir(mod_dir):
            status = AddonManagerRepo.UpdateStatus.UNCHECKED
        else:
            status = AddonManagerRepo.UpdateStatus.NOT_INSTALLED
        instance = AddonManagerRepo(data["name"], data["url"], status,
                                    data["branch"])
        instance.display_name = data["display_name"]
        instance.repo_type = AddonManagerRepo.RepoType(data["repo_type"])
        instance.description = data["description"]
        instance.cached_icon_filename = data["cached_icon_filename"]
        if instance.repo_type == AddonManagerRepo.RepoType.PACKAGE:
            # There must be a cached metadata file, too
            cached_package_xml_file = os.path.join(FreeCAD.getUserCachePath(),
                                                   "AddonManager",
                                                   "PackageMetadata",
                                                   instance.name)
            if os.path.isfile(cached_package_xml_file):
                instance.load_metadata_file(cached_package_xml_file)
        return instance
Example #8
0
    def fill_details_from_wiki(self, url):
        code = ""
        p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url)
        if not p:
            FreeCAD.Console.PrintWarning(
                translate(
                    "AddonsInstaller",
                    "Unable to open macro wiki page at {}",
                ).format(url) + "\n")
            return
        p = p.data().decode("utf8")
        # check if the macro page has its code hosted elsewhere, download if
        # needed
        if "rawcodeurl" in p:
            rawcodeurl = re.findall('rawcodeurl.*?href="(http.*?)">', p)
            if rawcodeurl:
                rawcodeurl = rawcodeurl[0]
                u2 = NetworkManager.AM_NETWORK_MANAGER.blocking_get(rawcodeurl)
                if not u2:
                    FreeCAD.Console.PrintWarning(
                        translate(
                            "AddonsInstaller",
                            "Unable to open macro code URL {rawcodeurl}",
                        ).format(rawcodeurl) + "\n")
                    return
                code = u2.data().decode("utf8")
        if not code:
            code = re.findall(r"<pre>(.*?)</pre>", p.replace("\n", "--endl--"))
            if code:
                # take the biggest code block
                code = sorted(code, key=len)[-1]
                code = code.replace("--endl--", "\n")
                # Clean HTML escape codes.
                code = unescape(code)
                code = code.replace(b"\xc2\xa0".decode("utf-8"), " ")
            else:
                FreeCAD.Console.PrintWarning(
                    translate("AddonsInstaller",
                              "Unable to fetch the code of this macro.") +
                    "\n")

        desc = re.findall(
            r"<td class=\"ctEven left macro-description\">(.*?)</td>",
            p.replace("\n", " "),
        )
        if desc:
            desc = desc[0]
        else:
            FreeCAD.Console.PrintWarning(
                translate(
                    "AddonsInstaller",
                    "Unable to retrieve a description from the wiki for macro {}",
                ).format(self.name) + "\n")
            desc = "No description available"
        self.desc = desc
        self.comment, _, _ = desc.partition(
            "<br")  # Up to the first line break
        self.comment = re.sub("<.*?>", "", self.comment)  # Strip any tags
        self.url = url
        if isinstance(code, list):
            flat_code = ""
            for chunk in code:
                flat_code += chunk
            code = flat_code
        self.code = code
        self.fill_details_from_code(self.code)
        if not self.icon and not self.xpm:
            self.parse_wiki_page_for_icon(p)

        if not self.author:
            self.author = self.parse_desc("Author: ")
        if not self.date:
            self.date = self.parse_desc("Last modified: ")
        if self.icon.startswith("http://") or self.icon.startswith("https://"):
            FreeCAD.Console.PrintLog(
                f"Attempting to fetch macro icon from {self.icon}\n")
            p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(self.icon)
            if p:
                cache_path = FreeCAD.getUserCachePath()
                am_path = os.path.join(cache_path, "AddonManager",
                                       "MacroIcons")
                os.makedirs(am_path, exist_ok=True)
                _, _, filename = self.icon.rpartition("/")
                base, _, extension = filename.rpartition(".")
                if base.lower().startswith("file:"):
                    FreeCAD.Console.PrintMessage(
                        f"Cannot use specified icon for {self.name}, {self.icon} is not a direct download link\n"
                    )
                    self.icon = ""
                else:
                    constructed_name = os.path.join(am_path,
                                                    base + "." + extension)
                    with open(constructed_name, "wb") as f:
                        f.write(p.data())
                    self.icon_source = self.icon
                    self.icon = constructed_name
Example #9
0
class Addon:
    """Encapsulates information about a FreeCAD addon"""

    class Kind(IntEnum):
        """The type of Addon: Workbench, macro, or package"""

        WORKBENCH = 1
        MACRO = 2
        PACKAGE = 3

        def __str__(self) -> str:
            if self.value == 1:
                return "Workbench"
            if self.value == 2:
                return "Macro"
            if self.value == 3:
                return "Package"
            return "ERROR_TYPE"

    class Status(IntEnum):
        """The installation status of an Addon"""

        NOT_INSTALLED = 0
        UNCHECKED = 1
        NO_UPDATE_AVAILABLE = 2
        UPDATE_AVAILABLE = 3
        PENDING_RESTART = 4
        CANNOT_CHECK = 5  # If we don't have git, etc.

        def __lt__(self, other):
            if self.__class__ is other.__class__:
                return self.value < other.value
            return NotImplemented

        def __str__(self) -> str:
            result = ""
            if self.value == 0:
                result = "Not installed"
            elif self.value == 1:
                result = "Unchecked"
            elif self.value == 2:
                result = "No update available"
            elif self.value == 3:
                result = "Update available"
            elif self.value == 4:
                result = "Restart required"
            elif self.value == 5:
                result = "Can't check"
            else:
                result = "ERROR_STATUS"
            return result

    class Dependencies:
        """Addon dependency information"""

        def __init__(self):
            self.required_external_addons = []  # A list of Addons
            self.blockers = []  # A list of Addons
            self.replaces = []  # A list of Addons
            self.internal_workbenches: Set[str] = set()  # Required internal workbenches
            self.python_required: Set[str] = set()
            self.python_optional: Set[str] = set()

    class ResolutionFailed(RuntimeError):
        """An exception type for dependency resolution failure."""

    # The location of Addon Manager cache files: overridden by testing code
    cache_directory = os.path.join(FreeCAD.getUserCachePath(), "AddonManager")

    # 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):
        self.name = name.strip()
        self.display_name = self.name
        self.url = url.strip()
        self.branch = branch.strip()
        self.python2 = False
        self.obsolete = False
        self.rejected = False
        self.repo_type = Addon.Kind.WORKBENCH
        self.description = None
        self.tags = set()  # Just a cache, loaded from Metadata

        # 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)

        # The url should never end in ".git", so strip it if it's there
        parsed_url = urlparse(self.url)
        if parsed_url.path.endswith(".git"):
            self.url = (
                parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.path[:-4]
            )
            if parsed_url.query:
                self.url += "?" + parsed_url.query
            if parsed_url.fragment:
                self.url += "#" + parsed_url.fragment

        if utils.recognized_git_location(self):
            self.metadata_url = construct_git_url(self, "package.xml")
        else:
            self.metadata_url = None
        self.metadata = None
        self.icon = None
        self.cached_icon_filename = ""
        self.macro = None  # Bridge to Gaël Écorchard's macro management class
        self.updated_timestamp = None
        self.installed_version = None

        # Each repo is also a node in a directed dependency graph (referenced by name so
        # they cen be serialized):
        self.requires: Set[str] = set()
        self.blocks: Set[str] = set()

        # And maintains a list of required and optional Python dependencies from metadata.txt
        self.python_requires: Set[str] = set()
        self.python_optional: Set[str] = set()

    def __str__(self) -> str:
        result = f"FreeCAD {self.repo_type}\n"
        result += f"Name: {self.name}\n"
        result += f"URL: {self.url}\n"
        result += (
            "Has metadata\n" if self.metadata is not None else "No metadata found\n"
        )
        if self.macro is not None:
            result += "Has linked Macro object\n"
        return result

    @classmethod
    def from_macro(cls, macro: Macro):
        """Create an Addon object from a Macro wrapper object"""

        if macro.is_installed():
            status = Addon.Status.UNCHECKED
        else:
            status = Addon.Status.NOT_INSTALLED
        instance = Addon(macro.name, macro.url, status, "master")
        instance.macro = macro
        instance.repo_type = Addon.Kind.MACRO
        instance.description = macro.desc
        return instance

    @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."""

        mod_dir = os.path.join(cls.mod_directory, cache_dict["name"])
        if os.path.isdir(mod_dir):
            status = Addon.Status.UNCHECKED
        else:
            status = Addon.Status.NOT_INSTALLED
        instance = Addon(
            cache_dict["name"], cache_dict["url"], status, cache_dict["branch"]
        )

        for key, value in cache_dict.items():
            instance.__dict__[key] = value

        instance.repo_type = Addon.Kind(cache_dict["repo_type"])
        if instance.repo_type == Addon.Kind.PACKAGE:
            # There must be a cached metadata file, too
            cached_package_xml_file = os.path.join(
                instance.cache_directory,
                "PackageMetadata",
                instance.name,
            )
            if os.path.isfile(cached_package_xml_file):
                instance.load_metadata_file(cached_package_xml_file)

        if "requires" in cache_dict:
            instance.requires = set(cache_dict["requires"])
            instance.blocks = set(cache_dict["blocks"])
            instance.python_requires = set(cache_dict["python_requires"])
            instance.python_optional = set(cache_dict["python_optional"])

        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."""

        return {
            "name": self.name,
            "display_name": self.display_name,
            "url": self.url,
            "branch": self.branch,
            "repo_type": int(self.repo_type),
            "description": self.description,
            "cached_icon_filename": self.get_cached_icon_filename(),
            "python2": self.python2,
            "obsolete": self.obsolete,
            "rejected": self.rejected,
            "requires": list(self.requires),
            "blocks": list(self.blocks),
            "python_requires": list(self.python_requires),
            "python_optional": list(self.python_optional),
        }

    def load_metadata_file(self, file: str) -> None:
        """Read a given metadata file and set it as this object's metadata"""

        if os.path.exists(file):
            metadata = FreeCAD.Metadata(file)
            self.set_metadata(metadata)
        else:
            FreeCAD.Console.PrintLog(f"Internal error: {file} does not exist")

    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."""

        self.metadata = metadata
        self.display_name = metadata.Name
        self.repo_type = Addon.Kind.PACKAGE
        self.description = metadata.Description
        for url in metadata.Urls:
            if "type" in url and url["type"] == "repository":
                self.url = url["location"]
                if "branch" in url:
                    self.branch = url["branch"]
                else:
                    self.branch = "master"
        self.extract_tags(self.metadata)
        self.extract_metadata_dependencies(self.metadata)

    def version_is_ok(self, metadata) -> bool:
        """Checks to see if the current running version of FreeCAD meets the requirements set by
        the passed-in metadata parameter."""

        dep_fc_min = metadata.FreeCADMin
        dep_fc_max = metadata.FreeCADMax

        fc_major = int(FreeCAD.Version()[0])
        fc_minor = int(FreeCAD.Version()[1])

        try:
            if dep_fc_min and dep_fc_min != "0.0.0":
                required_version = dep_fc_min.split(".")
                if fc_major < int(required_version[0]):
                    return False  # Major version is too low
                if fc_major == int(required_version[0]):
                    if len(required_version) > 1 and fc_minor < int(
                        required_version[1]
                    ):
                        return False  # Same major, and minor is too low
        except ValueError:
            FreeCAD.Console.PrintMessage(
                f"Metadata file for {self.name} has invalid FreeCADMin version info\n"
            )

        try:
            if dep_fc_max and dep_fc_max != "0.0.0":
                required_version = dep_fc_max.split(".")
                if fc_major > int(required_version[0]):
                    return False  # Major version is too high
                if fc_major == int(required_version[0]):
                    if len(required_version) > 1 and fc_minor > int(
                        required_version[1]
                    ):
                        return False  # Same major, and minor is too high
        except ValueError:
            FreeCAD.Console.PrintMessage(
                f"Metadata file for {self.name} has invalid FreeCADMax version info\n"
            )

        return True

    def extract_metadata_dependencies(self, metadata):
        """Read dependency information from a metadata object and store it in this Addon"""

        # Version check: if this piece of metadata doesn't apply to this version of
        # FreeCAD, just skip it.
        if not self.version_is_ok(metadata):
            return

        for dep in metadata.Depend:
            # Simple version for now: eventually support all of the version params...
            self.requires.add(dep["package"])
            FreeCAD.Console.PrintLog(
                f"Package {self.name}: Adding dependency on {dep['package']}\n"
            )
        for dep in metadata.Conflict:
            self.blocks.add(dep["package"])

        # Recurse
        content = metadata.Content
        for _, value in content.items():
            for item in value:
                self.extract_metadata_dependencies(item)

    def verify_url_and_branch(self, url: str, branch: str) -> None:
        """Print diagnostic information for Addon Developers if their metadata is
        inconsistent with the actual fetch location. Most often this is due to using
        the wrong branch name."""

        if self.url != url:
            FreeCAD.Console.PrintWarning(
                translate(
                    "AddonsInstaller",
                    "Addon Developer Warning: Repository URL set in package.xml file for addon {} ({}) does not match the URL it was fetched from ({})",
                ).format(self.display_name, self.url, url)
                + "\n"
            )
        if self.branch != branch:
            FreeCAD.Console.PrintWarning(
                translate(
                    "AddonsInstaller",
                    "Addon Developer Warning: Repository branch set in package.xml file for addon {} ({}) does not match the branch it was fetched from ({})",
                ).format(self.display_name, self.branch, branch)
                + "\n"
            )

    def extract_tags(self, metadata: FreeCAD.Metadata) -> None:
        """Read the tags from the metadata object"""

        # Version check: if this piece of metadata doesn't apply to this version of
        # FreeCAD, just skip it.
        if not self.version_is_ok(metadata):
            return

        for new_tag in metadata.Tag:
            self.tags.add(new_tag)

        content = metadata.Content
        for _, value in content.items():
            for item in value:
                self.extract_tags(item)

    def contains_workbench(self) -> bool:
        """Determine if this package contains (or is) a workbench"""

        if self.repo_type == Addon.Kind.WORKBENCH:
            return True
        if self.repo_type == Addon.Kind.PACKAGE:
            if self.metadata is None:
                FreeCAD.Console.PrintLog(
                    f"Addon Manager internal error: lost metadata for package {self.name}\n"
                )
                return False
            content = self.metadata.Content
            if not content:
                FreeCAD.Console.PrintLog(
                    f"Package {self.display_name} does not list any content items in its package.xml metadata file.\n"
                )
                return False
            return "workbench" in content
        return False

    def contains_macro(self) -> bool:
        """Determine if this package contains (or is) a macro"""

        if self.repo_type == Addon.Kind.MACRO:
            return True
        if self.repo_type == Addon.Kind.PACKAGE:
            if self.metadata is None:
                FreeCAD.Console.PrintLog(
                    f"Addon Manager internal error: lost metadata for package {self.name}\n"
                )
                return False
            content = self.metadata.Content
            return "macro" in content
        return False

    def contains_preference_pack(self) -> bool:
        """Determine if this package contains a preference pack"""

        if self.repo_type == Addon.Kind.PACKAGE:
            if self.metadata is None:
                FreeCAD.Console.PrintLog(
                    f"Addon Manager internal error: lost metadata for package {self.name}\n"
                )
                return False
            content = self.metadata.Content
            return "preferencepack" in content
        return False

    def get_cached_icon_filename(self) -> str:
        """Get the filename for the locally-cached copy of the icon"""

        if self.cached_icon_filename:
            return self.cached_icon_filename

        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

        real_icon = real_icon.replace(
            "/", os.path.sep
        )  # Required path separator in the metadata.xml file to local separator

        _, file_extension = os.path.splitext(real_icon)
        store = os.path.join(self.cache_directory, "PackageMetadata")
        self.cached_icon_filename = os.path.join(
            store, self.name, "cached_icon" + file_extension
        )

        return self.cached_icon_filename

    def walk_dependency_tree(self, all_repos, deps):
        """Compute the total dependency tree for this repo (recursive)
        - all_repos is a dictionary of repos, keyed on the name of the repo
        - deps is an Addon.Dependency object encapsulating all the types of dependency
        information that may be needed.
        """

        deps.python_required |= self.python_requires
        deps.python_optional |= self.python_optional
        for dep in self.requires:
            if dep in all_repos:
                if not dep in deps.required_external_addons:
                    deps.required_external_addons.append(all_repos[dep])
                    all_repos[dep].walk_dependency_tree(all_repos, deps)
            else:
                # See if this is an internal workbench:
                if dep.upper().endswith("WB"):
                    real_name = dep[:-2].strip().lower()
                elif dep.upper().endswith("WORKBENCH"):
                    real_name = dep[:-9].strip().lower()
                else:
                    real_name = dep.strip().lower()

                if real_name in INTERNAL_WORKBENCHES:
                    deps.internal_workbenches.add(INTERNAL_WORKBENCHES[real_name])
                else:
                    # Assume it's a Python requirement of some kind:
                    deps.python_required.add(dep)

        for dep in self.blocks:
            if dep in all_repos:
                deps.blockers[dep] = all_repos[dep]

    def status(self):
        """Threadsafe access to the current update status"""
        with self.status_lock:
            return self.update_status

    def set_status(self, status):
        """Threadsafe setting of the update status"""
        with self.status_lock:
            self.update_status = status

    def is_disabled(self):
        """Check to see if the disabling stopfile exists"""

        stopfile = os.path.join(self.mod_directory, self.name, "ADDON_DISABLED")
        return os.path.exists(stopfile)

    def disable(self):
        """Disable this addon from loading when FreeCAD starts up by creating a stopfile"""

        stopfile = os.path.join(mod_directory, self.name, "ADDON_DISABLED")
        with open(stopfile, "w", encoding="utf-8") as f:
            f.write(
                "The existence of this file prevents FreeCAD from loading this Addon. To re-enable, delete the file."
            )

    def enable(self):
        """Re-enable loading this addon by deleting the stopfile"""

        stopfile = os.path.join(self.mod_directory, self.name, "ADDON_DISABLED")
        try:
            os.unlink(stopfile)
        except FileNotFoundError:
            pass
Example #10
0
 def cache_path(self, repo: AddonManagerRepo) -> str:
     cache_path = FreeCAD.getUserCachePath()
     full_path = os.path.join(cache_path, "AddonManager", repo.name)
     return full_path
Example #11
0
def get_cache_file_name(file: str) -> str:
    """Get the full path to a cache file with a given name."""
    cache_path = FreeCAD.getUserCachePath()
    am_path = os.path.join(cache_path, "AddonManager")
    os.makedirs(am_path, exist_ok=True)
    return os.path.join(am_path, file)
 def __init__(self, cache_file: str):
     QtCore.QThread.__init__(self)
     self.cache_file = cache_file
     self.metadata_cache_path = os.path.join(
         FreeCAD.getUserCachePath(), "AddonManager", "PackageMetadata"
     )