--- a/PluginToolGenerateHash.py Sat Dec 28 15:09:05 2013 +0100 +++ b/PluginToolGenerateHash.py Sat Dec 28 16:30:45 2013 +0100 @@ -0,0 +1,292 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the 'Generate Hash' tool plug-in. +""" + +import os +import hashlib + +from PyQt4.QtCore import QObject, QTranslator +from PyQt4.QtGui import QMenu + +from E5Gui.E5Application import e5App +from E5Gui import E5FileDialog, E5MessageBox + +# Start-Of-Header +name = "Generate Hash Tool Plug-in" +author = "Detlev Offenbach <detlev@die-offenbachs.de>" +autoactivate = True +deactivateable = True +version = "0.1.0" +className = "ToolGenerateHashPlugin" +packageName = "ToolGenerateHash" +shortDescription = "Split, merge or convert camel case text" +longDescription = \ + """Plug-in to generate a hash for a selectable file or directory. The"""\ + """ hash string will be inserted at the cursor position of the current"""\ + """ editor. The menu will be disabled, if no editor is open.""" +needsRestart = False +pyqtApi = 2 +# End-Of-Header + +error = "" + + +class ToolGenerateHashPlugin(QObject): + """ + Class implementing the 'Generate Hash' tool plug-in. + """ + Hashes = { + "MD5": hashlib.md5, + "SHA1": hashlib.sha1, + "SHA224": hashlib.sha224, + "SHA256": hashlib.sha256, + "SHA384": hashlib.sha384, + "SHA512": hashlib.sha512, + } + + def __init__(self, ui): + """ + Constructor + + @param ui reference to the user interface object (UI.UserInterface) + """ + QObject.__init__(self, ui) + self.__ui = ui + + self.__translator = None + self.__loadTranslator() + + self.__initMenus() + + self.__editors = {} + + def activate(self): + """ + Public method to activate this plugin. + + @return tuple of None and activation status (boolean) + """ + global error + error = "" # clear previous error + + self.__ui.showMenu.connect(self.__populateMenu) + + e5App().getObject("ViewManager").editorOpenedEd.connect( + self.__editorOpened) + e5App().getObject("ViewManager").editorClosedEd.connect( + self.__editorClosed) + + for editor in e5App().getObject("ViewManager").getOpenEditors(): + self.__editorOpened(editor) + + return None, True + + def deactivate(self): + """ + Public method to deactivate this plugin. + """ + self.__ui.showMenu.disconnect(self.__populateMenu) + + e5App().getObject("ViewManager").editorOpenedEd.disconnect( + self.__editorOpened) + e5App().getObject("ViewManager").editorClosedEd.disconnect( + self.__editorClosed) + + for editor, acts in self.__editors.items(): + editor.showMenu.disconnect(self.__editorShowMenu) + menu = editor.getMenu("Tools") + if menu is not None: + for act in acts: + menu.removeAction(act) + self.__editors = {} + + def __loadTranslator(self): + """ + Private method to load the translation file. + """ + if self.__ui is not None: + loc = self.__ui.getLocale() + if loc and loc != "C": + locale_dir = os.path.join( + os.path.dirname(__file__), "ToolGenerateHash", "i18n") + translation = "generatehash_{0}".format(loc) + translator = QTranslator(None) + loaded = translator.load(translation, locale_dir) + if loaded: + self.__translator = translator + e5App().installTranslator(self.__translator) + else: + print("Warning: translation file '{0}' could not be" + " loaded.".format(translation)) + print("Using default.") + + def __initMenus(self): + """ + Private method to initialize the hash generation menus. + """ + self.__fileMenu = QMenu(self.tr("Generate File Hash")) + self.__fileMenu.addAction("MD5", self.__hashFile).setData("MD5") + self.__fileMenu.addAction("SHA1", self.__hashFile).setData("SHA1") + self.__fileMenu.addAction("SHA224", self.__hashFile).setData("SHA224") + self.__fileMenu.addAction("SHA256", self.__hashFile).setData("SHA256") + self.__fileMenu.addAction("SHA384", self.__hashFile).setData("SHA384") + self.__fileMenu.addAction("SHA512", self.__hashFile).setData("SHA512") + + self.__dirMenu = QMenu(self.tr("Generate Directory Hash")) + self.__dirMenu.addAction( + "MD5", self.__hashDirectory).setData("MD5") + self.__dirMenu.addAction( + "SHA1", self.__hashDirectory).setData("SHA1") + self.__dirMenu.addAction( + "SHA224", self.__hashDirectory).setData("SHA224") + self.__dirMenu.addAction( + "SHA256", self.__hashDirectory).setData("SHA256") + self.__dirMenu.addAction( + "SHA384", self.__hashDirectory).setData("SHA384") + self.__dirMenu.addAction( + "SHA512", self.__hashDirectory).setData("SHA512") + + def __populateMenu(self, name, menu): + """ + Private slot to populate the tools menu with our entries. + + @param name name of the menu (string) + @param menu reference to the menu to be populated (QMenu) + """ + if name != "Tools": + return + + editor = e5App().getObject("ViewManager").activeWindow() + + if not menu.isEmpty(): + menu.addSeparator() + + act = menu.addMenu(self.__fileMenu) + act.setEnabled(editor is not None) + act = menu.addMenu(self.__dirMenu) + act.setEnabled(editor is not None) + + def __editorOpened(self, editor): + """ + Private slot called, when a new editor was opened. + + @param editor reference to the new editor (QScintilla.Editor) + """ + menu = editor.getMenu("Tools") + if menu is not None: + self.__editors[editor] = [] + if not menu.isEmpty(): + act = menu.addSeparator() + self.__editors[editor].append(act) + act = menu.addMenu(self.__fileMenu) + self.__editors[editor].append(act) + act = menu.addMenu(self.__dirMenu) + self.__editors[editor].append(act) + + def __editorClosed(self, editor): + """ + Private slot called, when an editor was closed. + + @param editor reference to the editor (QScintilla.Editor) + """ + try: + del self.__editors[editor] + except KeyError: + pass + + def __insertHash(self, hashStr): + """ + Private method to insert the generated hash string. + + @param hashStr hash string (string) + """ + if hashStr: + editor = e5App().getObject("ViewManager").activeWindow() + line, index = editor.getCursorPosition() + # It should be done on this way to allow undo + editor.beginUndoAction() + editor.insertAt(hashStr, line, index) + editor.endUndoAction() + + def __hashFile(self): + """ + Private slot to generate the hash for a file. + """ + act = self.sender() + if act is None: + return + + name = E5FileDialog.getOpenFileName( + self.__ui, + self.trUtf8("Generate File Hash")) + if name: + try: + f = open(name, "rb") + hashStr = self.Hashes[act.data()](f.read()).hexdigest() + f.close() + except (IOError, OSError) as err: + E5MessageBox.critical( + self.__ui, + self.trUtf8("Generate File Hash"), + self.trUtf8("""<p>The hash for <b>{0}</b> could not""" + """ be generated.</p><p>Reason: {1}</p>""") + .format(name, str(err)) + ) + return + + self.__insertHash(hashStr) + + def __hashDirectory(self): + """ + Private slot to generate the hash for a directory. + """ + act = self.sender() + if act is None: + return + + folder = E5FileDialog.getExistingDirectory( + self.__ui, + self.trUtf8("Generate Directory Hash"), + "", + E5FileDialog.Options(E5FileDialog.Option(0))) + if folder and os.path.isdir(folder): + fails = 0 + hashes = [] + for name in os.listdir(folder): + if not name.startswith(".") and \ + os.path.isfile(os.path.join(folder, name)): + try: + f = open(os.path.join(folder, name), "rb") + hashStr = self.Hashes[act.data()](f.read()).hexdigest() + f.close() + hashes.append((name, hashStr)) + except (IOError, OSError): + fails += 1 + if fails: + E5MessageBox.critical( + self.__ui, + self.trUtf8("Generate Directory Hash"), + self.trUtf8("""<p>The hash for some files could not""" + """ be generated.</p>""") + ) + else: + editor = e5App().getObject("ViewManager").activeWindow() + line, index = editor.getCursorPosition() + indLevel = (editor.indentation(line) // + editor.indentationWidth()) + if editor.indentationsUseTabs(): + indString = '\t' + else: + indString = editor.indentationWidth() * ' ' + indent = (indLevel + 1) * indString + code = ["["] + for hash in hashes: + code.append("{0}{1},".format(indent, str(hash))) + code.append("{0}]".format(indLevel * indString)) + + self.__insertHash(os.linesep.join(code))