-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlauncher.py
More file actions
204 lines (164 loc) · 6.2 KB
/
launcher.py
File metadata and controls
204 lines (164 loc) · 6.2 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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
"""
Echo Application Launcher
Handles the complete application lifecycle including setup, directory management, and server startup.
"""
import sys
import os
import argparse
import webbrowser
import threading
import time
import signal
from pathlib import Path
import uvicorn
import certifi
# Add the project root to Python path
if getattr(sys, 'frozen', False):
# Running as PyInstaller bundle
project_root = Path(sys._MEIPASS)
else:
# Running in development environment
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
def get_app_data_path():
"""Get the application data directory for Echo."""
if os.name == 'nt': # Windows
appdata = os.environ.get('LOCALAPPDATA')
if appdata:
return Path(appdata) / "Echo"
else:
return Path.home() / "AppData" / "Local" / "Echo"
else:
return Path.home() / ".echo"
def setup_directories():
"""Create necessary directories in AppData and copy default files."""
app_data = get_app_data_path()
print(f"Setting up Echo in: {app_data}")
# Import path management
from backend.paths import initialize_paths
# Initialize paths with AppData location
paths = initialize_paths(base_path=app_data)
# Copy default files from project root
source_base = project_root
paths.copy_default_files(source_base)
return paths
def find_available_port(start_port=8000, max_attempts=10):
"""Find an available port starting from start_port."""
import socket
for port in range(start_port, start_port + max_attempts):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.bind(('127.0.0.1', port))
sock.close()
return port
except OSError:
continue
return start_port # Return original if none found
def launch_browser(port):
"""Launch web browser after a short delay."""
def delayed_launch():
time.sleep(3) # Wait for server to fully start
try:
webbrowser.open(f'http://127.0.0.1:{port}')
print(f"Browser launched at http://127.0.0.1:{port}")
except Exception as e:
print(f"Failed to launch browser: {e}")
print(f"Please open your browser and navigate to http://127.0.0.1:{port}")
browser_thread = threading.Thread(target=delayed_launch, daemon=True)
browser_thread.start()
def setup_signal_handler():
"""Setup signal handler for graceful shutdown."""
def signal_handler(signum, frame):
print("\nShutting down Echo gracefully...")
sys.exit(0)
if os.name != 'nt': # Unix-like systems
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
def parse_args():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(description="Echo Application Launcher")
parser.add_argument('--port', type=int, default=None,
help='Use a specific port instead of auto-detecting')
parser.add_argument('--tauri', action='store_true',
help='Run as Tauri sidecar (skip browser launch)')
return parser.parse_args()
def main():
"""Main application entry point."""
args = parse_args()
print("Starting Echo Application...")
print(f"Python version: {sys.version}")
print(f"Platform: {sys.platform}")
print(f"Frozen (packaged): {getattr(sys, 'frozen', False)}")
if args.tauri:
os.environ['TAURI_MODE'] = '1'
print("Running in Tauri sidecar mode")
# Set up SSL certificates for HTTPS connections
try:
os.environ['SSL_CERT_FILE'] = certifi.where()
print(f"Using SSL certificates from: {certifi.where()}")
except Exception as e:
print(f"Warning: Could not set SSL certificates: {e}")
try:
# Setup signal handling
setup_signal_handler()
# Setup directories and copy default files
print("Setting up directories...")
paths = setup_directories()
print(f"App data directory: {paths.base_path}")
# Find available port
if args.port:
port = args.port
print(f"Using specified port {port}")
else:
print("Finding available port...")
port = find_available_port()
print(f"Starting server on port {port}")
# Set working directory to app data for consistent relative paths
print(f"Changing working directory to: {paths.base_path}")
os.chdir(paths.base_path)
# Add project root to Python path for imports
sys.path.insert(0, str(project_root))
print(f"Project root added to Python path: {project_root}")
# Import the app directly to avoid string import issues
print("Importing FastAPI app...")
from backend.main import app
print("FastAPI app imported successfully")
# Launch browser in separate thread (skip in Tauri mode)
if not args.tauri:
launch_browser(port)
# Print startup message
print("=" * 50)
print("Echo is starting up...")
print(f"Application data directory: {paths.base_path}")
print(f"Server will be available at: http://127.0.0.1:{port}")
if not args.tauri:
print("Press Ctrl+C to stop the server")
print("=" * 50)
# Run the server with direct app reference
uvicorn.run(
app,
host="127.0.0.1",
port=port,
log_level="info",
reload=False, # Disable reload in packaged version
access_log=False # Reduce log noise
)
except KeyboardInterrupt:
print("\nEcho stopped by user")
except Exception as e:
print(f"\n" + "="*50)
print(f"ERROR starting Echo: {e}")
print("="*50)
print("\nPlease check the following:")
print("1. All required files are in place")
print("2. Port 8000-8009 is not already in use")
print("3. You have sufficient permissions")
print("4. Your antivirus is not blocking the application")
print("\nPress Enter to exit...")
try:
input()
except:
pass
sys.exit(1)
if __name__ == "__main__":
main()