Mon, 26 Apr 2021 19:32:47 +0200
Created global tag <release-3.2.0>.
# -*- coding: utf-8 -*- # Copyright (c) 2013 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the 'Generate Hash' tool plug-in. """ import contextlib import os import hashlib from PyQt5.QtCore import QObject, QTranslator from PyQt5.QtWidgets 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 = "3.2.0" className = "ToolGenerateHashPlugin" packageName = "ToolGenerateHash" shortDescription = "Generate a hash for a selectable file or directory" 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) """ super().__init__(ui) self.__ui = ui self.__translator = None self.__loadTranslator() self.__initMenus() self.__editors = {} self.__mainActions = [] 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) menu = self.__ui.getMenu("plugin_tools") if menu is not None: if not menu.isEmpty(): act = menu.addSeparator() self.__mainActions.append(act) act = menu.addMenu(self.__fileMenu) self.__mainActions.append(act) act = menu.addMenu(self.__dirMenu) self.__mainActions.append(act) 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) menu = self.__ui.getMenu("plugin_tools") if menu is not None: for act in self.__mainActions: menu.removeAction(act) self.__mainActions = [] 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.__fileMenu.setEnabled(False) 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") self.__dirMenu.setEnabled(False) 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 not in ["Tools", "PluginTools"]: return editor = e5App().getObject("ViewManager").activeWindow() if name == "Tools": 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) elif name == "PluginTools" and self.__mainActions: self.__mainActions[-2].setEnabled(editor is not None) self.__mainActions[-1].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) editor.showMenu.connect(self.__editorShowMenu) self.__fileMenu.setEnabled(True) self.__dirMenu.setEnabled(True) def __editorClosed(self, editor): """ Private slot called, when an editor was closed. @param editor reference to the editor (QScintilla.Editor) """ with contextlib.suppress(KeyError): del self.__editors[editor] if not self.__editors: self.__fileMenu.setEnabled(False) self.__dirMenu.setEnabled(False) def __editorShowMenu(self, menuName, menu, editor): """ Private slot called, when the the editor context menu or a submenu is about to be shown. @param menuName name of the menu to be shown (string) @param menu reference to the menu (QMenu) @param editor reference to the editor """ if ( menuName == "Tools" and self.__fileMenu.menuAction() not in menu.actions() ): # Re-add our menu 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) self.__fileMenu.setEnabled(True) self.__dirMenu.setEnabled(True) 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.tr("Generate File Hash")) if name: try: with open(name, "rb") as f: hashStr = self.Hashes[act.data()](f.read()).hexdigest() except OSError as err: E5MessageBox.critical( self.__ui, self.tr("Generate File Hash"), self.tr("""<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.tr("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: with open(os.path.join(folder, name), "rb") as f: hashStr = self.Hashes[act.data()]( f.read()).hexdigest() hashes.append((name, hashStr)) except OSError: fails += 1 if fails: E5MessageBox.critical( self.__ui, self.tr("Generate Directory Hash"), self.tr("""<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 name, hashStr in hashes: code.append("{0}('{1}', '{2}'),".format( indent, name, hashStr)) code.append("{0}]".format(indLevel * indString)) self.__insertHash(os.linesep.join(code)) # # eflag: noqa = M801