Addon Manager: Extract PySide QtCore interface

This commit is contained in:
Chris Hennes
2023-03-26 18:45:01 -05:00
parent 66a738ab1e
commit fcda1ffc25
6 changed files with 94 additions and 35 deletions

View File

@@ -26,6 +26,7 @@ SET(AddonManager_SRCS
addonmanager_macro.py
addonmanager_macro_parser.py
addonmanager_metadata.py
addonmanager_pyside_interface.py
addonmanager_update_all_gui.py
addonmanager_uninstaller.py
addonmanager_uninstaller_gui.py

View File

@@ -28,8 +28,8 @@ import subprocess
from typing import List
import addonmanager_freecad_interface as fci
from addonmanager_pyside_interface import QObject, Signal, is_interruption_requested
from PySide import QtCore
import addonmanager_utilities as utils
from addonmanager_installer import AddonInstaller, MacroInstaller
from Addon import Addon
@@ -37,14 +37,14 @@ from Addon import Addon
translate = fci.translate
class DependencyInstaller(QtCore.QObject):
class DependencyInstaller(QObject):
"""Install Python dependencies using pip. Intended to be instantiated and then moved into a
QThread: connect the run() function to the QThread's started() signal."""
no_python_exe = QtCore.Signal()
no_pip = QtCore.Signal(str) # Attempted command
failure = QtCore.Signal(str, str) # Short message, detailed message
finished = QtCore.Signal()
no_python_exe = Signal()
no_pip = Signal(str) # Attempted command
failure = Signal(str, str) # Short message, detailed message
finished = Signal()
def __init__(
self,
@@ -69,9 +69,9 @@ class DependencyInstaller(QtCore.QObject):
signal."""
if self._verify_pip():
if self.python_requires or self.python_optional:
if not QtCore.QThread.currentThread().isInterruptionRequested():
if not is_interruption_requested():
self._install_python_packages()
if not QtCore.QThread.currentThread().isInterruptionRequested():
if not is_interruption_requested():
self._install_addons()
self.finished.emit()
@@ -107,7 +107,7 @@ class DependencyInstaller(QtCore.QObject):
signal is emitted and the function exits without proceeding with any additional
installations."""
for pymod in self.python_requires:
if QtCore.QThread.currentThread().isInterruptionRequested():
if is_interruption_requested():
return False
try:
proc = self._run_pip(
@@ -136,7 +136,7 @@ class DependencyInstaller(QtCore.QObject):
"""Install the optional Python package dependencies. If any fail a message is printed to
the console, but installation of the others continues."""
for pymod in self.python_optional:
if QtCore.QThread.currentThread().isInterruptionRequested():
if is_interruption_requested():
return
try:
proc = self._run_pip(
@@ -179,7 +179,7 @@ class DependencyInstaller(QtCore.QObject):
def _install_addons(self):
for addon in self.addons:
if QtCore.QThread.currentThread().isInterruptionRequested():
if is_interruption_requested():
return
fci.Console.PrintMessage(
translate(

View File

@@ -31,11 +31,11 @@ import shutil
import subprocess
from typing import List, Optional
import time
import FreeCAD
import addonmanager_utilities as utils
import addonmanager_freecad_interface as fci
translate = FreeCAD.Qt.translate
translate = fci.translate
class NoGitFound(RuntimeError):
@@ -90,7 +90,7 @@ class GitManager:
self._synchronous_call_git(["pull"])
self._synchronous_call_git(["submodule", "update", "--init", "--recursive"])
except GitFailed as e:
FreeCAD.Console.PrintWarning(
fci.Console.PrintWarning(
translate(
"AddonsInstaller",
"Basic git update failed with the following message:",
@@ -98,7 +98,7 @@ class GitManager:
+ str(e)
+ "\n"
)
FreeCAD.Console.PrintWarning(
fci.Console.PrintWarning(
translate(
"AddonsInstaller",
"Backing up the original directory and re-cloning",
@@ -209,7 +209,7 @@ class GitManager:
try:
self.clone(remote, local_path)
except GitFailed as e:
FreeCAD.Console.PrintError(
fci.Console.PrintError(
translate(
"AddonsInstaller", "Failed to clone {} into {} using git"
).format(remote, local_path)
@@ -240,10 +240,10 @@ class GitManager:
if len(segments) == 3:
result = segments[1]
break
FreeCAD.Console.PrintWarning(
fci.Console.PrintWarning(
"Error parsing the results from git remote -v show:\n"
)
FreeCAD.Console.PrintWarning(line + "\n")
fci.Console.PrintWarning(line + "\n")
os.chdir(old_dir)
return result
@@ -320,10 +320,10 @@ class GitManager:
# A) The value of the GitExecutable user preference
# B) The executable located in the same bin directory as FreeCAD and called "git"
# C) The result of a shutil search for your system's "git" executable
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
prefs = fci.ParamGet("User parameter:BaseApp/Preferences/Addons")
git_exe = prefs.GetString("GitExecutable", "Not set")
if not git_exe or git_exe == "Not set" or not os.path.exists(git_exe):
fc_dir = FreeCAD.getHomePath()
fc_dir = fci.DataPaths().home_dir()
git_exe = os.path.join(fc_dir, "bin", "git")
if "Windows" in platform.system():
git_exe += ".exe"
@@ -361,7 +361,7 @@ def initialize_git() -> Optional[GitManager]:
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")
pref = fci.ParamGet("User parameter:BaseApp/Preferences/Addons")
disable_git = pref.GetBool("disableGit", False)
if not disable_git:
try:

View File

@@ -0,0 +1,59 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2022 FreeCAD Project Association *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
"""Wrap QtCore imports so that can be replaced when running outside of FreeCAD (e.g. for
unit tests, etc. Only provides wrappers for the things commonly used by the Addon
Manager."""
try:
from PySide import QtCore
QObject = QtCore.QObject
Signal = QtCore.Signal
def is_interruption_requested() -> bool:
return QtCore.QThread.currentThread().isInterruptionRequested()
except ImportError:
QObject = object
class Signal:
"""A purely synchronous signal. emit() does not use queued slots so cannot be
used across threads."""
def __init__(self, *args):
self.expected_types = args
self.connections = []
def connect(self, func):
self.connections.append(func)
def disconnect(self, func):
if func in self.connections:
self.connections.remove(func)
def emit(self, *args):
for connection in self.connections:
connection(args)
def is_interruption_requested() -> bool:
return False

View File

@@ -28,14 +28,13 @@ details. """
import os
from typing import List
import FreeCAD
from PySide import QtCore
import addonmanager_freecad_interface as fci
from addonmanager_pyside_interface import QObject, Signal
import addonmanager_utilities as utils
from Addon import Addon
translate = FreeCAD.Qt.translate
translate = fci.translate
# pylint: disable=too-few-public-methods
@@ -44,7 +43,7 @@ class InvalidAddon(RuntimeError):
"""Raised when an object that cannot be uninstalled is passed to the constructor"""
class AddonUninstaller(QtCore.QObject):
class AddonUninstaller(QObject):
"""The core, non-GUI uninstaller class for non-macro addons. Usually instantiated and moved to
its own thread, otherwise it will block the GUI (if the GUI is running) -- since all it does is
delete files this is not a huge problem, but in some cases the Addon might be quite large, and
@@ -59,7 +58,7 @@ class AddonUninstaller(QtCore.QObject):
addon_to_remove = MyAddon() # Some class with 'name' attribute
self.worker_thread = QtCore.QThread()
self.worker_thread = QThread()
self.uninstaller = AddonUninstaller(addon_to_remove)
self.uninstaller.moveToThread(self.worker_thread)
self.uninstaller.success.connect(self.removal_succeeded)
@@ -83,12 +82,12 @@ class AddonUninstaller(QtCore.QObject):
# Signals: success and failure
# Emitted when the installation process is complete. The object emitted is the object that the
# installation was requested for.
success = QtCore.Signal(object)
failure = QtCore.Signal(object, str)
success = Signal(object)
failure = Signal(object, str)
# Finished: regardless of the outcome, this is emitted when all work that is going to be done
# is done (i.e. whatever thread this is running in can quit).
finished = QtCore.Signal()
finished = Signal()
def __init__(self, addon: object):
"""Initialize the uninstaller."""
@@ -185,7 +184,7 @@ class AddonUninstaller(QtCore.QObject):
FreeCAD.Console.PrintWarning(str(e) + "\n")
class MacroUninstaller(QtCore.QObject):
class MacroUninstaller(QObject):
"""The core, non-GUI uninstaller class for macro addons. May be run directly on the GUI thread
if desired, since macros are intended to be relatively small and shouldn't have too many files
to delete. However, it is a QObject so may also be moved into a QThread -- see AddonUninstaller
@@ -200,12 +199,12 @@ class MacroUninstaller(QtCore.QObject):
# Signals: success and failure
# Emitted when the removal process is complete. The object emitted is the object that the
# removal was requested for.
success = QtCore.Signal(object)
failure = QtCore.Signal(object, str)
success = Signal(object)
failure = Signal(object, str)
# Finished: regardless of the outcome, this is emitted when all work that is going to be done
# is done (i.e. whatever thread this is running in can quit).
finished = QtCore.Signal()
finished = Signal()
def __init__(self, addon):
super().__init__()

View File

@@ -407,7 +407,7 @@ class PackageDetails(QtWidgets.QWidget):
def set_change_branch_button_state(self):
"""The change branch button is only available for installed Addons that have a .git directory
and in runs where the GitPython import is available."""
and in runs where the git is available."""
self.ui.buttonChangeBranch.hide()