MoleditPy 3.0.0b1__tar.gz → 3.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/PKG-INFO +1 -1
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/pyproject.toml +1 -1
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/MoleditPy.egg-info/PKG-INFO +1 -1
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/MoleditPy.egg-info/SOURCES.txt +0 -6
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/plugins/plugin_interface.py +44 -22
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/plugins/plugin_manager.py +0 -1
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/plugins/plugin_manager_window.py +56 -56
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/angle_dialog.py +404 -404
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/app_state.py +0 -1
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/bond_item.py +0 -7
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/bond_length_dialog.py +351 -351
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/custom_interactor_style.py +0 -6
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/dialog_logic.py +0 -4
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/edit_actions_logic.py +1 -1
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/io_logic.py +10 -6
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/main_window.py +7 -2
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/main_window_init.py +34 -45
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/molecular_scene_handler.py +1 -3
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/molecule_scene.py +0 -4
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/settings_dialog.py +5 -2
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +2 -4
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/ui_manager.py +51 -10
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/user_template_dialog.py +1 -1
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/view_3d_logic.py +3 -3
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/zoomable_view.py +0 -3
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/utils/constants.py +1 -1
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/utils/default_settings.py +85 -85
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/LICENSE +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/README.md +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/setup.cfg +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/MoleditPy.egg-info/entry_points.txt +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/MoleditPy.egg-info/requires.txt +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/MoleditPy.egg-info/top_level.txt +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/__init__.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/__main__.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/assets/file_icon.ico +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/assets/icon.icns +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/core/__init__.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/core/mol_geometry.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/core/molecular_data.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/main.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/plugins/__init__.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/__init__.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/about_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/align_plane_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/alignment_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/analysis_window.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/atom_item.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/base_picking_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/calculation_worker.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/color_settings_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/compute_logic.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/constrained_optimization_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/custom_qt_interactor.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/dialog_3d_picking_mixin.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/dihedral_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/edit_3d_logic.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/export_logic.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/geometry_base_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/mirror_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/move_group_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/periodic_table_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/planarize_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/settings_tabs/__init__.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/sip_isdeleted_safe.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/string_importers.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/template_preview_item.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/template_preview_view.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/ui/translation_dialog.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/utils/__init__.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy-3.0.0b1 → moleditpy-3.0.2}/src/moleditpy/utils/system_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -10,12 +10,6 @@ src/MoleditPy.egg-info/top_level.txt
|
|
|
10
10
|
src/moleditpy/__init__.py
|
|
11
11
|
src/moleditpy/__main__.py
|
|
12
12
|
src/moleditpy/main.py
|
|
13
|
-
src/moleditpy.egg-info/PKG-INFO
|
|
14
|
-
src/moleditpy.egg-info/SOURCES.txt
|
|
15
|
-
src/moleditpy.egg-info/dependency_links.txt
|
|
16
|
-
src/moleditpy.egg-info/entry_points.txt
|
|
17
|
-
src/moleditpy.egg-info/requires.txt
|
|
18
|
-
src/moleditpy.egg-info/top_level.txt
|
|
19
13
|
src/moleditpy/assets/file_icon.ico
|
|
20
14
|
src/moleditpy/assets/icon.icns
|
|
21
15
|
src/moleditpy/assets/icon.ico
|
|
@@ -119,12 +119,6 @@ class PluginContext:
|
|
|
119
119
|
"""
|
|
120
120
|
return Plugin3DController(self._manager.get_main_window())
|
|
121
121
|
|
|
122
|
-
def show_status_message(self, message: str, timeout: int = 3000) -> None:
|
|
123
|
-
"""
|
|
124
|
-
Display a message in the application status bar.
|
|
125
|
-
"""
|
|
126
|
-
self._manager.show_status_message(message, timeout)
|
|
127
|
-
|
|
128
122
|
def push_undo_checkpoint(self) -> None:
|
|
129
123
|
"""
|
|
130
124
|
Create an undo checkpoint for the current state.
|
|
@@ -133,18 +127,6 @@ class PluginContext:
|
|
|
133
127
|
"""
|
|
134
128
|
self._manager.push_undo_checkpoint()
|
|
135
129
|
|
|
136
|
-
def refresh_3d_view(self) -> None:
|
|
137
|
-
"""
|
|
138
|
-
Force a refresh (re-render) of the 3D scene.
|
|
139
|
-
"""
|
|
140
|
-
self._manager.refresh_3d_view()
|
|
141
|
-
|
|
142
|
-
def reset_3d_camera(self) -> None:
|
|
143
|
-
"""
|
|
144
|
-
Resets the 3D camera to fit the current molecule.
|
|
145
|
-
"""
|
|
146
|
-
self._manager.reset_3d_camera()
|
|
147
|
-
|
|
148
130
|
def get_selected_atom_indices(self) -> List[int]:
|
|
149
131
|
"""
|
|
150
132
|
Returns a list of RDKit atom indices currently selected in the 2D or 3D view.
|
|
@@ -173,21 +155,30 @@ class PluginContext:
|
|
|
173
155
|
"""
|
|
174
156
|
return self._manager.get_main_window()
|
|
175
157
|
|
|
158
|
+
def show_status_message(self, message: str, timeout: int = 3000) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Display a temporary message in the status bar of the main window.
|
|
161
|
+
"""
|
|
162
|
+
self._manager.show_status_message(message, timeout)
|
|
163
|
+
|
|
176
164
|
@property
|
|
177
165
|
def current_mol(self) -> Any:
|
|
178
166
|
"""
|
|
179
167
|
Get or set the current molecule (RDKit Mol object). Shortcut for current_molecule.
|
|
180
168
|
"""
|
|
181
169
|
mw = self.get_main_window()
|
|
182
|
-
return
|
|
170
|
+
return (
|
|
171
|
+
mw.view_3d_manager.current_mol
|
|
172
|
+
if mw and hasattr(mw, "view_3d_manager")
|
|
173
|
+
else None
|
|
174
|
+
)
|
|
183
175
|
|
|
184
176
|
@current_mol.setter
|
|
185
177
|
def current_mol(self, mol: Any):
|
|
186
178
|
mw = self.get_main_window()
|
|
187
179
|
if mw and hasattr(mw, "view_3d_manager"):
|
|
188
180
|
mw.view_3d_manager.current_mol = mol
|
|
189
|
-
|
|
190
|
-
mw.view_3d_manager.draw_molecule_3d(mol)
|
|
181
|
+
mw.view_3d_manager.draw_molecule_3d(mol)
|
|
191
182
|
|
|
192
183
|
@property
|
|
193
184
|
def current_molecule(self) -> Any:
|
|
@@ -204,7 +195,11 @@ class PluginContext:
|
|
|
204
195
|
Returns the PyVista plotter from the MainWindow.
|
|
205
196
|
"""
|
|
206
197
|
mw = self.get_main_window()
|
|
207
|
-
return
|
|
198
|
+
return (
|
|
199
|
+
mw.view_3d_manager.plotter
|
|
200
|
+
if mw and hasattr(mw, "view_3d_manager")
|
|
201
|
+
else None
|
|
202
|
+
)
|
|
208
203
|
|
|
209
204
|
@property
|
|
210
205
|
def scene(self) -> Any:
|
|
@@ -214,6 +209,33 @@ class PluginContext:
|
|
|
214
209
|
mw = self.get_main_window()
|
|
215
210
|
return mw.init_manager.scene if mw and hasattr(mw, "init_manager") else None
|
|
216
211
|
|
|
212
|
+
def draw_molecule_3d(self, mol: Any) -> None:
|
|
213
|
+
"""Draw a molecule in the 3D scene (Direct manager call)."""
|
|
214
|
+
mw = self.get_main_window()
|
|
215
|
+
if mw and hasattr(mw, "view_3d_manager"):
|
|
216
|
+
mw.view_3d_manager.draw_molecule_3d(mol)
|
|
217
|
+
|
|
218
|
+
def refresh_3d_view(self) -> None:
|
|
219
|
+
"""Force the 3D window to redraw using the current molecule."""
|
|
220
|
+
mw = self.get_main_window()
|
|
221
|
+
if mw and hasattr(mw, "view_3d_manager"):
|
|
222
|
+
mol = getattr(mw.view_3d_manager, "current_mol", None)
|
|
223
|
+
if mol:
|
|
224
|
+
mw.view_3d_manager.draw_molecule_3d(mol)
|
|
225
|
+
else:
|
|
226
|
+
# Also redraw/clear plotter if no molecule
|
|
227
|
+
if (
|
|
228
|
+
hasattr(mw.view_3d_manager, "plotter")
|
|
229
|
+
and mw.view_3d_manager.plotter
|
|
230
|
+
):
|
|
231
|
+
mw.view_3d_manager.plotter.render()
|
|
232
|
+
|
|
233
|
+
def reset_3d_camera(self) -> None:
|
|
234
|
+
"""Zoom in and re-center the 3D viewport to fit the current molecule."""
|
|
235
|
+
mw = self.get_main_window()
|
|
236
|
+
if mw and hasattr(mw, "view_3d_manager") and mw.view_3d_manager.plotter:
|
|
237
|
+
mw.view_3d_manager.plotter.reset_camera()
|
|
238
|
+
|
|
217
239
|
def add_export_action(self, label: str, callback: Callable):
|
|
218
240
|
"""
|
|
219
241
|
Register a custom export action.
|
|
@@ -514,7 +514,6 @@ class PluginManager:
|
|
|
514
514
|
|
|
515
515
|
# Check 2D selection
|
|
516
516
|
try:
|
|
517
|
-
from .plugin_interface import PluginContext
|
|
518
517
|
# We need to access the scene items.
|
|
519
518
|
# In MoleditPy, atoms in the scene are AtomItem objects which have an 'atom_id'.
|
|
520
519
|
# These atom_ids map to entries in state_manager.data.atoms.
|
|
@@ -10,9 +10,9 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
|
10
10
|
DOI: 10.5281/zenodo.17268532
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
import os
|
|
14
|
-
import shutil
|
|
15
|
-
import hashlib
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
import hashlib
|
|
16
16
|
|
|
17
17
|
from PyQt6.QtCore import Qt, QUrl
|
|
18
18
|
from PyQt6.QtGui import QDesktopServices, QDragEnterEvent, QDropEvent
|
|
@@ -258,10 +258,10 @@ class PluginManagerWindow(QDialog):
|
|
|
258
258
|
is_valid = True
|
|
259
259
|
is_folder = True
|
|
260
260
|
|
|
261
|
-
if is_valid:
|
|
262
|
-
sha256_value = self._compute_sha256(file_path)
|
|
263
|
-
# Extract info and confirm
|
|
264
|
-
info = {
|
|
261
|
+
if is_valid:
|
|
262
|
+
sha256_value = self._compute_sha256(file_path)
|
|
263
|
+
# Extract info and confirm
|
|
264
|
+
info = {
|
|
265
265
|
"name": os.path.basename(file_path),
|
|
266
266
|
"version": "Unknown",
|
|
267
267
|
"author": "Unknown",
|
|
@@ -281,15 +281,15 @@ class PluginManagerWindow(QDialog):
|
|
|
281
281
|
elif file_path.endswith(".py"):
|
|
282
282
|
info = self.plugin_manager.get_plugin_info_safe(file_path)
|
|
283
283
|
|
|
284
|
-
msg = (
|
|
285
|
-
f"Do you want to install this plugin?\n\n"
|
|
286
|
-
f"Name: {info['name']}\n"
|
|
287
|
-
f"Author: {info['author']}\n"
|
|
288
|
-
f"Version: {info['version']}\n"
|
|
289
|
-
f"Description: {info['description']}\n\n"
|
|
290
|
-
f"File: {os.path.basename(file_path)}\n"
|
|
291
|
-
f"SHA-256: {sha256_value}"
|
|
292
|
-
)
|
|
284
|
+
msg = (
|
|
285
|
+
f"Do you want to install this plugin?\n\n"
|
|
286
|
+
f"Name: {info['name']}\n"
|
|
287
|
+
f"Author: {info['author']}\n"
|
|
288
|
+
f"Version: {info['version']}\n"
|
|
289
|
+
f"Description: {info['description']}\n\n"
|
|
290
|
+
f"File: {os.path.basename(file_path)}\n"
|
|
291
|
+
f"SHA-256: {sha256_value}"
|
|
292
|
+
)
|
|
293
293
|
|
|
294
294
|
reply = QMessageBox.question(
|
|
295
295
|
self,
|
|
@@ -310,43 +310,43 @@ class PluginManagerWindow(QDialog):
|
|
|
310
310
|
summary = ""
|
|
311
311
|
if files_installed:
|
|
312
312
|
summary += "Installed:\n" + "\n".join(files_installed) + "\n\n"
|
|
313
|
-
if errors:
|
|
314
|
-
summary += "Errors:\n" + "\n".join(errors)
|
|
315
|
-
|
|
316
|
-
QMessageBox.information(self, "Plugin Installation", summary)
|
|
317
|
-
|
|
318
|
-
def _compute_sha256(self, path):
|
|
319
|
-
if os.path.isfile(path):
|
|
320
|
-
return self._sha256_for_file(path)
|
|
321
|
-
if os.path.isdir(path):
|
|
322
|
-
return self._sha256_for_directory(path)
|
|
323
|
-
return "N/A"
|
|
324
|
-
|
|
325
|
-
def _sha256_for_file(self, path):
|
|
326
|
-
hasher = hashlib.sha256()
|
|
327
|
-
try:
|
|
328
|
-
with open(path, "rb") as f:
|
|
329
|
-
for chunk in iter(lambda: f.read(8192), b""):
|
|
330
|
-
hasher.update(chunk)
|
|
331
|
-
return hasher.hexdigest()
|
|
332
|
-
except (AttributeError, OSError, RuntimeError, ValueError, TypeError):
|
|
333
|
-
return "N/A"
|
|
334
|
-
|
|
335
|
-
def _sha256_for_directory(self, dir_path):
|
|
336
|
-
hasher = hashlib.sha256()
|
|
337
|
-
try:
|
|
338
|
-
root = os.path.abspath(dir_path)
|
|
339
|
-
for current_root, _dirs, files in os.walk(root):
|
|
340
|
-
rel_root = os.path.relpath(current_root, root)
|
|
341
|
-
for filename in sorted(files):
|
|
342
|
-
file_path = os.path.join(current_root, filename)
|
|
343
|
-
rel_path = os.path.normpath(os.path.join(rel_root, filename))
|
|
344
|
-
hasher.update(rel_path.encode("utf-8", errors="replace"))
|
|
345
|
-
hasher.update(b"\0")
|
|
346
|
-
with open(file_path, "rb") as f:
|
|
347
|
-
for chunk in iter(lambda: f.read(8192), b""):
|
|
348
|
-
hasher.update(chunk)
|
|
349
|
-
hasher.update(b"\0")
|
|
350
|
-
return hasher.hexdigest()
|
|
351
|
-
except (AttributeError, OSError, RuntimeError, ValueError, TypeError):
|
|
352
|
-
return "N/A"
|
|
313
|
+
if errors:
|
|
314
|
+
summary += "Errors:\n" + "\n".join(errors)
|
|
315
|
+
|
|
316
|
+
QMessageBox.information(self, "Plugin Installation", summary)
|
|
317
|
+
|
|
318
|
+
def _compute_sha256(self, path):
|
|
319
|
+
if os.path.isfile(path):
|
|
320
|
+
return self._sha256_for_file(path)
|
|
321
|
+
if os.path.isdir(path):
|
|
322
|
+
return self._sha256_for_directory(path)
|
|
323
|
+
return "N/A"
|
|
324
|
+
|
|
325
|
+
def _sha256_for_file(self, path):
|
|
326
|
+
hasher = hashlib.sha256()
|
|
327
|
+
try:
|
|
328
|
+
with open(path, "rb") as f:
|
|
329
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
330
|
+
hasher.update(chunk)
|
|
331
|
+
return hasher.hexdigest()
|
|
332
|
+
except (AttributeError, OSError, RuntimeError, ValueError, TypeError):
|
|
333
|
+
return "N/A"
|
|
334
|
+
|
|
335
|
+
def _sha256_for_directory(self, dir_path):
|
|
336
|
+
hasher = hashlib.sha256()
|
|
337
|
+
try:
|
|
338
|
+
root = os.path.abspath(dir_path)
|
|
339
|
+
for current_root, _dirs, files in os.walk(root):
|
|
340
|
+
rel_root = os.path.relpath(current_root, root)
|
|
341
|
+
for filename in sorted(files):
|
|
342
|
+
file_path = os.path.join(current_root, filename)
|
|
343
|
+
rel_path = os.path.normpath(os.path.join(rel_root, filename))
|
|
344
|
+
hasher.update(rel_path.encode("utf-8", errors="replace"))
|
|
345
|
+
hasher.update(b"\0")
|
|
346
|
+
with open(file_path, "rb") as f:
|
|
347
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
348
|
+
hasher.update(chunk)
|
|
349
|
+
hasher.update(b"\0")
|
|
350
|
+
return hasher.hexdigest()
|
|
351
|
+
except (AttributeError, OSError, RuntimeError, ValueError, TypeError):
|
|
352
|
+
return "N/A"
|