37 |
41 |
38 class PluginInstallWidget(QWidget, Ui_PluginInstallDialog): |
42 class PluginInstallWidget(QWidget, Ui_PluginInstallDialog): |
39 """ |
43 """ |
40 Class implementing the Plugin installation dialog. |
44 Class implementing the Plugin installation dialog. |
41 """ |
45 """ |
|
46 |
42 def __init__(self, pluginManager, pluginFileNames, parent=None): |
47 def __init__(self, pluginManager, pluginFileNames, parent=None): |
43 """ |
48 """ |
44 Constructor |
49 Constructor |
45 |
50 |
46 @param pluginManager reference to the plugin manager object |
51 @param pluginManager reference to the plugin manager object |
47 @param pluginFileNames list of plugin files suggested for |
52 @param pluginFileNames list of plugin files suggested for |
48 installation (list of strings) |
53 installation (list of strings) |
49 @param parent parent of this dialog (QWidget) |
54 @param parent parent of this dialog (QWidget) |
50 """ |
55 """ |
51 super().__init__(parent) |
56 super().__init__(parent) |
52 self.setupUi(self) |
57 self.setupUi(self) |
53 |
58 |
54 if pluginManager is None: |
59 if pluginManager is None: |
55 # started as external plugin installer |
60 # started as external plugin installer |
56 from .PluginManager import PluginManager |
61 from .PluginManager import PluginManager |
|
62 |
57 self.__pluginManager = PluginManager(doLoadPlugins=False) |
63 self.__pluginManager = PluginManager(doLoadPlugins=False) |
58 self.__external = True |
64 self.__external = True |
59 else: |
65 else: |
60 self.__pluginManager = pluginManager |
66 self.__pluginManager = pluginManager |
61 self.__external = False |
67 self.__external = False |
62 |
68 |
63 self.__backButton = self.buttonBox.addButton( |
69 self.__backButton = self.buttonBox.addButton( |
64 self.tr("< Back"), QDialogButtonBox.ButtonRole.ActionRole) |
70 self.tr("< Back"), QDialogButtonBox.ButtonRole.ActionRole |
|
71 ) |
65 self.__nextButton = self.buttonBox.addButton( |
72 self.__nextButton = self.buttonBox.addButton( |
66 self.tr("Next >"), QDialogButtonBox.ButtonRole.ActionRole) |
73 self.tr("Next >"), QDialogButtonBox.ButtonRole.ActionRole |
|
74 ) |
67 self.__finishButton = self.buttonBox.addButton( |
75 self.__finishButton = self.buttonBox.addButton( |
68 self.tr("Install"), QDialogButtonBox.ButtonRole.ActionRole) |
76 self.tr("Install"), QDialogButtonBox.ButtonRole.ActionRole |
69 |
77 ) |
|
78 |
70 self.__closeButton = self.buttonBox.button( |
79 self.__closeButton = self.buttonBox.button( |
71 QDialogButtonBox.StandardButton.Close) |
80 QDialogButtonBox.StandardButton.Close |
|
81 ) |
72 self.__cancelButton = self.buttonBox.button( |
82 self.__cancelButton = self.buttonBox.button( |
73 QDialogButtonBox.StandardButton.Cancel) |
83 QDialogButtonBox.StandardButton.Cancel |
74 |
84 ) |
|
85 |
75 userDir = self.__pluginManager.getPluginDir("user") |
86 userDir = self.__pluginManager.getPluginDir("user") |
76 if userDir is not None: |
87 if userDir is not None: |
77 self.destinationCombo.addItem( |
88 self.destinationCombo.addItem(self.tr("User plugins directory"), userDir) |
78 self.tr("User plugins directory"), |
89 |
79 userDir) |
|
80 |
|
81 globalDir = self.__pluginManager.getPluginDir("global") |
90 globalDir = self.__pluginManager.getPluginDir("global") |
82 if globalDir is not None and os.access(globalDir, os.W_OK): |
91 if globalDir is not None and os.access(globalDir, os.W_OK): |
83 self.destinationCombo.addItem( |
92 self.destinationCombo.addItem( |
84 self.tr("Global plugins directory"), |
93 self.tr("Global plugins directory"), globalDir |
85 globalDir) |
94 ) |
86 |
95 |
87 self.__installedDirs = [] |
96 self.__installedDirs = [] |
88 self.__installedFiles = [] |
97 self.__installedFiles = [] |
89 |
98 |
90 self.__restartNeeded = False |
99 self.__restartNeeded = False |
91 |
100 |
92 downloadDir = Preferences.getPluginManager("DownloadPath") |
101 downloadDir = Preferences.getPluginManager("DownloadPath") |
93 for pluginFileName in pluginFileNames: |
102 for pluginFileName in pluginFileNames: |
94 pluginFilePath = pathlib.Path(pluginFileName) |
103 pluginFilePath = pathlib.Path(pluginFileName) |
95 if not pluginFilePath.is_absolute(): |
104 if not pluginFilePath.is_absolute(): |
96 pluginFilePath = downloadDir / pluginFilePath |
105 pluginFilePath = downloadDir / pluginFilePath |
97 self.archivesList.addItem(str(pluginFilePath)) |
106 self.archivesList.addItem(str(pluginFilePath)) |
98 self.archivesList.sortItems() |
107 self.archivesList.sortItems() |
99 |
108 |
100 self.__currentIndex = 0 |
109 self.__currentIndex = 0 |
101 self.__selectPage() |
110 self.__selectPage() |
102 |
111 |
103 def restartNeeded(self): |
112 def restartNeeded(self): |
104 """ |
113 """ |
105 Public method to check, if a restart of the IDE is required. |
114 Public method to check, if a restart of the IDE is required. |
106 |
115 |
107 @return flag indicating a restart is required (boolean) |
116 @return flag indicating a restart is required (boolean) |
108 """ |
117 """ |
109 return self.__restartNeeded |
118 return self.__restartNeeded |
110 |
119 |
111 def __createArchivesList(self): |
120 def __createArchivesList(self): |
112 """ |
121 """ |
113 Private method to create a list of plugin archive names. |
122 Private method to create a list of plugin archive names. |
114 |
123 |
115 @return list of plugin archive names (list of strings) |
124 @return list of plugin archive names (list of strings) |
116 """ |
125 """ |
117 archivesList = [] |
126 archivesList = [] |
118 for row in range(self.archivesList.count()): |
127 for row in range(self.archivesList.count()): |
119 archivesList.append(self.archivesList.item(row).text()) |
128 archivesList.append(self.archivesList.item(row).text()) |
140 self.__backButton.setEnabled(True) |
149 self.__backButton.setEnabled(True) |
141 self.__nextButton.setEnabled(False) |
150 self.__nextButton.setEnabled(False) |
142 self.__finishButton.setEnabled(True) |
151 self.__finishButton.setEnabled(True) |
143 self.__closeButton.hide() |
152 self.__closeButton.hide() |
144 self.__cancelButton.show() |
153 self.__cancelButton.show() |
145 |
154 |
146 msg = self.tr( |
155 msg = self.tr( |
147 "Plugin ZIP-Archives:\n{0}\n\nDestination:\n{1} ({2})" |
156 "Plugin ZIP-Archives:\n{0}\n\nDestination:\n{1} ({2})" |
148 ).format( |
157 ).format( |
149 "\n".join(self.__createArchivesList()), |
158 "\n".join(self.__createArchivesList()), |
150 self.destinationCombo.currentText(), |
159 self.destinationCombo.currentText(), |
151 self.destinationCombo.itemData( |
160 self.destinationCombo.itemData(self.destinationCombo.currentIndex()), |
152 self.destinationCombo.currentIndex() |
|
153 ) |
|
154 ) |
161 ) |
155 self.summaryEdit.setPlainText(msg) |
162 self.summaryEdit.setPlainText(msg) |
156 |
163 |
157 @pyqtSlot() |
164 @pyqtSlot() |
158 def on_addArchivesButton_clicked(self): |
165 def on_addArchivesButton_clicked(self): |
159 """ |
166 """ |
160 Private slot to select plugin ZIP-archives via a file selection dialog. |
167 Private slot to select plugin ZIP-archives via a file selection dialog. |
161 """ |
168 """ |
162 dn = Preferences.getPluginManager("DownloadPath") |
169 dn = Preferences.getPluginManager("DownloadPath") |
163 archives = EricFileDialog.getOpenFileNames( |
170 archives = EricFileDialog.getOpenFileNames( |
164 self, |
171 self, |
165 self.tr("Select plugin ZIP-archives"), |
172 self.tr("Select plugin ZIP-archives"), |
166 dn, |
173 dn, |
167 self.tr("Plugin archive (*.zip)")) |
174 self.tr("Plugin archive (*.zip)"), |
168 |
175 ) |
|
176 |
169 if archives: |
177 if archives: |
170 matchflags = Qt.MatchFlag.MatchFixedString |
178 matchflags = Qt.MatchFlag.MatchFixedString |
171 if not Utilities.isWindowsPlatform(): |
179 if not Utilities.isWindowsPlatform(): |
172 matchflags |= Qt.MatchFlag.MatchCaseSensitive |
180 matchflags |= Qt.MatchFlag.MatchCaseSensitive |
173 for archive in archives: |
181 for archive in archives: |
174 if len(self.archivesList.findItems(archive, matchflags)) == 0: |
182 if len(self.archivesList.findItems(archive, matchflags)) == 0: |
175 # entry not in list already |
183 # entry not in list already |
176 self.archivesList.addItem(archive) |
184 self.archivesList.addItem(archive) |
177 self.archivesList.sortItems() |
185 self.archivesList.sortItems() |
178 |
186 |
179 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
187 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
180 |
188 |
181 @pyqtSlot() |
189 @pyqtSlot() |
182 def on_archivesList_itemSelectionChanged(self): |
190 def on_archivesList_itemSelectionChanged(self): |
183 """ |
191 """ |
184 Private slot called, when the selection of the archives list changes. |
192 Private slot called, when the selection of the archives list changes. |
185 """ |
193 """ |
186 self.removeArchivesButton.setEnabled( |
194 self.removeArchivesButton.setEnabled(len(self.archivesList.selectedItems()) > 0) |
187 len(self.archivesList.selectedItems()) > 0) |
195 |
188 |
|
189 @pyqtSlot() |
196 @pyqtSlot() |
190 def on_removeArchivesButton_clicked(self): |
197 def on_removeArchivesButton_clicked(self): |
191 """ |
198 """ |
192 Private slot to remove archives from the list. |
199 Private slot to remove archives from the list. |
193 """ |
200 """ |
194 for archiveItem in self.archivesList.selectedItems(): |
201 for archiveItem in self.archivesList.selectedItems(): |
195 itm = self.archivesList.takeItem( |
202 itm = self.archivesList.takeItem(self.archivesList.row(archiveItem)) |
196 self.archivesList.row(archiveItem)) |
|
197 del itm |
203 del itm |
198 |
204 |
199 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
205 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
200 |
206 |
201 @pyqtSlot(QAbstractButton) |
207 @pyqtSlot(QAbstractButton) |
202 def on_buttonBox_clicked(self, button): |
208 def on_buttonBox_clicked(self, button): |
203 """ |
209 """ |
204 Private slot to handle the click of a button of the button box. |
210 Private slot to handle the click of a button of the button box. |
205 |
211 |
206 @param button reference to the button pressed (QAbstractButton) |
212 @param button reference to the button pressed (QAbstractButton) |
207 """ |
213 """ |
208 if button == self.__backButton: |
214 if button == self.__backButton: |
209 self.__currentIndex -= 1 |
215 self.__currentIndex -= 1 |
210 self.__selectPage() |
216 self.__selectPage() |
217 if not Preferences.getPluginManager("ActivateExternal"): |
223 if not Preferences.getPluginManager("ActivateExternal"): |
218 Preferences.setPluginManager("ActivateExternal", True) |
224 Preferences.setPluginManager("ActivateExternal", True) |
219 self.__restartNeeded = True |
225 self.__restartNeeded = True |
220 self.__closeButton.show() |
226 self.__closeButton.show() |
221 self.__cancelButton.hide() |
227 self.__cancelButton.hide() |
222 |
228 |
223 def __installPlugins(self): |
229 def __installPlugins(self): |
224 """ |
230 """ |
225 Private method to install the selected plugin archives. |
231 Private method to install the selected plugin archives. |
226 |
232 |
227 @return flag indicating success (boolean) |
233 @return flag indicating success (boolean) |
228 """ |
234 """ |
229 res = True |
235 res = True |
230 self.summaryEdit.clear() |
236 self.summaryEdit.clear() |
231 for archive in self.__createArchivesList(): |
237 for archive in self.__createArchivesList(): |
232 self.summaryEdit.append( |
238 self.summaryEdit.append(self.tr("Installing {0} ...").format(archive)) |
233 self.tr("Installing {0} ...").format(archive)) |
|
234 ok, msg, restart = self.__installPlugin(archive) |
239 ok, msg, restart = self.__installPlugin(archive) |
235 res = res and ok |
240 res = res and ok |
236 if ok: |
241 if ok: |
237 self.summaryEdit.append(self.tr(" ok")) |
242 self.summaryEdit.append(self.tr(" ok")) |
238 else: |
243 else: |
239 self.summaryEdit.append(msg) |
244 self.summaryEdit.append(msg) |
240 if restart: |
245 if restart: |
241 self.__restartNeeded = True |
246 self.__restartNeeded = True |
242 self.summaryEdit.append("\n") |
247 self.summaryEdit.append("\n") |
243 if res: |
248 if res: |
244 self.summaryEdit.append(self.tr( |
249 self.summaryEdit.append( |
245 """The plugins were installed successfully.""")) |
250 self.tr("""The plugins were installed successfully.""") |
|
251 ) |
246 else: |
252 else: |
247 self.summaryEdit.append(self.tr( |
253 self.summaryEdit.append(self.tr("""Some plugins could not be installed.""")) |
248 """Some plugins could not be installed.""")) |
254 |
249 |
|
250 return res |
255 return res |
251 |
256 |
252 def __installPlugin(self, archiveFilename): |
257 def __installPlugin(self, archiveFilename): |
253 """ |
258 """ |
254 Private slot to install the selected plugin. |
259 Private slot to install the selected plugin. |
255 |
260 |
256 @param archiveFilename name of the plugin archive |
261 @param archiveFilename name of the plugin archive |
257 file (string) |
262 file (string) |
258 @return flag indicating success (boolean), error message |
263 @return flag indicating success (boolean), error message |
259 upon failure (string) and flag indicating a restart |
264 upon failure (string) and flag indicating a restart |
260 of the IDE is required (boolean) |
265 of the IDE is required (boolean) |
261 """ |
266 """ |
262 installedPluginName = "" |
267 installedPluginName = "" |
263 |
268 |
264 archive = archiveFilename |
269 archive = archiveFilename |
265 destination = self.destinationCombo.itemData( |
270 destination = self.destinationCombo.itemData( |
266 self.destinationCombo.currentIndex()) |
271 self.destinationCombo.currentIndex() |
267 |
272 ) |
|
273 |
268 # check if archive is a local url |
274 # check if archive is a local url |
269 url = urllib.parse.urlparse(archive) |
275 url = urllib.parse.urlparse(archive) |
270 if url[0].lower() == 'file': |
276 if url[0].lower() == "file": |
271 archive = url[2] |
277 archive = url[2] |
272 |
278 |
273 # check, if the archive exists |
279 # check, if the archive exists |
274 if not os.path.exists(archive): |
280 if not os.path.exists(archive): |
275 return ( |
281 return ( |
276 False, |
282 False, |
277 self.tr( |
283 self.tr( |
278 """<p>The archive file <b>{0}</b> does not exist. """ |
284 """<p>The archive file <b>{0}</b> does not exist. """ |
279 """Aborting...</p>""").format(archive), |
285 """Aborting...</p>""" |
280 False |
286 ).format(archive), |
281 ) |
287 False, |
282 |
288 ) |
|
289 |
283 # check, if the archive is a valid zip file |
290 # check, if the archive is a valid zip file |
284 if not zipfile.is_zipfile(archive): |
291 if not zipfile.is_zipfile(archive): |
285 return ( |
292 return ( |
286 False, |
293 False, |
287 self.tr( |
294 self.tr( |
288 """<p>The file <b>{0}</b> is not a valid plugin """ |
295 """<p>The file <b>{0}</b> is not a valid plugin """ |
289 """ZIP-archive. Aborting...</p>""").format(archive), |
296 """ZIP-archive. Aborting...</p>""" |
290 False |
297 ).format(archive), |
291 ) |
298 False, |
292 |
299 ) |
|
300 |
293 # check, if the destination is writeable |
301 # check, if the destination is writeable |
294 if not os.access(destination, os.W_OK): |
302 if not os.access(destination, os.W_OK): |
295 return ( |
303 return ( |
296 False, |
304 False, |
297 self.tr( |
305 self.tr( |
298 """<p>The destination directory <b>{0}</b> is not """ |
306 """<p>The destination directory <b>{0}</b> is not """ |
299 """writeable. Aborting...</p>""").format(destination), |
307 """writeable. Aborting...</p>""" |
300 False |
308 ).format(destination), |
301 ) |
309 False, |
302 |
310 ) |
|
311 |
303 zipFile = zipfile.ZipFile(archive, "r") |
312 zipFile = zipfile.ZipFile(archive, "r") |
304 |
313 |
305 # check, if the archive contains a valid plugin |
314 # check, if the archive contains a valid plugin |
306 pluginFound = False |
315 pluginFound = False |
307 pluginFileName = "" |
316 pluginFileName = "" |
308 for name in zipFile.namelist(): |
317 for name in zipFile.namelist(): |
309 if self.__pluginManager.isValidPluginName(name): |
318 if self.__pluginManager.isValidPluginName(name): |
310 installedPluginName = name[:-3] |
319 installedPluginName = name[:-3] |
311 pluginFound = True |
320 pluginFound = True |
312 pluginFileName = name |
321 pluginFileName = name |
313 break |
322 break |
314 |
323 |
315 if not pluginFound: |
324 if not pluginFound: |
316 return ( |
325 return ( |
317 False, |
326 False, |
318 self.tr( |
327 self.tr( |
319 """<p>The file <b>{0}</b> is not a valid plugin """ |
328 """<p>The file <b>{0}</b> is not a valid plugin """ |
320 """ZIP-archive. Aborting...</p>""").format(archive), |
329 """ZIP-archive. Aborting...</p>""" |
321 False |
330 ).format(archive), |
322 ) |
331 False, |
323 |
332 ) |
|
333 |
324 # parse the plugin module's plugin header |
334 # parse the plugin module's plugin header |
325 pluginSource = Utilities.decode(zipFile.read(pluginFileName))[0] |
335 pluginSource = Utilities.decode(zipFile.read(pluginFileName))[0] |
326 packageName = "" |
336 packageName = "" |
327 internalPackages = [] |
337 internalPackages = [] |
328 needsRestart = False |
338 needsRestart = False |
356 tokens = line.split("=") |
366 tokens = line.split("=") |
357 if tokens[1].strip() == "True": |
367 if tokens[1].strip() == "True": |
358 doCompile = False |
368 doCompile = False |
359 elif line.startswith("# End-Of-Header"): |
369 elif line.startswith("# End-Of-Header"): |
360 break |
370 break |
361 |
371 |
362 if not packageName: |
372 if not packageName: |
363 return ( |
373 return ( |
364 False, |
374 False, |
365 self.tr( |
375 self.tr( |
366 """<p>The plugin module <b>{0}</b> does not contain """ |
376 """<p>The plugin module <b>{0}</b> does not contain """ |
367 """a 'packageName' attribute. Aborting...</p>""" |
377 """a 'packageName' attribute. Aborting...</p>""" |
368 ).format(pluginFileName), |
378 ).format(pluginFileName), |
369 False |
379 False, |
370 ) |
380 ) |
371 |
381 |
372 if pyqtApi < 2: |
382 if pyqtApi < 2: |
373 return ( |
383 return ( |
374 False, |
384 False, |
375 self.tr( |
385 self.tr( |
376 """<p>The plugin module <b>{0}</b> does not conform""" |
386 """<p>The plugin module <b>{0}</b> does not conform""" |
377 """ with the PyQt v2 API. Aborting...</p>""" |
387 """ with the PyQt v2 API. Aborting...</p>""" |
378 ).format(pluginFileName), |
388 ).format(pluginFileName), |
379 False |
389 False, |
380 ) |
390 ) |
381 |
391 |
382 # check, if it is a plugin, that collides with others |
392 # check, if it is a plugin, that collides with others |
383 if ( |
393 if ( |
384 not os.path.exists(os.path.join(destination, pluginFileName)) and |
394 not os.path.exists(os.path.join(destination, pluginFileName)) |
385 packageName != "None" and |
395 and packageName != "None" |
386 os.path.exists(os.path.join(destination, packageName)) |
396 and os.path.exists(os.path.join(destination, packageName)) |
387 ): |
397 ): |
388 return ( |
398 return ( |
389 False, |
399 False, |
390 self.tr("""<p>The plugin package <b>{0}</b> exists. """ |
400 self.tr( |
391 """Aborting...</p>""").format( |
401 """<p>The plugin package <b>{0}</b> exists. """ |
392 os.path.join(destination, packageName)), |
402 """Aborting...</p>""" |
393 False |
403 ).format(os.path.join(destination, packageName)), |
394 ) |
404 False, |
395 |
405 ) |
|
406 |
396 if ( |
407 if ( |
397 os.path.exists(os.path.join(destination, pluginFileName)) and |
408 os.path.exists(os.path.join(destination, pluginFileName)) |
398 packageName != "None" and |
409 and packageName != "None" |
399 not os.path.exists(os.path.join(destination, packageName)) |
410 and not os.path.exists(os.path.join(destination, packageName)) |
400 ): |
411 ): |
401 return ( |
412 return ( |
402 False, |
413 False, |
403 self.tr("""<p>The plugin module <b>{0}</b> exists. """ |
414 self.tr( |
404 """Aborting...</p>""").format( |
415 """<p>The plugin module <b>{0}</b> exists. """ """Aborting...</p>""" |
405 os.path.join(destination, pluginFileName)), |
416 ).format(os.path.join(destination, pluginFileName)), |
406 False |
417 False, |
407 ) |
418 ) |
408 |
419 |
409 activatePlugin = False |
420 activatePlugin = False |
410 if not self.__external: |
421 if not self.__external: |
411 activatePlugin = ( |
422 activatePlugin = not self.__pluginManager.isPluginLoaded( |
412 not self.__pluginManager.isPluginLoaded( |
423 installedPluginName |
413 installedPluginName) or |
424 ) or ( |
414 (self.__pluginManager.isPluginLoaded(installedPluginName) and |
425 self.__pluginManager.isPluginLoaded(installedPluginName) |
415 self.__pluginManager.isPluginActive(installedPluginName)) |
426 and self.__pluginManager.isPluginActive(installedPluginName) |
416 ) |
427 ) |
417 # try to unload a plugin with the same name |
428 # try to unload a plugin with the same name |
418 self.__pluginManager.unloadPlugin(installedPluginName) |
429 self.__pluginManager.unloadPlugin(installedPluginName) |
419 |
430 |
420 # uninstall existing plug-in first to get clean conditions |
431 # uninstall existing plug-in first to get clean conditions |
421 if ( |
432 if packageName != "None" and not os.path.exists( |
422 packageName != "None" and |
433 os.path.join(destination, packageName, "__init__.py") |
423 not os.path.exists( |
|
424 os.path.join(destination, packageName, "__init__.py")) |
|
425 ): |
434 ): |
426 # package directory contains just data, don't delete it |
435 # package directory contains just data, don't delete it |
427 self.__uninstallPackage(destination, pluginFileName, "") |
436 self.__uninstallPackage(destination, pluginFileName, "") |
428 else: |
437 else: |
429 self.__uninstallPackage(destination, pluginFileName, packageName) |
438 self.__uninstallPackage(destination, pluginFileName, packageName) |
430 |
439 |
431 # clean sys.modules |
440 # clean sys.modules |
432 reload_ = self.__pluginManager.removePluginFromSysModules( |
441 reload_ = self.__pluginManager.removePluginFromSysModules( |
433 installedPluginName, packageName, internalPackages) |
442 installedPluginName, packageName, internalPackages |
434 |
443 ) |
|
444 |
435 # now do the installation |
445 # now do the installation |
436 self.__installedDirs = [] |
446 self.__installedDirs = [] |
437 self.__installedFiles = [] |
447 self.__installedFiles = [] |
438 try: |
448 try: |
439 if packageName != "None": |
449 if packageName != "None": |
440 namelist = sorted(zipFile.namelist()) |
450 namelist = sorted(zipFile.namelist()) |
441 tot = len(namelist) |
451 tot = len(namelist) |
442 self.progress.setMaximum(tot) |
452 self.progress.setMaximum(tot) |
443 QApplication.processEvents() |
453 QApplication.processEvents() |
444 |
454 |
445 now = time.monotonic() |
455 now = time.monotonic() |
446 for prog, name in enumerate(namelist): |
456 for prog, name in enumerate(namelist): |
447 self.progress.setValue(prog) |
457 self.progress.setValue(prog) |
448 if time.monotonic() - now > 0.01: |
458 if time.monotonic() - now > 0.01: |
449 QApplication.processEvents() |
459 QApplication.processEvents() |
450 now = time.monotonic() |
460 now = time.monotonic() |
451 if ( |
461 if ( |
452 name == pluginFileName or |
462 name == pluginFileName |
453 name.startswith("{0}/".format(packageName)) or |
463 or name.startswith("{0}/".format(packageName)) |
454 name.startswith("{0}\\".format(packageName)) |
464 or name.startswith("{0}\\".format(packageName)) |
455 ): |
465 ): |
456 outname = name.replace("/", os.sep) |
466 outname = name.replace("/", os.sep) |
457 outname = os.path.join(destination, outname) |
467 outname = os.path.join(destination, outname) |
458 if outname.endswith("/") or outname.endswith("\\"): |
468 if outname.endswith("/") or outname.endswith("\\"): |
459 # it is a directory entry |
469 # it is a directory entry |
478 self.__installedFiles.append(outname) |
488 self.__installedFiles.append(outname) |
479 except OSError as why: |
489 except OSError as why: |
480 self.__rollback() |
490 self.__rollback() |
481 return ( |
491 return ( |
482 False, |
492 False, |
483 self.tr("Error installing plugin. Reason: {0}") |
493 self.tr("Error installing plugin. Reason: {0}").format(str(why)), |
484 .format(str(why)), |
494 False, |
485 False |
|
486 ) |
495 ) |
487 except Exception: |
496 except Exception: |
488 sys.stderr.write("Unspecific exception installing plugin.\n") |
497 sys.stderr.write("Unspecific exception installing plugin.\n") |
489 self.__rollback() |
498 self.__rollback() |
490 return ( |
499 return (False, self.tr("Unspecific exception installing plugin."), False) |
491 False, |
500 |
492 self.tr("Unspecific exception installing plugin."), |
|
493 False |
|
494 ) |
|
495 |
|
496 # now compile the plugins |
501 # now compile the plugins |
497 if doCompile: |
502 if doCompile: |
498 dirName = os.path.join(destination, packageName) |
503 dirName = os.path.join(destination, packageName) |
499 files = os.path.join(destination, pluginFileName) |
504 files = os.path.join(destination, pluginFileName) |
500 os.path.join_unicode = False |
505 os.path.join_unicode = False |
501 compileall.compile_dir(dirName, quiet=True) |
506 compileall.compile_dir(dirName, quiet=True) |
502 compileall.compile_file(files, quiet=True) |
507 compileall.compile_file(files, quiet=True) |
503 os.path.join_unicode = True |
508 os.path.join_unicode = True |
504 |
509 |
505 # now load and activate the plugin |
510 # now load and activate the plugin |
506 self.__pluginManager.loadPlugin( |
511 self.__pluginManager.loadPlugin( |
507 installedPluginName, destination, reload_=reload_, install=True) |
512 installedPluginName, destination, reload_=reload_, install=True |
|
513 ) |
508 if activatePlugin and not self.__external: |
514 if activatePlugin and not self.__external: |
509 self.__pluginManager.activatePlugin(installedPluginName) |
515 self.__pluginManager.activatePlugin(installedPluginName) |
510 |
516 |
511 return True, "", needsRestart |
517 return True, "", needsRestart |
512 |
518 |
513 def __rollback(self): |
519 def __rollback(self): |
514 """ |
520 """ |
515 Private method to rollback a failed installation. |
521 Private method to rollback a failed installation. |
516 """ |
522 """ |
517 for fname in self.__installedFiles: |
523 for fname in self.__installedFiles: |
518 if os.path.exists(fname): |
524 if os.path.exists(fname): |
519 os.remove(fname) |
525 os.remove(fname) |
520 for dname in self.__installedDirs: |
526 for dname in self.__installedDirs: |
521 if os.path.exists(dname): |
527 if os.path.exists(dname): |
522 shutil.rmtree(dname) |
528 shutil.rmtree(dname) |
523 |
529 |
524 def __makedirs(self, name, mode=0o777): |
530 def __makedirs(self, name, mode=0o777): |
525 """ |
531 """ |
526 Private method to create a directory and all intermediate ones. |
532 Private method to create a directory and all intermediate ones. |
527 |
533 |
528 This is an extended version of the Python one in order to |
534 This is an extended version of the Python one in order to |
529 record the created directories. |
535 record the created directories. |
530 |
536 |
531 @param name name of the directory to create (string) |
537 @param name name of the directory to create (string) |
532 @param mode permission to set for the new directory (integer) |
538 @param mode permission to set for the new directory (integer) |
533 """ |
539 """ |
534 head, tail = os.path.split(name) |
540 head, tail = os.path.split(name) |
535 if not tail: |
541 if not tail: |
539 if tail == os.curdir: |
545 if tail == os.curdir: |
540 # xxx/newdir/. exists if xxx/newdir exists |
546 # xxx/newdir/. exists if xxx/newdir exists |
541 return |
547 return |
542 os.mkdir(name, mode) |
548 os.mkdir(name, mode) |
543 self.__installedDirs.append(name) |
549 self.__installedDirs.append(name) |
544 |
550 |
545 def __uninstallPackage(self, destination, pluginFileName, packageName): |
551 def __uninstallPackage(self, destination, pluginFileName, packageName): |
546 """ |
552 """ |
547 Private method to uninstall an already installed plugin to prepare |
553 Private method to uninstall an already installed plugin to prepare |
548 the update. |
554 the update. |
549 |
555 |
550 @param destination name of the plugin directory (string) |
556 @param destination name of the plugin directory (string) |
551 @param pluginFileName name of the plugin file (string) |
557 @param pluginFileName name of the plugin file (string) |
552 @param packageName name of the plugin package (string) |
558 @param packageName name of the plugin package (string) |
553 """ |
559 """ |
554 packageDir = ( |
560 packageDir = ( |
555 None |
561 None |
556 if packageName in ("", "None") else |
562 if packageName in ("", "None") |
557 os.path.join(destination, packageName) |
563 else os.path.join(destination, packageName) |
558 ) |
564 ) |
559 pluginFile = os.path.join(destination, pluginFileName) |
565 pluginFile = os.path.join(destination, pluginFileName) |
560 |
566 |
561 with contextlib.suppress(OSError, os.error): |
567 with contextlib.suppress(OSError, os.error): |
562 if packageDir and os.path.exists(packageDir): |
568 if packageDir and os.path.exists(packageDir): |
563 shutil.rmtree(packageDir) |
569 shutil.rmtree(packageDir) |
564 |
570 |
565 fnameo = "{0}o".format(pluginFile) |
571 fnameo = "{0}o".format(pluginFile) |
566 if os.path.exists(fnameo): |
572 if os.path.exists(fnameo): |
567 os.remove(fnameo) |
573 os.remove(fnameo) |
568 |
574 |
569 fnamec = "{0}c".format(pluginFile) |
575 fnamec = "{0}c".format(pluginFile) |
570 if os.path.exists(fnamec): |
576 if os.path.exists(fnamec): |
571 os.remove(fnamec) |
577 os.remove(fnamec) |
572 |
578 |
573 pluginDirCache = os.path.join( |
579 pluginDirCache = os.path.join(os.path.dirname(pluginFile), "__pycache__") |
574 os.path.dirname(pluginFile), "__pycache__") |
|
575 if os.path.exists(pluginDirCache): |
580 if os.path.exists(pluginDirCache): |
576 pluginFileName = os.path.splitext( |
581 pluginFileName = os.path.splitext(os.path.basename(pluginFile))[0] |
577 os.path.basename(pluginFile))[0] |
|
578 for fnameo in glob.glob( |
582 for fnameo in glob.glob( |
579 os.path.join(pluginDirCache, |
583 os.path.join(pluginDirCache, "{0}*.pyo".format(pluginFileName)) |
580 "{0}*.pyo".format(pluginFileName))): |
584 ): |
581 os.remove(fnameo) |
585 os.remove(fnameo) |
582 for fnamec in glob.glob( |
586 for fnamec in glob.glob( |
583 os.path.join(pluginDirCache, |
587 os.path.join(pluginDirCache, "{0}*.pyc".format(pluginFileName)) |
584 "{0}*.pyc".format(pluginFileName))): |
588 ): |
585 os.remove(fnamec) |
589 os.remove(fnamec) |
586 |
590 |
587 os.remove(pluginFile) |
591 os.remove(pluginFile) |
588 |
592 |
589 |
593 |
590 class PluginInstallDialog(QDialog): |
594 class PluginInstallDialog(QDialog): |
591 """ |
595 """ |
592 Class for the dialog variant. |
596 Class for the dialog variant. |
593 """ |
597 """ |
|
598 |
594 def __init__(self, pluginManager, pluginFileNames, parent=None): |
599 def __init__(self, pluginManager, pluginFileNames, parent=None): |
595 """ |
600 """ |
596 Constructor |
601 Constructor |
597 |
602 |
598 @param pluginManager reference to the plugin manager object |
603 @param pluginManager reference to the plugin manager object |
599 @param pluginFileNames list of plugin files suggested for |
604 @param pluginFileNames list of plugin files suggested for |
600 installation (list of strings) |
605 installation (list of strings) |
601 @param parent reference to the parent widget (QWidget) |
606 @param parent reference to the parent widget (QWidget) |
602 """ |
607 """ |
603 super().__init__(parent) |
608 super().__init__(parent) |
604 self.setSizeGripEnabled(True) |
609 self.setSizeGripEnabled(True) |
605 |
610 |
606 self.__layout = QVBoxLayout(self) |
611 self.__layout = QVBoxLayout(self) |
607 self.__layout.setContentsMargins(0, 0, 0, 0) |
612 self.__layout.setContentsMargins(0, 0, 0, 0) |
608 self.setLayout(self.__layout) |
613 self.setLayout(self.__layout) |
609 |
614 |
610 self.cw = PluginInstallWidget(pluginManager, pluginFileNames, self) |
615 self.cw = PluginInstallWidget(pluginManager, pluginFileNames, self) |
611 size = self.cw.size() |
616 size = self.cw.size() |
612 self.__layout.addWidget(self.cw) |
617 self.__layout.addWidget(self.cw) |
613 self.resize(size) |
618 self.resize(size) |
614 self.setWindowTitle(self.cw.windowTitle()) |
619 self.setWindowTitle(self.cw.windowTitle()) |
615 |
620 |
616 self.cw.buttonBox.accepted.connect(self.accept) |
621 self.cw.buttonBox.accepted.connect(self.accept) |
617 self.cw.buttonBox.rejected.connect(self.reject) |
622 self.cw.buttonBox.rejected.connect(self.reject) |
618 |
623 |
619 def restartNeeded(self): |
624 def restartNeeded(self): |
620 """ |
625 """ |
621 Public method to check, if a restart of the IDE is required. |
626 Public method to check, if a restart of the IDE is required. |
622 |
627 |
623 @return flag indicating a restart is required (boolean) |
628 @return flag indicating a restart is required (boolean) |
624 """ |
629 """ |
625 return self.cw.restartNeeded() |
630 return self.cw.restartNeeded() |
626 |
631 |
627 |
632 |
628 class PluginInstallWindow(EricMainWindow): |
633 class PluginInstallWindow(EricMainWindow): |
629 """ |
634 """ |
630 Main window class for the standalone dialog. |
635 Main window class for the standalone dialog. |
631 """ |
636 """ |
|
637 |
632 def __init__(self, pluginFileNames, parent=None): |
638 def __init__(self, pluginFileNames, parent=None): |
633 """ |
639 """ |
634 Constructor |
640 Constructor |
635 |
641 |
636 @param pluginFileNames list of plugin files suggested for |
642 @param pluginFileNames list of plugin files suggested for |
637 installation (list of strings) |
643 installation (list of strings) |
638 @param parent reference to the parent widget (QWidget) |
644 @param parent reference to the parent widget (QWidget) |
639 """ |
645 """ |
640 super().__init__(parent) |
646 super().__init__(parent) |
641 self.cw = PluginInstallWidget(None, pluginFileNames, self) |
647 self.cw = PluginInstallWidget(None, pluginFileNames, self) |
642 size = self.cw.size() |
648 size = self.cw.size() |
643 self.setCentralWidget(self.cw) |
649 self.setCentralWidget(self.cw) |
644 self.resize(size) |
650 self.resize(size) |
645 self.setWindowTitle(self.cw.windowTitle()) |
651 self.setWindowTitle(self.cw.windowTitle()) |
646 |
652 |
647 self.setStyle(Preferences.getUI("Style"), |
653 self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) |
648 Preferences.getUI("StyleSheet")) |
654 |
649 |
|
650 self.cw.buttonBox.accepted.connect(self.close) |
655 self.cw.buttonBox.accepted.connect(self.close) |
651 self.cw.buttonBox.rejected.connect(self.close) |
656 self.cw.buttonBox.rejected.connect(self.close) |