Addon Manager: Refactoring and pylint cleanup
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user