PluginToolGenerateHash.py

Wed, 14 Oct 2020 19:32:42 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 14 Oct 2020 19:32:42 +0200
changeset 47
aee34fc5bccc
parent 45
60ef6c28f7fb
child 49
6b5abf3bc568
permissions
-rw-r--r--

Changed code to use the 'open()' context manager.

# -*- coding: utf-8 -*-

# Copyright (c) 2013 - 2020 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the 'Generate Hash' tool plug-in.
"""

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.1.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)
        """
        QObject.__init__(self, 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)
        """
        try:
            del self.__editors[editor]
            if not self.__editors:
                self.__fileMenu.setEnabled(False)
                self.__dirMenu.setEnabled(False)
        except KeyError:
            pass
    
    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":
            if 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 (IOError, 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 (IOError, 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

eric ide

mercurial