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