src/eric7/PluginManager/PluginInstallDialog.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
18 import urllib.parse 18 import urllib.parse
19 import zipfile 19 import zipfile
20 20
21 from PyQt6.QtCore import pyqtSlot, Qt 21 from PyQt6.QtCore import pyqtSlot, Qt
22 from PyQt6.QtWidgets import ( 22 from PyQt6.QtWidgets import (
23 QWidget, QDialogButtonBox, QAbstractButton, QApplication, QDialog, 23 QWidget,
24 QVBoxLayout 24 QDialogButtonBox,
25 QAbstractButton,
26 QApplication,
27 QDialog,
28 QVBoxLayout,
25 ) 29 )
26 30
27 from EricWidgets import EricFileDialog 31 from EricWidgets import EricFileDialog
28 from EricWidgets.EricMainWindow import EricMainWindow 32 from EricWidgets.EricMainWindow import EricMainWindow
29 33
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
330 doCompile = True 340 doCompile = True
331 for line in pluginSource.splitlines(): 341 for line in pluginSource.splitlines():
332 if line.startswith("packageName"): 342 if line.startswith("packageName"):
333 tokens = line.split("=") 343 tokens = line.split("=")
334 if ( 344 if (
335 tokens[0].strip() == "packageName" and 345 tokens[0].strip() == "packageName"
336 tokens[1].strip()[1:-1] != "__core__" 346 and tokens[1].strip()[1:-1] != "__core__"
337 ): 347 ):
338 if tokens[1].strip()[0] in ['"', "'"]: 348 if tokens[1].strip()[0] in ['"', "'"]:
339 packageName = tokens[1].strip()[1:-1] 349 packageName = tokens[1].strip()[1:-1]
340 else: 350 else:
341 if tokens[1].strip() == "None": 351 if tokens[1].strip() == "None":
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)

eric ide

mercurial