Repository navigation
Expand file tree
/
Copy pathmain_interface.py
More file actions
180 lines (149 loc) · 5.25 KB
/
Copy pathmain_interface.py
File metadata and controls
180 lines (149 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python3
"""
Entry point for Batch File Sender application.
This module provides a minimal entry point that initializes and runs the
application. All application logic has been refactored into separate modules:
- Application class: interface/qt/app.py
- DatabaseObj: interface/database/database_obj.py
- FolderManager: interface/operations/folder_manager.py
- Email validation: interface/validation/email_validator.py
- UI protocols: interface/interfaces.py
- Dialogs: interface/qt/dialogs/
This script can be run directly without installation:
python main_interface.py
Or as a module (if installed):
python -m main_interface
"""
from __future__ import annotations
import importlib
import os
import platform
import sys
import tempfile
import traceback
from pathlib import Path
# Ensure the project root is in sys.path so we can import 'interface' package
# when running as a script without installation
_script_dir = os.path.dirname(os.path.abspath(__file__))
if _script_dir not in sys.path:
sys.path.insert(0, _script_dir)
_SUPPORT_LOG_NAME = "batch-file-sender-startup-error.log"
# Paths to the Qt runtime DLLs in the frozen (PyInstaller / Nuitka) bundle.
# Used by _build_startup_error_lines to suggest what to check when Qt
# fails to load at startup. The Qt binding shipped with this build is
# PySide6 (Qt6); see pyproject.toml's [tool.nuitka-wine-build] block
# and the Phase 5 commit history for the PyQt5 -> PySide6 cutover.
_FROZEN_QT_PATHS = (
Path("qt6core.dll"),
Path("qt6gui.dll"),
Path("qt6widgets.dll"),
Path("PySide6") / "QtCore.pyd",
Path("PySide6") / "qt-plugins" / "platforms" / "qwindows.dll",
)
def _resolve_bundle_root() -> Path:
if getattr(sys, "frozen", False):
# Single-file mode: PyInstaller extracts to sys._MEIPASS at runtime
meipass = getattr(sys, "_MEIPASS", None)
if meipass:
return Path(meipass)
return Path(sys.executable).resolve().parent
return Path(_script_dir)
def _build_startup_error_lines(exc: ImportError | OSError) -> list[str]:
bundle_root = _resolve_bundle_root()
lines = [
"Batch File Sender could not start because the Qt runtime failed to load.",
f"Error: {exc.__class__.__name__}: {exc}",
"",
"What to check:",
(
"- Install or repair the Microsoft Visual C++ 2015-2022 "
"Redistributable (x64) and Windows UCRT."
),
(
"- This is a single-file executable; if the error persists, "
"re-download the .exe."
),
]
if getattr(sys, "frozen", False):
lines.extend(
[
"- Verify these bundled Qt paths exist next to the executable:",
*[f" - {relative_path}" for relative_path in _FROZEN_QT_PATHS],
]
)
for relative_path in _FROZEN_QT_PATHS:
resolved_path = bundle_root / relative_path
lines.append(
f" {'OK' if resolved_path.exists() else 'MISSING'}: {resolved_path}"
)
else:
lines.append(
"- If running from source, confirm PySide6 is installed and "
"matches this Python environment."
)
lines.extend(
[
"",
f"Executable directory: {bundle_root}",
]
)
meipass = getattr(sys, "_MEIPASS", None)
if meipass:
lines.append(f"PyInstaller extraction dir: {meipass}")
return lines
def _write_startup_error_log(
lines: list[str], exc: ImportError | OSError
) -> Path | None:
candidate_dirs = []
bundle_root = _resolve_bundle_root()
if getattr(sys, "frozen", False):
candidate_dirs.append(bundle_root)
candidate_dirs.append(Path(tempfile.gettempdir()))
log_body = "\n".join(
[
*lines,
"",
f"Platform: {platform.platform()}",
f"Python: {sys.version}",
"",
"Traceback:",
"".join(traceback.format_exception(type(exc), exc, exc.__traceback__)),
]
)
for directory in candidate_dirs:
try:
directory.mkdir(parents=True, exist_ok=True)
log_path = directory / _SUPPORT_LOG_NAME
log_path.write_text(log_body, encoding="utf-8")
return log_path
except OSError:
continue
return None
def _emit_startup_import_error(exc: ImportError | OSError) -> None:
lines = _build_startup_error_lines(exc)
log_path = _write_startup_error_log(lines, exc)
if log_path is not None:
lines.extend(["", f"Support log: {log_path}"])
print("\n".join(lines), file=sys.stderr)
def _load_qt_app_class() -> type:
try:
module = importlib.import_module("interface.qt.app")
except (ImportError, OSError) as exc:
_emit_startup_import_error(exc)
raise SystemExit(1) from exc
return module.QtBatchFileSenderApp
def main() -> None:
"""Main entry point for the Batch File Sender application."""
from core.constants import APP_VERSION
qt_app_class = _load_qt_app_class()
app = qt_app_class(
appname="Batch File Sender",
version=APP_VERSION,
)
app.initialize()
try:
sys.exit(app.run())
finally:
app.shutdown()
if __name__ == "__main__":
main()