FreeCAD

Форк
0
/
addonmanager_update_all_gui.py 
210 строк · 8.6 Кб
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.row_map = {}
95
        self.in_process_row = None
96
        self.active_installer = None
97
        self.addons_with_update: List[Addon] = []
98
        self.updater_factory = UpdaterFactory(addons)
99
        self.worker_thread = None
100
        self.running = False
101
        self.cancelled = False
102

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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