FreeCAD

Форк
0
/
addonmanager_update_all_gui.py 
211 строк · 8.7 Кб
1
# SPDX-License-Identifier: LGPL-2.1-or-later
2
# ***************************************************************************
3
# *                                                                         *
4
# *   Copyright (c) 2022 FreeCAD Project Association                        *
5
# *                                                                         *
6
# *   This file is part of FreeCAD.                                         *
7
# *                                                                         *
8
# *   FreeCAD is free software: you can redistribute it and/or modify it    *
9
# *   under the terms of the GNU Lesser General Public License as           *
10
# *   published by the Free Software Foundation, either version 2.1 of the  *
11
# *   License, or (at your option) any later version.                       *
12
# *                                                                         *
13
# *   FreeCAD is distributed in the hope that it will be useful, but        *
14
# *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
15
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
16
# *   Lesser General Public License for more details.                       *
17
# *                                                                         *
18
# *   You should have received a copy of the GNU Lesser General Public      *
19
# *   License along with FreeCAD. If not, see                               *
20
# *   <https://www.gnu.org/licenses/>.                                      *
21
# *                                                                         *
22
# ***************************************************************************
23

24
"""Class to manage the display of an Update All dialog."""
25

26
from enum import IntEnum, auto
27
import os
28
from typing import List
29

30
import FreeCAD
31
import FreeCADGui
32

33
from PySide import QtCore, QtWidgets
34

35
from Addon import Addon
36

37
from addonmanager_installer import AddonInstaller, MacroInstaller
38

39
translate = FreeCAD.Qt.translate
40

41
# pylint: disable=too-few-public-methods,too-many-instance-attributes
42

43

44
class UpdaterFactory:
45
    """A factory class for generating updaters. Mainly exists to allow easily mocking
46
    those updaters during testing. A replacement class need only provide a
47
    "get_updater" function that returns mock updater objects. Those objects must be
48
    QObjects with a run() function and a finished signal."""
49

50
    def __init__(self, addons):
51
        self.addons = addons
52

53
    def get_updater(self, addon):
54
        """Get an updater for this addon (either a MacroInstaller or an
55
        AddonInstaller)"""
56
        if addon.macro is not None:
57
            return MacroInstaller(addon)
58
        return AddonInstaller(addon, self.addons)
59

60

61
class AddonStatus(IntEnum):
62
    """The current status of the installation process for a given addon"""
63

64
    WAITING = auto()
65
    INSTALLING = auto()
66
    SUCCEEDED = auto()
67
    FAILED = auto()
68

69
    def ui_string(self):
70
        """Get the string that the UI should show for this status"""
71
        if self.value == AddonStatus.WAITING:
72
            return ""
73
        if self.value == AddonStatus.INSTALLING:
74
            return translate("AddonsInstaller", "Installing") + "..."
75
        if self.value == AddonStatus.SUCCEEDED:
76
            return translate("AddonsInstaller", "Succeeded")
77
        if self.value == AddonStatus.FAILED:
78
            return translate("AddonsInstaller", "Failed")
79
        return "[INTERNAL ERROR]"
80

81

82
class UpdateAllGUI(QtCore.QObject):
83
    """A GUI to display and manage an "update all" process."""
84

85
    finished = QtCore.Signal()
86
    addon_updated = QtCore.Signal(object)
87

88
    def __init__(self, addons: List[Addon]):
89
        super().__init__()
90
        self.addons = addons
91
        self.dialog = FreeCADGui.PySideUic.loadUi(
92
            os.path.join(os.path.dirname(__file__), "update_all.ui")
93
        )
94
        self.dialog.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint, True)
95
        self.row_map = {}
96
        self.in_process_row = None
97
        self.active_installer = None
98
        self.addons_with_update: List[Addon] = []
99
        self.updater_factory = UpdaterFactory(addons)
100
        self.worker_thread = None
101
        self.running = False
102
        self.cancelled = False
103

104
    def run(self):
105
        """Run the Update All process. Blocks until updates are complete or
106
        cancelled."""
107
        self.running = True
108
        self._setup_dialog()
109
        self.dialog.show()
110
        self._process_next_update()
111

112
    def _setup_dialog(self):
113
        """Prepare the dialog for display"""
114
        self.dialog.rejected.connect(self._cancel_installation)
115
        self.dialog.tableWidget.clear()
116
        self.in_process_row = None
117
        self.row_map = {}
118
        for addon in self.addons:
119
            if addon.status() == Addon.Status.UPDATE_AVAILABLE:
120
                self._add_addon_to_table(addon)
121
                self.addons_with_update.append(addon)
122

123
    def _cancel_installation(self):
124
        self.cancelled = True
125
        if self.worker_thread and self.worker_thread.isRunning():
126
            self.worker_thread.requestInterruption()
127

128
    def _add_addon_to_table(self, addon: Addon):
129
        """Add the given addon to the list, with no icon in the first column"""
130
        new_row = self.dialog.tableWidget.rowCount()
131
        self.dialog.tableWidget.setColumnCount(2)
132
        self.dialog.tableWidget.setRowCount(new_row + 1)
133
        self.dialog.tableWidget.setItem(new_row, 0, QtWidgets.QTableWidgetItem(addon.display_name))
134
        self.dialog.tableWidget.setItem(new_row, 1, QtWidgets.QTableWidgetItem(""))
135
        self.row_map[addon.name] = new_row
136

137
    def _update_addon_status(self, row: int, status: AddonStatus):
138
        """Update the GUI to reflect this addon's new status."""
139
        self.dialog.tableWidget.item(row, 1).setText(status.ui_string())
140

141
    def _process_next_update(self):
142
        """Grab the next addon in the list and start its updater."""
143
        if self.addons_with_update:
144
            addon = self.addons_with_update.pop(0)
145
            self.in_process_row = self.row_map[addon.name] if addon.name in self.row_map else None
146
            self._update_addon_status(self.in_process_row, AddonStatus.INSTALLING)
147
            self.dialog.tableWidget.scrollToItem(
148
                self.dialog.tableWidget.item(self.in_process_row, 0)
149
            )
150
            self.active_installer = self.updater_factory.get_updater(addon)
151
            self._launch_active_installer()
152
        else:
153
            self._finalize()
154

155
    def _launch_active_installer(self):
156
        """Set up and run the active installer in a new thread."""
157

158
        self.active_installer.success.connect(self._update_succeeded)
159
        self.active_installer.failure.connect(self._update_failed)
160
        self.active_installer.finished.connect(self._update_finished)
161

162
        self.worker_thread = QtCore.QThread()
163
        self.active_installer.moveToThread(self.worker_thread)
164
        self.worker_thread.started.connect(self.active_installer.run)
165
        self.worker_thread.start()
166

167
    def _update_succeeded(self, addon):
168
        """Callback for a successful update"""
169
        self._update_addon_status(self.row_map[addon.name], AddonStatus.SUCCEEDED)
170
        self.addon_updated.emit(addon)
171

172
    def _update_failed(self, addon):
173
        """Callback for a failed update"""
174
        self._update_addon_status(self.row_map[addon.name], AddonStatus.FAILED)
175

176
    def _update_finished(self):
177
        """Callback for updater that has finished all its work"""
178
        if self.worker_thread is not None and self.worker_thread.isRunning():
179
            self.worker_thread.quit()
180
            self.worker_thread.wait()
181
        self.addon_updated.emit(self.active_installer.addon_to_install)
182
        if not self.cancelled:
183
            self._process_next_update()
184
        else:
185
            self._setup_cancelled_state()
186

187
    def _finalize(self):
188
        """No more updates, clean up and shut down"""
189
        if self.worker_thread is not None and self.worker_thread.isRunning():
190
            self.worker_thread.quit()
191
            self.worker_thread.wait()
192
        text = translate("Addons installer", "Finished updating the following addons")
193
        self._set_dialog_to_final_state(text)
194
        self.running = False
195
        self.finished.emit()
196

197
    def _setup_cancelled_state(self):
198
        text1 = translate("AddonsInstaller", "Update was cancelled")
199
        text2 = translate("AddonsInstaller", "some addons may have been updated")
200
        self._set_dialog_to_final_state(text1 + ": " + text2)
201
        self.running = False
202
        self.finished.emit()
203

204
    def _set_dialog_to_final_state(self, new_content):
205
        self.dialog.buttonBox.clear()
206
        self.dialog.buttonBox.addButton(QtWidgets.QDialogButtonBox.Close)
207
        self.dialog.label.setText(new_content)
208

209
    def is_running(self):
210
        """True if the thread is running, and False if not"""
211
        return self.running
212

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.