|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the 'Generate Hash' tool plug-in. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import hashlib |
|
12 |
|
13 from PyQt4.QtCore import QObject, QTranslator |
|
14 from PyQt4.QtGui import QMenu |
|
15 |
|
16 from E5Gui.E5Application import e5App |
|
17 from E5Gui import E5FileDialog, E5MessageBox |
|
18 |
|
19 # Start-Of-Header |
|
20 name = "Generate Hash Tool Plug-in" |
|
21 author = "Detlev Offenbach <detlev@die-offenbachs.de>" |
|
22 autoactivate = True |
|
23 deactivateable = True |
|
24 version = "0.1.0" |
|
25 className = "ToolGenerateHashPlugin" |
|
26 packageName = "ToolGenerateHash" |
|
27 shortDescription = "Split, merge or convert camel case text" |
|
28 longDescription = \ |
|
29 """Plug-in to generate a hash for a selectable file or directory. The"""\ |
|
30 """ hash string will be inserted at the cursor position of the current"""\ |
|
31 """ editor. The menu will be disabled, if no editor is open.""" |
|
32 needsRestart = False |
|
33 pyqtApi = 2 |
|
34 # End-Of-Header |
|
35 |
|
36 error = "" |
|
37 |
|
38 |
|
39 class ToolGenerateHashPlugin(QObject): |
|
40 """ |
|
41 Class implementing the 'Generate Hash' tool plug-in. |
|
42 """ |
|
43 Hashes = { |
|
44 "MD5": hashlib.md5, |
|
45 "SHA1": hashlib.sha1, |
|
46 "SHA224": hashlib.sha224, |
|
47 "SHA256": hashlib.sha256, |
|
48 "SHA384": hashlib.sha384, |
|
49 "SHA512": hashlib.sha512, |
|
50 } |
|
51 |
|
52 def __init__(self, ui): |
|
53 """ |
|
54 Constructor |
|
55 |
|
56 @param ui reference to the user interface object (UI.UserInterface) |
|
57 """ |
|
58 QObject.__init__(self, ui) |
|
59 self.__ui = ui |
|
60 |
|
61 self.__translator = None |
|
62 self.__loadTranslator() |
|
63 |
|
64 self.__initMenus() |
|
65 |
|
66 self.__editors = {} |
|
67 |
|
68 def activate(self): |
|
69 """ |
|
70 Public method to activate this plugin. |
|
71 |
|
72 @return tuple of None and activation status (boolean) |
|
73 """ |
|
74 global error |
|
75 error = "" # clear previous error |
|
76 |
|
77 self.__ui.showMenu.connect(self.__populateMenu) |
|
78 |
|
79 e5App().getObject("ViewManager").editorOpenedEd.connect( |
|
80 self.__editorOpened) |
|
81 e5App().getObject("ViewManager").editorClosedEd.connect( |
|
82 self.__editorClosed) |
|
83 |
|
84 for editor in e5App().getObject("ViewManager").getOpenEditors(): |
|
85 self.__editorOpened(editor) |
|
86 |
|
87 return None, True |
|
88 |
|
89 def deactivate(self): |
|
90 """ |
|
91 Public method to deactivate this plugin. |
|
92 """ |
|
93 self.__ui.showMenu.disconnect(self.__populateMenu) |
|
94 |
|
95 e5App().getObject("ViewManager").editorOpenedEd.disconnect( |
|
96 self.__editorOpened) |
|
97 e5App().getObject("ViewManager").editorClosedEd.disconnect( |
|
98 self.__editorClosed) |
|
99 |
|
100 for editor, acts in self.__editors.items(): |
|
101 editor.showMenu.disconnect(self.__editorShowMenu) |
|
102 menu = editor.getMenu("Tools") |
|
103 if menu is not None: |
|
104 for act in acts: |
|
105 menu.removeAction(act) |
|
106 self.__editors = {} |
|
107 |
|
108 def __loadTranslator(self): |
|
109 """ |
|
110 Private method to load the translation file. |
|
111 """ |
|
112 if self.__ui is not None: |
|
113 loc = self.__ui.getLocale() |
|
114 if loc and loc != "C": |
|
115 locale_dir = os.path.join( |
|
116 os.path.dirname(__file__), "ToolGenerateHash", "i18n") |
|
117 translation = "generatehash_{0}".format(loc) |
|
118 translator = QTranslator(None) |
|
119 loaded = translator.load(translation, locale_dir) |
|
120 if loaded: |
|
121 self.__translator = translator |
|
122 e5App().installTranslator(self.__translator) |
|
123 else: |
|
124 print("Warning: translation file '{0}' could not be" |
|
125 " loaded.".format(translation)) |
|
126 print("Using default.") |
|
127 |
|
128 def __initMenus(self): |
|
129 """ |
|
130 Private method to initialize the hash generation menus. |
|
131 """ |
|
132 self.__fileMenu = QMenu(self.tr("Generate File Hash")) |
|
133 self.__fileMenu.addAction("MD5", self.__hashFile).setData("MD5") |
|
134 self.__fileMenu.addAction("SHA1", self.__hashFile).setData("SHA1") |
|
135 self.__fileMenu.addAction("SHA224", self.__hashFile).setData("SHA224") |
|
136 self.__fileMenu.addAction("SHA256", self.__hashFile).setData("SHA256") |
|
137 self.__fileMenu.addAction("SHA384", self.__hashFile).setData("SHA384") |
|
138 self.__fileMenu.addAction("SHA512", self.__hashFile).setData("SHA512") |
|
139 |
|
140 self.__dirMenu = QMenu(self.tr("Generate Directory Hash")) |
|
141 self.__dirMenu.addAction( |
|
142 "MD5", self.__hashDirectory).setData("MD5") |
|
143 self.__dirMenu.addAction( |
|
144 "SHA1", self.__hashDirectory).setData("SHA1") |
|
145 self.__dirMenu.addAction( |
|
146 "SHA224", self.__hashDirectory).setData("SHA224") |
|
147 self.__dirMenu.addAction( |
|
148 "SHA256", self.__hashDirectory).setData("SHA256") |
|
149 self.__dirMenu.addAction( |
|
150 "SHA384", self.__hashDirectory).setData("SHA384") |
|
151 self.__dirMenu.addAction( |
|
152 "SHA512", self.__hashDirectory).setData("SHA512") |
|
153 |
|
154 def __populateMenu(self, name, menu): |
|
155 """ |
|
156 Private slot to populate the tools menu with our entries. |
|
157 |
|
158 @param name name of the menu (string) |
|
159 @param menu reference to the menu to be populated (QMenu) |
|
160 """ |
|
161 if name != "Tools": |
|
162 return |
|
163 |
|
164 editor = e5App().getObject("ViewManager").activeWindow() |
|
165 |
|
166 if not menu.isEmpty(): |
|
167 menu.addSeparator() |
|
168 |
|
169 act = menu.addMenu(self.__fileMenu) |
|
170 act.setEnabled(editor is not None) |
|
171 act = menu.addMenu(self.__dirMenu) |
|
172 act.setEnabled(editor is not None) |
|
173 |
|
174 def __editorOpened(self, editor): |
|
175 """ |
|
176 Private slot called, when a new editor was opened. |
|
177 |
|
178 @param editor reference to the new editor (QScintilla.Editor) |
|
179 """ |
|
180 menu = editor.getMenu("Tools") |
|
181 if menu is not None: |
|
182 self.__editors[editor] = [] |
|
183 if not menu.isEmpty(): |
|
184 act = menu.addSeparator() |
|
185 self.__editors[editor].append(act) |
|
186 act = menu.addMenu(self.__fileMenu) |
|
187 self.__editors[editor].append(act) |
|
188 act = menu.addMenu(self.__dirMenu) |
|
189 self.__editors[editor].append(act) |
|
190 |
|
191 def __editorClosed(self, editor): |
|
192 """ |
|
193 Private slot called, when an editor was closed. |
|
194 |
|
195 @param editor reference to the editor (QScintilla.Editor) |
|
196 """ |
|
197 try: |
|
198 del self.__editors[editor] |
|
199 except KeyError: |
|
200 pass |
|
201 |
|
202 def __insertHash(self, hashStr): |
|
203 """ |
|
204 Private method to insert the generated hash string. |
|
205 |
|
206 @param hashStr hash string (string) |
|
207 """ |
|
208 if hashStr: |
|
209 editor = e5App().getObject("ViewManager").activeWindow() |
|
210 line, index = editor.getCursorPosition() |
|
211 # It should be done on this way to allow undo |
|
212 editor.beginUndoAction() |
|
213 editor.insertAt(hashStr, line, index) |
|
214 editor.endUndoAction() |
|
215 |
|
216 def __hashFile(self): |
|
217 """ |
|
218 Private slot to generate the hash for a file. |
|
219 """ |
|
220 act = self.sender() |
|
221 if act is None: |
|
222 return |
|
223 |
|
224 name = E5FileDialog.getOpenFileName( |
|
225 self.__ui, |
|
226 self.trUtf8("Generate File Hash")) |
|
227 if name: |
|
228 try: |
|
229 f = open(name, "rb") |
|
230 hashStr = self.Hashes[act.data()](f.read()).hexdigest() |
|
231 f.close() |
|
232 except (IOError, OSError) as err: |
|
233 E5MessageBox.critical( |
|
234 self.__ui, |
|
235 self.trUtf8("Generate File Hash"), |
|
236 self.trUtf8("""<p>The hash for <b>{0}</b> could not""" |
|
237 """ be generated.</p><p>Reason: {1}</p>""") |
|
238 .format(name, str(err)) |
|
239 ) |
|
240 return |
|
241 |
|
242 self.__insertHash(hashStr) |
|
243 |
|
244 def __hashDirectory(self): |
|
245 """ |
|
246 Private slot to generate the hash for a directory. |
|
247 """ |
|
248 act = self.sender() |
|
249 if act is None: |
|
250 return |
|
251 |
|
252 folder = E5FileDialog.getExistingDirectory( |
|
253 self.__ui, |
|
254 self.trUtf8("Generate Directory Hash"), |
|
255 "", |
|
256 E5FileDialog.Options(E5FileDialog.Option(0))) |
|
257 if folder and os.path.isdir(folder): |
|
258 fails = 0 |
|
259 hashes = [] |
|
260 for name in os.listdir(folder): |
|
261 if not name.startswith(".") and \ |
|
262 os.path.isfile(os.path.join(folder, name)): |
|
263 try: |
|
264 f = open(os.path.join(folder, name), "rb") |
|
265 hashStr = self.Hashes[act.data()](f.read()).hexdigest() |
|
266 f.close() |
|
267 hashes.append((name, hashStr)) |
|
268 except (IOError, OSError): |
|
269 fails += 1 |
|
270 if fails: |
|
271 E5MessageBox.critical( |
|
272 self.__ui, |
|
273 self.trUtf8("Generate Directory Hash"), |
|
274 self.trUtf8("""<p>The hash for some files could not""" |
|
275 """ be generated.</p>""") |
|
276 ) |
|
277 else: |
|
278 editor = e5App().getObject("ViewManager").activeWindow() |
|
279 line, index = editor.getCursorPosition() |
|
280 indLevel = (editor.indentation(line) // |
|
281 editor.indentationWidth()) |
|
282 if editor.indentationsUseTabs(): |
|
283 indString = '\t' |
|
284 else: |
|
285 indString = editor.indentationWidth() * ' ' |
|
286 indent = (indLevel + 1) * indString |
|
287 code = ["["] |
|
288 for hash in hashes: |
|
289 code.append("{0}{1},".format(indent, str(hash))) |
|
290 code.append("{0}]".format(indLevel * indString)) |
|
291 |
|
292 self.__insertHash(os.linesep.join(code)) |