Addon Manager: Extract PySide QtCore interface
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
59
src/Mod/AddonManager/addonmanager_pyside_interface.py
Normal file
59
src/Mod/AddonManager/addonmanager_pyside_interface.py
Normal 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
|
||||
@@ -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__()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user