14 import compileall |
14 import compileall |
15 import urllib.parse |
15 import urllib.parse |
16 import glob |
16 import glob |
17 |
17 |
18 from PyQt4.QtCore import pyqtSlot, Qt, QDir, QFileInfo |
18 from PyQt4.QtCore import pyqtSlot, Qt, QDir, QFileInfo |
19 from PyQt4.QtGui import QWidget, QDialogButtonBox, QAbstractButton, QApplication, \ |
19 from PyQt4.QtGui import QWidget, QDialogButtonBox, QAbstractButton, \ |
20 QDialog, QVBoxLayout |
20 QApplication, QDialog, QVBoxLayout |
21 |
21 |
22 from E5Gui import E5FileDialog |
22 from E5Gui import E5FileDialog |
23 from E5Gui.E5MainWindow import E5MainWindow |
23 from E5Gui.E5MainWindow import E5MainWindow |
24 |
24 |
25 from .Ui_PluginInstallDialog import Ui_PluginInstallDialog |
25 from .Ui_PluginInstallDialog import Ui_PluginInstallDialog |
53 self.__external = True |
53 self.__external = True |
54 else: |
54 else: |
55 self.__pluginManager = pluginManager |
55 self.__pluginManager = pluginManager |
56 self.__external = False |
56 self.__external = False |
57 |
57 |
58 self.__backButton = \ |
58 self.__backButton = self.buttonBox.addButton( |
59 self.buttonBox.addButton(self.trUtf8("< Back"), QDialogButtonBox.ActionRole) |
59 self.trUtf8("< Back"), QDialogButtonBox.ActionRole) |
60 self.__nextButton = \ |
60 self.__nextButton = self.buttonBox.addButton( |
61 self.buttonBox.addButton(self.trUtf8("Next >"), QDialogButtonBox.ActionRole) |
61 self.trUtf8("Next >"), QDialogButtonBox.ActionRole) |
62 self.__finishButton = \ |
62 self.__finishButton = self.buttonBox.addButton( |
63 self.buttonBox.addButton(self.trUtf8("Install"), QDialogButtonBox.ActionRole) |
63 self.trUtf8("Install"), QDialogButtonBox.ActionRole) |
64 |
64 |
65 self.__closeButton = self.buttonBox.button(QDialogButtonBox.Close) |
65 self.__closeButton = self.buttonBox.button(QDialogButtonBox.Close) |
66 self.__cancelButton = self.buttonBox.button(QDialogButtonBox.Cancel) |
66 self.__cancelButton = self.buttonBox.button(QDialogButtonBox.Cancel) |
67 |
67 |
68 userDir = self.__pluginManager.getPluginDir("user") |
68 userDir = self.__pluginManager.getPluginDir("user") |
69 if userDir is not None: |
69 if userDir is not None: |
70 self.destinationCombo.addItem(self.trUtf8("User plugins directory"), |
70 self.destinationCombo.addItem( |
|
71 self.trUtf8("User plugins directory"), |
71 userDir) |
72 userDir) |
72 |
73 |
73 globalDir = self.__pluginManager.getPluginDir("global") |
74 globalDir = self.__pluginManager.getPluginDir("global") |
74 if globalDir is not None and os.access(globalDir, os.W_OK): |
75 if globalDir is not None and os.access(globalDir, os.W_OK): |
75 self.destinationCombo.addItem(self.trUtf8("Global plugins directory"), |
76 self.destinationCombo.addItem( |
|
77 self.trUtf8("Global plugins directory"), |
76 globalDir) |
78 globalDir) |
77 |
79 |
78 self.__installedDirs = [] |
80 self.__installedDirs = [] |
79 self.__installedFiles = [] |
81 self.__installedFiles = [] |
80 |
82 |
82 |
84 |
83 downloadDir = QDir(Preferences.getPluginManager("DownloadPath")) |
85 downloadDir = QDir(Preferences.getPluginManager("DownloadPath")) |
84 for pluginFileName in pluginFileNames: |
86 for pluginFileName in pluginFileNames: |
85 fi = QFileInfo(pluginFileName) |
87 fi = QFileInfo(pluginFileName) |
86 if fi.isRelative(): |
88 if fi.isRelative(): |
87 pluginFileName = QFileInfo(downloadDir, fi.fileName()).absoluteFilePath() |
89 pluginFileName = QFileInfo( |
|
90 downloadDir, fi.fileName()).absoluteFilePath() |
88 self.archivesList.addItem(pluginFileName) |
91 self.archivesList.addItem(pluginFileName) |
89 self.archivesList.sortItems() |
92 self.archivesList.sortItems() |
90 |
93 |
91 self.__currentIndex = 0 |
94 self.__currentIndex = 0 |
92 self.__selectPage() |
95 self.__selectPage() |
132 self.__nextButton.setEnabled(False) |
135 self.__nextButton.setEnabled(False) |
133 self.__finishButton.setEnabled(True) |
136 self.__finishButton.setEnabled(True) |
134 self.__closeButton.hide() |
137 self.__closeButton.hide() |
135 self.__cancelButton.show() |
138 self.__cancelButton.show() |
136 |
139 |
137 msg = self.trUtf8("Plugin ZIP-Archives:\n{0}\n\nDestination:\n{1} ({2})")\ |
140 msg = self.trUtf8( |
|
141 "Plugin ZIP-Archives:\n{0}\n\nDestination:\n{1} ({2})")\ |
138 .format("\n".join(self.__createArchivesList()), |
142 .format("\n".join(self.__createArchivesList()), |
139 self.destinationCombo.currentText(), |
143 self.destinationCombo.currentText(), |
140 self.destinationCombo.itemData( |
144 self.destinationCombo.itemData( |
141 self.destinationCombo.currentIndex()) |
145 self.destinationCombo.currentIndex()) |
142 ) |
146 ) |
169 @pyqtSlot() |
173 @pyqtSlot() |
170 def on_archivesList_itemSelectionChanged(self): |
174 def on_archivesList_itemSelectionChanged(self): |
171 """ |
175 """ |
172 Private slot called, when the selection of the archives list changes. |
176 Private slot called, when the selection of the archives list changes. |
173 """ |
177 """ |
174 self.removeArchivesButton.setEnabled(len(self.archivesList.selectedItems()) > 0) |
178 self.removeArchivesButton.setEnabled( |
|
179 len(self.archivesList.selectedItems()) > 0) |
175 |
180 |
176 @pyqtSlot() |
181 @pyqtSlot() |
177 def on_removeArchivesButton_clicked(self): |
182 def on_removeArchivesButton_clicked(self): |
178 """ |
183 """ |
179 Private slot to remove archives from the list. |
184 Private slot to remove archives from the list. |
180 """ |
185 """ |
181 for archiveItem in self.archivesList.selectedItems(): |
186 for archiveItem in self.archivesList.selectedItems(): |
182 itm = self.archivesList.takeItem(self.archivesList.row(archiveItem)) |
187 itm = self.archivesList.takeItem( |
|
188 self.archivesList.row(archiveItem)) |
183 del itm |
189 del itm |
184 |
190 |
185 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
191 self.__nextButton.setEnabled(self.archivesList.count() > 0) |
186 |
192 |
187 @pyqtSlot(QAbstractButton) |
193 @pyqtSlot(QAbstractButton) |
210 @return flag indicating success (boolean) |
216 @return flag indicating success (boolean) |
211 """ |
217 """ |
212 res = True |
218 res = True |
213 self.summaryEdit.clear() |
219 self.summaryEdit.clear() |
214 for archive in self.__createArchivesList(): |
220 for archive in self.__createArchivesList(): |
215 self.summaryEdit.append(self.trUtf8("Installing {0} ...").format(archive)) |
221 self.summaryEdit.append( |
|
222 self.trUtf8("Installing {0} ...").format(archive)) |
216 ok, msg, restart = self.__installPlugin(archive) |
223 ok, msg, restart = self.__installPlugin(archive) |
217 res = res and ok |
224 res = res and ok |
218 if ok: |
225 if ok: |
219 self.summaryEdit.append(self.trUtf8(" ok")) |
226 self.summaryEdit.append(self.trUtf8(" ok")) |
220 else: |
227 else: |
242 of the IDE is required (boolean) |
249 of the IDE is required (boolean) |
243 """ |
250 """ |
244 installedPluginName = "" |
251 installedPluginName = "" |
245 |
252 |
246 archive = archiveFilename |
253 archive = archiveFilename |
247 destination = \ |
254 destination = self.destinationCombo.itemData( |
248 self.destinationCombo.itemData(self.destinationCombo.currentIndex()) |
255 self.destinationCombo.currentIndex()) |
249 |
256 |
250 # check if archive is a local url |
257 # check if archive is a local url |
251 url = urllib.parse.urlparse(archive) |
258 url = urllib.parse.urlparse(archive) |
252 if url[0].lower() == 'file': |
259 if url[0].lower() == 'file': |
253 archive = url[2] |
260 archive = url[2] |
254 |
261 |
255 # check, if the archive exists |
262 # check, if the archive exists |
256 if not os.path.exists(archive): |
263 if not os.path.exists(archive): |
257 return False, \ |
264 return False, \ |
258 self.trUtf8("""<p>The archive file <b>{0}</b> does not exist. """ |
265 self.trUtf8( |
259 """Aborting...</p>""").format(archive), \ |
266 """<p>The archive file <b>{0}</b> does not exist. """ |
|
267 """Aborting...</p>""").format(archive), \ |
260 False |
268 False |
261 |
269 |
262 # check, if the archive is a valid zip file |
270 # check, if the archive is a valid zip file |
263 if not zipfile.is_zipfile(archive): |
271 if not zipfile.is_zipfile(archive): |
264 return False, \ |
272 return False, \ |
265 self.trUtf8("""<p>The file <b>{0}</b> is not a valid plugin """ |
273 self.trUtf8( |
266 """ZIP-archive. Aborting...</p>""").format(archive), \ |
274 """<p>The file <b>{0}</b> is not a valid plugin """ |
|
275 """ZIP-archive. Aborting...</p>""").format(archive), \ |
267 False |
276 False |
268 |
277 |
269 # check, if the destination is writeable |
278 # check, if the destination is writeable |
270 if not os.access(destination, os.W_OK): |
279 if not os.access(destination, os.W_OK): |
271 return False, \ |
280 return False, \ |
272 self.trUtf8("""<p>The destination directory <b>{0}</b> is not """ |
281 self.trUtf8( |
273 """writeable. Aborting...</p>""").format(destination), \ |
282 """<p>The destination directory <b>{0}</b> is not """ |
|
283 """writeable. Aborting...</p>""").format(destination), \ |
274 False |
284 False |
275 |
285 |
276 zip = zipfile.ZipFile(archive, "r") |
286 zip = zipfile.ZipFile(archive, "r") |
277 |
287 |
278 # check, if the archive contains a valid plugin |
288 # check, if the archive contains a valid plugin |
285 pluginFileName = name |
295 pluginFileName = name |
286 break |
296 break |
287 |
297 |
288 if not pluginFound: |
298 if not pluginFound: |
289 return False, \ |
299 return False, \ |
290 self.trUtf8("""<p>The file <b>{0}</b> is not a valid plugin """ |
300 self.trUtf8( |
291 """ZIP-archive. Aborting...</p>""").format(archive), \ |
301 """<p>The file <b>{0}</b> is not a valid plugin """ |
|
302 """ZIP-archive. Aborting...</p>""").format(archive), \ |
292 False |
303 False |
293 |
304 |
294 # parse the plugin module's plugin header |
305 # parse the plugin module's plugin header |
295 pluginSource = Utilities.decode(zip.read(pluginFileName))[0] |
306 pluginSource = Utilities.decode(zip.read(pluginFileName))[0] |
296 packageName = "" |
307 packageName = "" |
308 else: |
319 else: |
309 if tokens[1].strip() == "None": |
320 if tokens[1].strip() == "None": |
310 packageName = "None" |
321 packageName = "None" |
311 elif line.startswith("internalPackages"): |
322 elif line.startswith("internalPackages"): |
312 tokens = line.split("=") |
323 tokens = line.split("=") |
313 token = tokens[1].strip()[1:-1] # it is a comma separated string |
324 token = tokens[1].strip()[1:-1] |
|
325 # it is a comma separated string |
314 internalPackages = [p.strip() for p in token.split(",")] |
326 internalPackages = [p.strip() for p in token.split(",")] |
315 elif line.startswith("needsRestart"): |
327 elif line.startswith("needsRestart"): |
316 tokens = line.split("=") |
328 tokens = line.split("=") |
317 needsRestart = tokens[1].strip() == "True" |
329 needsRestart = tokens[1].strip() == "True" |
318 elif line.startswith("pyqtApi"): |
330 elif line.startswith("pyqtApi"): |
328 elif line.startswith("# End-Of-Header"): |
340 elif line.startswith("# End-Of-Header"): |
329 break |
341 break |
330 |
342 |
331 if not packageName: |
343 if not packageName: |
332 return False, \ |
344 return False, \ |
333 self.trUtf8("""<p>The plugin module <b>{0}</b> does not contain """ |
345 self.trUtf8( |
334 """a 'packageName' attribute. Aborting...</p>""")\ |
346 """<p>The plugin module <b>{0}</b> does not contain """ |
|
347 """a 'packageName' attribute. Aborting...</p>""")\ |
335 .format(pluginFileName), \ |
348 .format(pluginFileName), \ |
336 False |
349 False |
337 |
350 |
338 if pyqtApi < 2: |
351 if pyqtApi < 2: |
339 return False, \ |
352 return False, \ |
340 self.trUtf8("""<p>The plugin module <b>{0}</b> does not conform""" |
353 self.trUtf8( |
341 """ with the PyQt v2 API. Aborting...</p>""")\ |
354 """<p>The plugin module <b>{0}</b> does not conform""" |
|
355 """ with the PyQt v2 API. Aborting...</p>""")\ |
342 .format(pluginFileName), \ |
356 .format(pluginFileName), \ |
343 False |
357 False |
344 |
358 |
345 # check, if it is a plugin, that collides with others |
359 # check, if it is a plugin, that collides with others |
346 if not os.path.exists(os.path.join(destination, pluginFileName)) and \ |
360 if not os.path.exists(os.path.join(destination, pluginFileName)) and \ |
362 False |
376 False |
363 |
377 |
364 activatePlugin = False |
378 activatePlugin = False |
365 if not self.__external: |
379 if not self.__external: |
366 activatePlugin = \ |
380 activatePlugin = \ |
367 not self.__pluginManager.isPluginLoaded(installedPluginName) or \ |
381 not self.__pluginManager.isPluginLoaded( |
|
382 installedPluginName) or \ |
368 (self.__pluginManager.isPluginLoaded(installedPluginName) and \ |
383 (self.__pluginManager.isPluginLoaded(installedPluginName) and \ |
369 self.__pluginManager.isPluginActive(installedPluginName)) |
384 self.__pluginManager.isPluginActive(installedPluginName)) |
370 # try to unload a plugin with the same name |
385 # try to unload a plugin with the same name |
371 self.__pluginManager.unloadPlugin(installedPluginName) |
386 self.__pluginManager.unloadPlugin(installedPluginName) |
372 |
387 |
420 f.close() |
435 f.close() |
421 self.__installedFiles.append(outname) |
436 self.__installedFiles.append(outname) |
422 except os.error as why: |
437 except os.error as why: |
423 self.__rollback() |
438 self.__rollback() |
424 return False, \ |
439 return False, \ |
425 self.trUtf8("Error installing plugin. Reason: {0}").format(str(why)), \ |
440 self.trUtf8( |
|
441 "Error installing plugin. Reason: {0}").format(str(why)), \ |
426 False |
442 False |
427 except IOError as why: |
443 except IOError as why: |
428 self.__rollback() |
444 self.__rollback() |
429 return False, \ |
445 return False, \ |
430 self.trUtf8("Error installing plugin. Reason: {0}").format(str(why)), \ |
446 self.trUtf8( |
|
447 "Error installing plugin. Reason: {0}").format(str(why)), \ |
431 False |
448 False |
432 except OSError as why: |
449 except OSError as why: |
433 self.__rollback() |
450 self.__rollback() |
434 return False, \ |
451 return False, \ |
435 self.trUtf8("Error installing plugin. Reason: {0}").format(str(why)), \ |
452 self.trUtf8( |
|
453 "Error installing plugin. Reason: {0}").format(str(why)), \ |
436 False |
454 False |
437 except: |
455 except: |
438 print("Unspecific exception installing plugin.", file=sys.stderr) |
456 print("Unspecific exception installing plugin.", file=sys.stderr) |
439 self.__rollback() |
457 self.__rollback() |
440 return False, \ |
458 return False, \ |
441 self.trUtf8("Unspecific exception installing plugin."), \ |
459 self.trUtf8("Unspecific exception installing plugin."), \ |
442 False |
460 False |
443 |
461 |
444 # now compile the plugins |
462 # now compile the plugins |
445 if doCompile: |
463 if doCompile: |
446 compileall.compile_dir(os.path.join(destination, packageName), quiet=True) |
464 compileall.compile_dir( |
447 compileall.compile_file(os.path.join(destination, pluginFileName), quiet=True) |
465 os.path.join(destination, packageName), quiet=True) |
|
466 compileall.compile_file( |
|
467 os.path.join(destination, pluginFileName), quiet=True) |
448 |
468 |
449 if not self.__external: |
469 if not self.__external: |
450 # now load and activate the plugin |
470 # now load and activate the plugin |
451 self.__pluginManager.loadPlugin(installedPluginName, destination, reload_) |
471 self.__pluginManager.loadPlugin(installedPluginName, destination, |
|
472 reload_) |
452 if activatePlugin: |
473 if activatePlugin: |
453 self.__pluginManager.activatePlugin(installedPluginName) |
474 self.__pluginManager.activatePlugin(installedPluginName) |
454 |
475 |
455 return True, "", needsRestart |
476 return True, "", needsRestart |
456 |
477 |
478 head, tail = os.path.split(name) |
499 head, tail = os.path.split(name) |
479 if not tail: |
500 if not tail: |
480 head, tail = os.path.split(head) |
501 head, tail = os.path.split(head) |
481 if head and tail and not os.path.exists(head): |
502 if head and tail and not os.path.exists(head): |
482 self.__makedirs(head, mode) |
503 self.__makedirs(head, mode) |
483 if tail == os.curdir: # xxx/newdir/. exists if xxx/newdir exists |
504 if tail == os.curdir: |
|
505 # xxx/newdir/. exists if xxx/newdir exists |
484 return |
506 return |
485 os.mkdir(name, mode) |
507 os.mkdir(name, mode) |
486 self.__installedDirs.append(name) |
508 self.__installedDirs.append(name) |
487 |
509 |
488 def __uninstallPackage(self, destination, pluginFileName, packageName): |
510 def __uninstallPackage(self, destination, pluginFileName, packageName): |
510 |
532 |
511 fnamec = "{0}c".format(pluginFile) |
533 fnamec = "{0}c".format(pluginFile) |
512 if os.path.exists(fnamec): |
534 if os.path.exists(fnamec): |
513 os.remove(fnamec) |
535 os.remove(fnamec) |
514 |
536 |
515 pluginDirCache = os.path.join(os.path.dirname(pluginFile), "__pycache__") |
537 pluginDirCache = os.path.join( |
|
538 os.path.dirname(pluginFile), "__pycache__") |
516 if os.path.exists(pluginDirCache): |
539 if os.path.exists(pluginDirCache): |
517 pluginFileName = os.path.splitext(os.path.basename(pluginFile))[0] |
540 pluginFileName = os.path.splitext( |
|
541 os.path.basename(pluginFile))[0] |
518 for fnameo in glob.glob( |
542 for fnameo in glob.glob( |
519 os.path.join(pluginDirCache, "{0}*.pyo".format(pluginFileName))): |
543 os.path.join(pluginDirCache, |
|
544 "{0}*.pyo".format(pluginFileName))): |
520 os.remove(fnameo) |
545 os.remove(fnameo) |
521 for fnamec in glob.glob( |
546 for fnamec in glob.glob( |
522 os.path.join(pluginDirCache, "{0}*.pyc".format(pluginFileName))): |
547 os.path.join(pluginDirCache, |
|
548 "{0}*.pyc".format(pluginFileName))): |
523 os.remove(fnamec) |
549 os.remove(fnamec) |
524 |
550 |
525 os.remove(pluginFile) |
551 os.remove(pluginFile) |
526 except (IOError, OSError, os.error): |
552 except (IOError, OSError, os.error): |
527 # ignore some exceptions |
553 # ignore some exceptions |
581 self.cw = PluginInstallWidget(None, pluginFileNames, self) |
607 self.cw = PluginInstallWidget(None, pluginFileNames, self) |
582 size = self.cw.size() |
608 size = self.cw.size() |
583 self.setCentralWidget(self.cw) |
609 self.setCentralWidget(self.cw) |
584 self.resize(size) |
610 self.resize(size) |
585 |
611 |
586 self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) |
612 self.setStyle(Preferences.getUI("Style"), |
|
613 Preferences.getUI("StyleSheet")) |
587 |
614 |
588 self.cw.buttonBox.accepted[()].connect(self.close) |
615 self.cw.buttonBox.accepted[()].connect(self.close) |
589 self.cw.buttonBox.rejected[()].connect(self.close) |
616 self.cw.buttonBox.rejected[()].connect(self.close) |