Addon Manager: Refactoring and pylint cleanup

This commit is contained in:
Chris Hennes
2022-08-21 14:00:26 -05:00
parent ee162ff3a8
commit 46fc605fca
15 changed files with 386 additions and 232 deletions

View File

@@ -462,9 +462,7 @@ class Addon:
) # 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"
)
store = os.path.join(self.cache_directory, "PackageMetadata")
self.cached_icon_filename = os.path.join(
store, self.name, "cached_icon" + file_extension
)
@@ -517,17 +515,13 @@ class Addon:
def is_disabled(self):
"""Check to see if the disabling stopfile exists"""
stopfile = os.path.join(
self.mod_directory, self.name, "ADDON_DISABLED"
)
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"
)
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."
@@ -536,9 +530,7 @@ class Addon:
def enable(self):
"""Re-enable loading this addon by deleting the stopfile"""
stopfile = os.path.join(
self.mod_directory, self.name, "ADDON_DISABLED"
)
stopfile = os.path.join(self.mod_directory, self.name, "ADDON_DISABLED")
try:
os.unlink(stopfile)
except FileNotFoundError:

View File

@@ -702,7 +702,9 @@ class CommandAddonManager:
if not self.update_cache:
self.update_cache = True # Make sure to trigger the other cache updates, if the json file was missing
self.create_addon_list_worker = CreateAddonListWorker()
self.create_addon_list_worker.status_message.connect(self.show_information)
self.create_addon_list_worker.status_message.connect(
self.show_information
)
self.create_addon_list_worker.addon_repo.connect(self.add_addon_repo)
self.update_progress_bar(10, 100)
self.create_addon_list_worker.finished.connect(

View File

@@ -30,8 +30,6 @@ import time
from zipfile import ZipFile
import FreeCAD
from typing import Dict
from addonmanager_git import GitManager, NoGitFound, GitFailed
@@ -89,6 +87,7 @@ class TestGit(unittest.TestCase):
status = self.git.status(checkout_dir).strip()
expected_status = "## HEAD (no branch)"
self.assertEqual(status, expected_status)
self.assertEqual(os.getcwd(),self.cwd, "We should be left in the same CWD we started")
def test_update(self):

View File

@@ -25,9 +25,10 @@
import unittest
import FreeCAD
class TestGui(unittest.TestCase):
MODULE = 'test_gui' # file name without extension
MODULE = "test_gui" # file name without extension
def setUp(self):
pass
pass

View File

@@ -22,15 +22,190 @@
# * *
# ***************************************************************************
import unittest
import json
import os
import shutil
import stat
import tempfile
import unittest
import FreeCAD
from addonmanager_git import initialize_git
from PySide2 import QtCore
import NetworkManager
from Addon import Addon
from addonmanager_workers_startup import (
CreateAddonListWorker,
UpdateChecker,
)
from addonmanager_workers_installation import InstallWorkbenchWorker
class TestWorkersInstallation(unittest.TestCase):
MODULE = "test_workers_installation" # file name without extension
addon_list = (
[]
) # Cache at the class level so only the first test has to download it
def setUp(self):
pass
"""Set up the test"""
self.test_dir = os.path.join(
FreeCAD.getHomePath(), "Mod", "AddonManager", "AddonManagerTest", "data"
)
self.saved_mod_directory = Addon.mod_directory
self.saved_cache_directory = Addon.cache_directory
Addon.mod_directory = os.path.join(
tempfile.gettempdir(), "FreeCADTesting", "Mod"
)
Addon.cache_directory = os.path.join(
tempfile.gettempdir(), "FreeCADTesting", "Cache"
)
os.makedirs(Addon.mod_directory, mode=0o777, exist_ok=True)
os.makedirs(Addon.cache_directory, mode=0o777, exist_ok=True)
url = "https://api.github.com/zen"
NetworkManager.InitializeNetworkManager()
result = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url)
if result is None:
self.skipTest("No active internet connection detected")
self.macro_counter = 0
self.workbench_counter = 0
self.prefpack_counter = 0
self.addon_from_cache_counter = 0
self.macro_from_cache_counter = 0
self.package_cache = {}
self.macro_cache = []
self.package_cache_filename = os.path.join(
Addon.cache_directory, "packages.json"
)
self.macro_cache_filename = os.path.join(Addon.cache_directory, "macros.json")
if not TestWorkersInstallation.addon_list:
self._create_addon_list()
# Workbench: use the FreeCAD-Help workbench for testing purposes
self.help_addon = None
for addon in self.addon_list:
if addon.name == "Help":
self.help_addon = addon
break
if not self.help_addon:
print("Unable to locate the FreeCAD-Help addon to test with")
self.skipTest("No active internet connection detected")
# Store the user's preference for whether git is enabled or disabled
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
self.saved_git_disabled_status = pref.GetBool("disableGit", False)
def tearDown(self):
mod_dir = os.path.join(tempfile.gettempdir(), "FreeCADTesting", "Mod")
if os.path.exists(mod_dir):
self._rmdir(mod_dir)
macro_dir = os.path.join(tempfile.gettempdir(), "FreeCADTesting", "Mod")
if os.path.exists(macro_dir):
self._rmdir(macro_dir)
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetBool("disableGit", self.saved_git_disabled_status)
def test_workbench_installation(self):
addon_location = os.path.join(
tempfile.gettempdir(), "FreeCADTesting", "Mod", self.help_addon.name
)
worker = InstallWorkbenchWorker(self.help_addon, addon_location)
worker.run() # Synchronous call, blocks until complete
self.assertTrue(os.path.exists(addon_location))
self.assertTrue(os.path.exists(os.path.join(addon_location, "package.xml")))
def test_workbench_installation_git_disabled(self):
"""If the testing user has git enabled, also test the addon manager with git disabled"""
if self.saved_git_disabled_status:
self.skipTest("Git is disabled, this test is redundant")
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetBool("disableGit", True)
self.test_workbench_installation()
pref.SetBool("disableGit", False)
def test_workbench_update_checker(self):
git_manager = initialize_git()
if not git_manager:
return
# Workbench: use the FreeCAD-Help workbench for testing purposes
help_addon = None
for addon in self.addon_list:
if addon.name == "Help":
help_addon = addon
break
if not help_addon:
print("Unable to locate the FreeCAD-Help addon to test with")
return
addon_location = os.path.join(
tempfile.gettempdir(), "FreeCADTesting", "Mod", self.help_addon.name
)
worker = InstallWorkbenchWorker(addon, addon_location)
worker.run() # Synchronous call, blocks until complete
self.assertEqual(help_addon.status(), Addon.Status.PENDING_RESTART)
# Back up one revision
git_manager.reset(addon_location, ["--hard", "HEAD~1"])
# At this point the addon should be "out of date", checked out to one revision behind
# the most recent.
worker = UpdateChecker()
worker.override_mod_directory(
os.path.join(tempfile.gettempdir(), "FreeCADTesting", "Mod")
)
worker.check_workbench(help_addon) # Synchronous call
self.assertEqual(help_addon.status(), Addon.Status.UPDATE_AVAILABLE)
# Now try to "update" it (which is really done via the install worker)
worker = InstallWorkbenchWorker(addon, addon_location)
worker.run() # Synchronous call, blocks until complete
self.assertEqual(help_addon.status(), Addon.Status.PENDING_RESTART)
def _rmdir(self, path):
try:
shutil.rmtree(path, onerror=self._remove_readonly)
except Exception as e:
print(e)
def _remove_readonly(self, func, path, _) -> None:
"""Remove a read-only file."""
os.chmod(path, stat.S_IWRITE)
func(path)
def _create_addon_list(self):
"""Create the list of addons"""
worker = CreateAddonListWorker()
worker.addon_repo.connect(self._addon_added)
worker.start()
while worker.isRunning():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
def _addon_added(self, addon: Addon):
"""Callback for adding an Addon: tracks the list, and counts the various types"""
print(f"Addon added: {addon.name}")
TestWorkersInstallation.addon_list.append(addon)
if addon.contains_workbench():
self.workbench_counter += 1
if addon.contains_macro():
self.macro_counter += 1
if addon.contains_preference_pack():
self.prefpack_counter += 1

View File

@@ -27,11 +27,7 @@ import unittest
import os
import tempfile
have_git = True
try:
import git
except ImportError:
have_git = False
from addonmanager_git import initialize_git
import FreeCAD
@@ -44,24 +40,31 @@ from addonmanager_workers_startup import (
LoadPackagesFromCacheWorker,
LoadMacrosFromCacheWorker,
CheckSingleUpdateWorker,
)
)
from addonmanager_workers_installation import (
InstallWorkbenchWorker,
)
)
class TestWorkersStartup(unittest.TestCase):
MODULE = "test_workers_startup" # file name without extension
def setUp(self):
""" Set up the test """
self.test_dir = os.path.join(FreeCAD.getHomePath(), "Mod", "AddonManager", "AddonManagerTest", "data")
"""Set up the test"""
self.test_dir = os.path.join(
FreeCAD.getHomePath(), "Mod", "AddonManager", "AddonManagerTest", "data"
)
self.saved_mod_directory = Addon.mod_directory
self.saved_cache_directory = Addon.cache_directory
Addon.mod_directory = os.path.join(tempfile.gettempdir(),"FreeCADTesting","Mod")
Addon.cache_directory = os.path.join(tempfile.gettempdir(),"FreeCADTesting","Cache")
Addon.mod_directory = os.path.join(
tempfile.gettempdir(), "FreeCADTesting", "Mod"
)
Addon.cache_directory = os.path.join(
tempfile.gettempdir(), "FreeCADTesting", "Cache"
)
os.makedirs(Addon.mod_directory, mode=0o777, exist_ok=True)
os.makedirs(Addon.cache_directory, mode=0o777, exist_ok=True)
@@ -78,23 +81,29 @@ class TestWorkersStartup(unittest.TestCase):
self.prefpack_counter = 0
self.addon_from_cache_counter = 0
self.macro_from_cache_counter = 0
# Populated when the addon list is created in the first test
self.package_cache = {}
self.macro_cache = []
self.package_cache_filename = os.path.join(Addon.cache_directory,"packages.json")
self.macro_cache_filename = os.path.join(Addon.cache_directory,"macros.json")
self.package_cache_filename = os.path.join(
Addon.cache_directory, "packages.json"
)
self.macro_cache_filename = os.path.join(Addon.cache_directory, "macros.json")
# Store the user's preference for whether git is enabled or disabled
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
self.saved_git_disabled_status = pref.GetBool("disableGit", False)
def tearDown(self):
""" Tear down the test """
"""Tear down the test"""
Addon.mod_directory = self.saved_mod_directory
Addon.cache_directory = self.saved_cache_directory
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetBool("disableGit", self.saved_git_disabled_status)
def test_create_addon_list_worker(self):
""" Test whether any addons are added: runs the full query, so this potentially is a SLOW
test. Note that this test must be run before any of the other tests, so that the cache gets
created. """
"""Test whether any addons are added: runs the full query, so this potentially is a SLOW
test."""
worker = CreateAddonListWorker()
worker.addon_repo.connect(self._addon_added)
worker.start()
@@ -102,21 +111,94 @@ class TestWorkersStartup(unittest.TestCase):
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
self.assertGreater(self.macro_counter,0, "No macros returned")
self.assertGreater(self.workbench_counter,0, "No workbenches returned")
self.assertGreater(self.prefpack_counter,0, "No preference packs returned")
self.assertGreater(self.macro_counter, 0, "No macros returned")
self.assertGreater(self.workbench_counter, 0, "No workbenches returned")
# Make sure there are no duplicates:
addon_name_set = set()
for addon in self.addon_list:
addon_name_set.add(addon.name)
self.assertEqual(
len(addon_name_set), len(self.addon_list), "Duplicate names are not allowed"
)
# Write the cache data
if hasattr(self, "package_cache"):
with open(self.package_cache_filename,"w",encoding="utf-8") as f:
with open(self.package_cache_filename, "w", encoding="utf-8") as f:
f.write(json.dumps(self.package_cache, indent=" "))
if hasattr(self, "macro_cache"):
with open(self.macro_cache_filename,"w",encoding="utf-8") as f:
with open(self.macro_cache_filename, "w", encoding="utf-8") as f:
f.write(json.dumps(self.macro_cache, indent=" "))
def _addon_added(self, addon:Addon):
""" Callback for adding an Addon: tracks the list, and counts the various types """
print (f"Addon Test: {addon.name}")
original_macro_counter = self.macro_counter
original_workbench_counter = self.workbench_counter
original_addon_list = self.addon_list.copy()
self.macro_counter = 0
self.workbench_counter = 0
self.addon_list.clear()
# Now try loading the same data from the cache we just created
worker = LoadPackagesFromCacheWorker(self.package_cache_filename)
worker.override_metadata_cache_path(
os.path.join(Addon.cache_directory, "PackageMetadata")
)
worker.addon_repo.connect(self._addon_added)
worker.start()
while worker.isRunning():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
worker = LoadMacrosFromCacheWorker(self.macro_cache_filename)
worker.add_macro_signal.connect(self._addon_added)
worker.start()
while worker.isRunning():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
# Make sure that every addon in the original list is also in the new list
fail_counter = 0
for original_addon in original_addon_list:
found = False
for addon in self.addon_list:
if addon.name == original_addon.name:
found = True
break
if not found:
print(f"Failed to load {addon.name} from cache")
fail_counter += 1
self.assertEqual(fail_counter, 0)
# Make sure there are no duplicates:
addon_name_set.clear()
for addon in self.addon_list:
addon_name_set.add(addon.name)
self.assertEqual(len(addon_name_set), len(self.addon_list))
self.assertEqual(len(original_addon_list), len(self.addon_list))
self.assertEqual(
original_macro_counter,
self.macro_counter,
"Cache loaded a different number of macros",
)
# We can't check workbench and preference pack counting at this point, because that relies
# on the package.xml metadata file, which this test does not download.
def test_create_addon_list_git_disabled(self):
"""If the user has git enabled, also test the addon manager with git disabled"""
if self.saved_git_disabled_status:
self.skipTest("Git is disabled, this test is redundant")
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetBool("disableGit", True)
self.test_create_addon_list_worker()
def _addon_added(self, addon: Addon):
"""Callback for adding an Addon: tracks the list, and counts the various types"""
print(f"Addon added: {addon.name}")
self.addon_list.append(addon)
if addon.contains_workbench():
self.workbench_counter += 1
@@ -126,94 +208,7 @@ class TestWorkersStartup(unittest.TestCase):
self.prefpack_counter += 1
# Also record the information for cache purposes
self.package_cache[addon.name] = addon.to_cache()
if addon.macro is not None:
if addon.macro is None:
self.package_cache[addon.name] = addon.to_cache()
else:
self.macro_cache.append(addon.macro.to_cache())
def test_load_packages_from_cache_worker(self):
""" Test loading packages from the cache """
worker = LoadPackagesFromCacheWorker(self.package_cache_filename)
worker.override_metadata_cache_path(os.path.join(Addon.cache_directory,"PackageMetadata"))
worker.addon_repo.connect(self._addon_added_from_cache)
self.addon_from_cache_counter = 0
worker.start()
while worker.isRunning():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
self.assertGreater(self.addon_from_cache_counter,0, "No addons in the cache")
def _addon_added_from_cache(self, addon:Addon):
""" Callback when addon added from cache """
print (f"Addon Cache Test: {addon.name}")
self.addon_from_cache_counter += 1
def test_load_macros_from_cache_worker(self):
""" Test loading macros from the cache """
worker = LoadMacrosFromCacheWorker(self.macro_cache_filename)
worker.add_macro_signal.connect(self._macro_added_from_cache)
self.macro_from_cache_counter = 0
worker.start()
while worker.isRunning():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
self.assertGreater(self.macro_from_cache_counter,0, "No macros in the cache")
def _macro_added_from_cache(self, addon:Addon):
""" Callback for adding macros from the cache """
print (f"Macro Cache Test: {addon.name}")
self.macro_from_cache_counter += 1
def test_update_checker(self):
""" Test the code that checks a single addon for available updates. """
if not have_git:
return
# Populate the test's Addon List:
self.test_create_addon_list_worker()
# First, install a specific Addon of each kind into a temp location
location = os.path.join(tempfile.gettempdir(),"FreeCADTesting")
self._test_workbench_update_checker(location)
# Preference Pack
# Macro
# Arrange for those addons to be out-of-date
# Check for updates
def _test_workbench_update_checker(self, location):
# Workbench: use the FreeCAD-Help workbench for testing purposes
help_addon = None
for addon in self.addon_list:
if addon.name == "Help":
help_addon = addon
break
if not help_addon:
print("Unable to locate the FreeCAD-Help addon to test with")
return
addon_location = os.path.join(location, help_addon.name)
worker = InstallWorkbenchWorker(addon, addon_location)
worker.run() # Synchronous call, blocks until complete
gitrepo = git.Git(addon_location)
gitrepo.reset("--hard", "HEAD^")
print (addon_location)
# At this point the addon should be "out of date", checked out to one revision behind
# the most recent.
worker = CheckSingleUpdateWorker(help_addon)
worker.do_work() # Synchronous call
self.assertEqual(help_addon.status(), Addon.Status.UPDATE_AVAILABLE)

View File

@@ -30,12 +30,15 @@ from PySide2 import QtCore
import NetworkManager
class TestWorkersUtility(unittest.TestCase):
MODULE = "test_workers_utility" # file name without extension
def setUp(self):
self.test_dir = os.path.join(FreeCAD.getHomePath(), "Mod", "AddonManager", "AddonManagerTest", "data")
self.test_dir = os.path.join(
FreeCAD.getHomePath(), "Mod", "AddonManager", "AddonManagerTest", "data"
)
self.last_result = None
url = "https://api.github.com/zen"
@@ -45,7 +48,7 @@ class TestWorkersUtility(unittest.TestCase):
self.skipTest("No active internet connection detected")
def test_connection_checker_basic(self):
""" Tests the connection checking worker's basic operation: does not exit until worker thread completes """
"""Tests the connection checking worker's basic operation: does not exit until worker thread completes"""
worker = ConnectionChecker()
worker.success.connect(self.connection_succeeded)
worker.failure.connect(self.connection_failed)
@@ -54,8 +57,8 @@ class TestWorkersUtility(unittest.TestCase):
while worker.isRunning():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
self.assertEqual(self.last_result,"SUCCESS")
self.assertEqual(self.last_result, "SUCCESS")
def test_connection_checker_thread_interrupt(self):
worker = ConnectionChecker()
worker.success.connect(self.connection_succeeded)
@@ -66,8 +69,10 @@ class TestWorkersUtility(unittest.TestCase):
while worker.isRunning():
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents)
self.assertIsNone(self.last_result, "Requesting interruption of thread failed to interrupt")
self.assertIsNone(
self.last_result, "Requesting interruption of thread failed to interrupt"
)
def connection_succeeded(self):
self.last_result = "SUCCESS"

View File

@@ -156,7 +156,9 @@ if HAVE_QTNETWORK:
# Make sure we exit nicely on quit
if QtCore.QCoreApplication.instance() is not None:
QtCore.QCoreApplication.instance().aboutToQuit.connect(self.__aboutToQuit)
QtCore.QCoreApplication.instance().aboutToQuit.connect(
self.__aboutToQuit
)
# Create the QNAM on this thread:
self.QNAM = QtNetwork.QNetworkAccessManager()

View File

@@ -40,4 +40,4 @@ from AddonManagerTest.gui.test_workers_installation import (
False if AddonManagerTestGui.__name__ else True
False if AddonManagerTestWorkersUtility.__name__ else True
False if AddonManagerTestWorkersStartup.__name__ else True
False if AddonManagerTestWorkersInstallation.__name__ else True
False if AddonManagerTestWorkersInstallation.__name__ else True

View File

@@ -35,6 +35,23 @@ import FreeCAD
translate = FreeCAD.Qt.translate
def initialize_git() -> object:
"""If git is enabled, locate the git executable if necessary and return a new
GitManager object. The executable location is saved in user preferences for reuse,
and git can be disabled by setting the disableGit parameter in the Addons
preference group. Returns None if for any of those reasons we aren't using git."""
git_manager = None
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
disable_git = pref.GetBool("disableGit", False)
if not disable_git:
try:
git_manager = GitManager()
except NoGitFound:
pass
return git_manager
class NoGitFound(RuntimeError):
"""Could not locate the git executable on this system."""

View File

@@ -382,8 +382,9 @@ def get_python_exe() -> str:
prefs.SetString("PythonExecutableForPip", python_exe)
return python_exe
def get_cache_file_name(file: str) -> str:
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)
return os.path.join(am_path, file)

View File

@@ -49,17 +49,7 @@ import addonmanager_utilities as utils
from addonmanager_macro import Macro
from Addon import Addon
import NetworkManager
from addonmanager_git import GitManager, GitFailed, NoGitFound
git_manager = None
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
disable_git = pref.GetBool("disableGit", False)
if not disable_git:
try:
git_manager = GitManager()
except NoGitFound:
pass # A log messsage was already printed by the startup code
from addonmanager_git import initialize_git
translate = FreeCAD.Qt.translate
@@ -95,33 +85,26 @@ class InstallWorkbenchWorker(QtCore.QThread):
if not os.path.exists(self.clone_directory):
os.makedirs(self.clone_directory)
self.git_manager = initialize_git()
def run(self):
"installs or updates the selected addon"
if not self.repo:
return
if not git_manager:
if not self.git_manager:
FreeCAD.Console.PrintLog(
translate(
"AddonsInstaller",
"GitPython not found. Using ZIP file download instead.",
"Git disabled - using ZIP file download instead.",
)
+ "\n"
)
if not have_zip:
FreeCAD.Console.PrintError(
translate(
"AddonsInstaller",
"Your version of Python doesn't appear to support ZIP files. Unable to proceed.",
)
+ "\n"
)
return
target_dir = self.clone_directory
if git_manager:
if self.git_manager:
# Do the git process...
self.run_git(target_dir)
else:
@@ -135,6 +118,8 @@ class InstallWorkbenchWorker(QtCore.QThread):
return
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50)
self.repo.set_status(Addon.Status.PENDING_RESTART)
def update_status(self) -> None:
if hasattr(self, "git_progress") and self.isRunning():
self.progress_made.emit(self.git_progress.current, self.git_progress.total)
@@ -142,18 +127,16 @@ class InstallWorkbenchWorker(QtCore.QThread):
def run_git(self, clonedir: str) -> None:
if not git_manager:
if not self.git_manager:
FreeCAD.Console.PrintLog(
translate(
"AddonsInstaller",
"No Git Python installed, skipping git operations",
"Git disabled, skipping git operations",
)
+ "\n"
)
return
self.git_progress = GitProgressMonitor()
if os.path.exists(clonedir):
self.run_git_update(clonedir)
else:
@@ -163,9 +146,9 @@ class InstallWorkbenchWorker(QtCore.QThread):
self.status_message.emit("Updating module...")
with self.repo.git_lock:
if not os.path.exists(clonedir + os.sep + ".git"):
git_manager.repair(self.repo.url, clonedir)
self.git_manager.repair(self.repo.url, clonedir)
try:
git_manager.update(clonedir)
self.git_manager.update(clonedir)
if self.repo.contains_workbench():
answer = translate(
"AddonsInstaller",
@@ -207,7 +190,7 @@ class InstallWorkbenchWorker(QtCore.QThread):
with self.repo.git_lock:
FreeCAD.Console.PrintMessage("Lock acquired...\n")
git_manager.clone(self.repo.url, clonedir)
self.git_manager.clone(self.repo.url, clonedir)
FreeCAD.Console.PrintMessage("Initial clone complete...\n")
if current_thread.isInterruptionRequested():
return
@@ -706,30 +689,6 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
repo.cached_icon_filename = cache_file
if git_manager:
class GitProgressMonitor:
"""An object that receives git progress updates and stores them for later display"""
def __init__(self):
self.current = 0
self.total = 100
self.message = ""
def update(
self,
_: int,
cur_count: Union[str, float],
max_count: Union[str, float, None] = None,
message: str = "",
) -> None:
if max_count:
self.current = int(cur_count)
self.total = int(max_count)
if message:
self.message = message
class UpdateAllWorker(QtCore.QThread):
"""Update all listed packages, of any kind"""
@@ -804,9 +763,10 @@ class UpdateSingleWorker(QtCore.QThread):
success = QtCore.Signal(Addon)
failure = QtCore.Signal(Addon)
def __init__(self, repo_queue: queue.Queue):
def __init__(self, repo_queue: queue.Queue, location=None):
super().__init__()
self.repo_queue = repo_queue
self.location = location
def run(self):
current_thread = QtCore.QThread.currentThread()
@@ -858,7 +818,7 @@ class UpdateSingleWorker(QtCore.QThread):
def update_package(self, repo: Addon):
"""Updating a package re-uses the package installation worker, so actually spawns another thread that we block on"""
worker = InstallWorkbenchWorker(repo)
worker = InstallWorkbenchWorker(repo, location=self.location)
worker.success.connect(lambda repo, _: self.success.emit(repo))
worker.failure.connect(lambda repo, _: self.failure.emit(repo))
worker.start()

View File

@@ -40,21 +40,10 @@ import addonmanager_utilities as utils
from addonmanager_macro import Macro
from Addon import Addon
import NetworkManager
from addonmanager_git import GitManager, GitFailed, NoGitFound
from addonmanager_git import initialize_git
translate = FreeCAD.Qt.translate
git_manager = None
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
disable_git = pref.GetBool("disableGit", False)
if not disable_git:
try:
git_manager = GitManager()
except NoGitFound:
FreeCAD.Console.PrintWarning(
translate("AddonsInstaller", "Could not locate a suitable git executable")
)
# Workers only have one public method by design
# pylint: disable=too-few-public-methods
@@ -83,6 +72,8 @@ class CreateAddonListWorker(QtCore.QThread):
self.moddir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod")
self.current_thread = None
self.git_manager = initialize_git()
def run(self):
"populates the list of addons"
@@ -262,12 +253,12 @@ class CreateAddonListWorker(QtCore.QThread):
macro_cache_location = utils.get_cache_file_name("Macros")
if not git_manager:
if not self.git_manager:
message = translate(
"AddonsInstaller",
"Failed to execute git command: check installation of git",
"Git is disabled, skipping git macros",
)
self.status_message_signal.emit(message)
self.status_message.emit(message)
FreeCAD.Console.PrintWarning(message + "\n")
return
@@ -290,6 +281,12 @@ class CreateAddonListWorker(QtCore.QThread):
return
if filename.lower().endswith(".fcmacro"):
macro = Macro(filename[:-8]) # Remove ".FCMacro".
if macro.name in self.package_names:
FreeCAD.Console.PrintLog(
f"Ignoring second macro named {macro.name} (found on git)\n"
)
continue # We already have a macro with this name
self.package_names.append(macro.name)
macro.on_git = True
macro.src_filename = os.path.join(dirpath, filename)
macro.fill_details_from_file(macro.src_filename)
@@ -310,13 +307,13 @@ class CreateAddonListWorker(QtCore.QThread):
"Attempting to change non-git Macro setup to use git\n",
)
)
git_manager.repair(
self.git_manager.repair(
"https://github.com/FreeCAD/FreeCAD-macros.git",
macro_cache_location,
)
git_manager.update(macro_cache_location)
self.git_manager.update(macro_cache_location)
else:
git_manager.clone(
self.git_manager.clone(
"https://github.com/FreeCAD/FreeCAD-macros.git",
macro_cache_location,
)
@@ -335,7 +332,7 @@ class CreateAddonListWorker(QtCore.QThread):
)
try:
shutil.rmtree(macro_cache_location, onerror=self._remove_readonly)
git_manager.clone(
self.git_manager.clone(
"https://github.com/FreeCAD/FreeCAD-macros.git",
macro_cache_location,
)
@@ -394,6 +391,12 @@ class CreateAddonListWorker(QtCore.QThread):
):
macro_names.append(macname)
macro = Macro(macname)
if macro.name in self.package_names:
FreeCAD.Console.PrintLog(
f"Ignoring second macro named {macro.name} (found on wiki)\n"
)
continue # We already have a macro with this name
self.package_names.append(macro.name)
macro.on_wiki = True
macro.parsed = False
repo = Addon.from_macro(macro)
@@ -548,6 +551,7 @@ class UpdateChecker:
def __init__(self):
self.basedir = FreeCAD.getUserAppDataDir()
self.moddir = os.path.join(self.basedir, "Mod")
self.git_manager = initialize_git()
def override_mod_directory(self, moddir):
"""Primarily for use when testing, sets an alternate directory to use for mods"""
@@ -556,7 +560,7 @@ class UpdateChecker:
def check_workbench(self, wb):
"""Given a workbench Addon wb, check it for updates using git. If git is not
available, does nothing."""
if not git_manager:
if not self.git_manager:
wb.set_status(Addon.Status.CANNOT_CHECK)
return
clonedir = os.path.join(self.moddir, wb.name)
@@ -564,15 +568,15 @@ class UpdateChecker:
# mark as already installed AND already checked for updates
if not os.path.exists(os.path.join(clonedir, ".git")):
with wb.git_lock:
git_manager.repair(wb.url, clonedir)
self.git_manager.repair(wb.url, clonedir)
with wb.git_lock:
try:
status = git_manager.status(clonedir)
if "(no branch)" in git_manager.status(clonedir):
status = self.git_manager.status(clonedir)
if "(no branch)" in self.git_manager.status(clonedir):
# By definition, in a detached-head state we cannot
# update, so don't even bother checking.
wb.set_status(Addon.Status.NO_UPDATE_AVAILABLE)
wb.branch = git_manager.current_branch(clonedir)
wb.branch = self.git_manager.current_branch(clonedir)
return
except GitFailed as e:
FreeCAD.Console.PrintWarning(
@@ -587,7 +591,7 @@ class UpdateChecker:
wb.set_status(Addon.Status.CANNOT_CHECK)
else:
try:
if git_manager.update_available(clonedir):
if self.git_manager.update_available(clonedir):
wb.set_status(Addon.Status.UPDATE_AVAILABLE)
else:
wb.set_status(Addon.Status.NO_UPDATE_AVAILABLE)
@@ -609,7 +613,7 @@ class UpdateChecker:
if os.path.exists(clonedir):
# First, try to just do a git-based update, which will give the most accurate results:
if git_manager:
if self.git_manager:
self.check_workbench(package)
if package.status() != Addon.Status.CANNOT_CHECK:
# It worked, just exit now

View File

@@ -27,6 +27,7 @@ import FreeCAD
from PySide2 import QtCore
import NetworkManager
class ConnectionChecker(QtCore.QThread):
"""A worker thread for checking the connection to GitHub as a proxy for overall
network connectivity. It has two signals: success() and failure(str). The failure
@@ -59,8 +60,8 @@ class ConnectionChecker(QtCore.QThread):
self.success.emit()
def check_network_connection(self) -> Optional[str]:
""" The main work of this object: returns the decoded result of the connection request, or
None if the request failed """
"""The main work of this object: returns the decoded result of the connection request, or
None if the request failed"""
url = "https://api.github.com/zen"
result = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url)
if result:

View File

@@ -170,7 +170,7 @@ class PackageDetails(QWidget):
self.display_repo_status(self.repo.update_status)
def display_repo_status(self, status):
""" Updates the contents of the widget to display the current install status of the widget. """
"""Updates the contents of the widget to display the current install status of the widget."""
repo = self.repo
self.set_change_branch_button_state()
self.set_disable_button_state()
@@ -599,7 +599,7 @@ class PackageDetails(QWidget):
self.ui.progressBar.setValue(progress)
def load_finished(self, load_succeeded: bool):
""" Once loading is complete, update the display of the progress bar and loading widget. """
"""Once loading is complete, update the display of the progress bar and loading widget."""
self.ui.loadingLabel.hide()
self.ui.slowLoadLabel.hide()
self.ui.webView.show()