Repository navigation
Expand file tree
/
Copy pathmain.py
More file actions
167 lines (135 loc) · 5.82 KB
/
Copy pathmain.py
File metadata and controls
167 lines (135 loc) · 5.82 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
"""Main Execution Entry Point for ShellPDF."""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from app.application import Application
from app.single_instance import SingleInstanceIPC
from core.constants import AppConstants
from core.logger import LoggerFactory, get_logger
from registry.context_menu import WindowsRegistryManager
# Initialize core logging before execution
logger = LoggerFactory.setup_logger()
def parse_arguments() -> argparse.Namespace:
"""Parse command-line arguments for ShellPDF."""
parser = argparse.ArgumentParser(
description="ShellPDF: Windows Office & LibreOffice to PDF Batch Converter",
)
parser.add_argument(
"files",
nargs="*",
help="Input document file paths or wildcards to convert",
)
parser.add_argument(
"--headless",
action="store_true",
help="Run batch conversion in headless CLI mode without displaying GUI",
)
parser.add_argument(
"--register-context-menu",
action="store_true",
help="Register Windows Explorer right-click context menu and exit",
)
parser.add_argument(
"--unregister-context-menu",
action="store_true",
help="Unregister Windows Explorer right-click context menu and exit",
)
parser.add_argument(
"--config",
type=str,
help="Custom configuration JSON file path",
)
return parser.parse_args()
def resolve_file_inputs(raw_paths: list[str]) -> list[Path]:
"""Resolve input document file paths, directories, or wildcards."""
input_files: list[Path] = []
if raw_paths:
for pattern in raw_paths:
p = Path(pattern)
if p.is_file():
input_files.append(p.resolve())
elif p.is_dir():
for f in p.rglob("*"):
if f.is_file():
input_files.append(f.resolve())
else:
parent = p.parent if p.parent.exists() else Path.cwd()
matched = list(parent.glob(p.name))
for m in matched:
if m.is_file():
input_files.append(m.resolve())
return input_files
def main() -> int:
"""Application main entry point."""
logger.info("=== ShellPDF Process Entry ===")
# 1. Parse Arguments
args = parse_arguments()
logger.info(f"Argument Parsing Complete: args={sys.argv[1:]}")
# Handle Context Menu Commands
reg_manager = WindowsRegistryManager()
if args.register_context_menu:
logger.info("Registering Windows Explorer Context Menu...")
success = reg_manager.register_context_menu()
return AppConstants.EXIT_SUCCESS if success else AppConstants.EXIT_ERROR
if args.unregister_context_menu:
logger.info("Unregistering Windows Explorer Context Menu...")
success = reg_manager.unregister_context_menu()
return AppConstants.EXIT_SUCCESS if success else AppConstants.EXIT_ERROR
# Resolve input file paths
input_files = resolve_file_inputs(args.files)
# 2. Check Single Instance Lock via Win32 Named Mutex BEFORE GUI Initialization
ipc = SingleInstanceIPC()
logger.info("IPC Connection Check: Attempting to acquire Win32 Named Mutex...")
is_primary = ipc.acquire_mutex()
if not is_primary:
logger.info("IPC Connection Check: Secondary instance detected.")
if input_files:
logger.info(f"IPC Forwarding: Forwarding {len(input_files)} files to primary instance...")
ipc.try_forward_to_existing_instance(input_files)
else:
logger.info("IPC Forwarding: No files provided to secondary process.")
logger.info("IPC Forwarding: Terminating secondary process immediately without GUI.")
return AppConstants.EXIT_SUCCESS
logger.info("IPC Connection Check: Primary instance confirmed. Initializing Application Context...")
# 3. Primary Process Setup
config_path = Path(args.config) if args.config else None
app = Application(config_path=config_path)
app.initialize()
if input_files:
logger.info(f"Resolved {len(input_files)} initial file inputs from CLI/Explorer.")
if args.headless:
if input_files:
from office.queue_manager import ConversionQueueManager
qm = ConversionQueueManager()
qm.add_files(input_files)
summary = qm.start(block=True)
logger.info(
f"Headless CLI conversion complete: {summary.completed_items}/{summary.total_items} succeeded."
)
ipc.stop_server()
return AppConstants.EXIT_SUCCESS if summary.failed_items == 0 else AppConstants.EXIT_ERROR
logger.info("Headless mode requested without files. Exiting.")
ipc.stop_server()
return AppConstants.EXIT_SUCCESS
# 4. Construct MainWindow (EXACTLY ONE INSTANTIATION IN CODEBASE)
logger.info("MainWindow Creation: Instantiating primary MainWindow GUI...")
from gui.window import MainWindow
window = MainWindow(title=app.context.metadata.name, context=app.context)
# 5. Start IPC Server to listen for file inputs from future secondary instances
def handle_ipc_files(files: list[Path]) -> None:
logger.info(f"IPC Event: Received {len(files)} files. Updating primary MainWindow queue.")
window.after(0, window.load_initial_files, files)
window.after(0, window.bring_to_front)
ipc_server = SingleInstanceIPC(on_files_received=handle_ipc_files)
ipc_server.start_server()
if input_files:
window.load_initial_files(input_files)
# 6. Start Main Event Loop
logger.info("Main Loop Start: Launching CustomTkinter GUI main event loop...")
try:
return window.run()
finally:
ipc_server.stop_server()
if __name__ == "__main__":
sys.exit(main())