forked from masonasons/FastSM
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFastSM.pyw
More file actions
148 lines (136 loc) · 4.41 KB
/
FastSM.pyw
File metadata and controls
148 lines (136 loc) · 4.41 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
import sys
sys.dont_write_bytecode = True
def _clear_requests_pycache():
"""Clear corrupted pycache for requests library."""
import shutil
import os
try:
import requests
requests_path = os.path.dirname(requests.__file__)
except:
# requests not importable, find it manually
for path in sys.path:
requests_path = os.path.join(path, 'requests')
if os.path.isdir(requests_path):
break
else:
return False
pycache = os.path.join(requests_path, '__pycache__')
if os.path.exists(pycache):
try:
shutil.rmtree(pycache)
return True
except:
pass
return False
# Try to import requests, clear pycache if it fails (only when running from source)
if not getattr(sys, 'frozen', False):
try:
import requests
except Exception:
if _clear_requests_pycache():
# Retry after clearing cache
import importlib
import requests
import threading
import platform
def _has_bluesky_accounts():
"""Check if any Bluesky accounts are configured (without loading full config module)."""
import os
import json
# Check for portable mode (userdata folder in current directory)
userdata_path = os.path.join(os.getcwd(), "userdata")
if os.path.isdir(userdata_path):
config_base = userdata_path
prefix = ""
else:
# Normal mode - use APPDATA on Windows
config_base = os.environ.get("APPDATA", os.path.expanduser("~"))
prefix = "FastSM/"
# Check account0, account1, etc. for bluesky platform_type
for i in range(10): # Check up to 10 accounts
config_path = os.path.join(config_base, f"{prefix}account{i}", "config.json")
if not os.path.exists(config_path):
continue
try:
with open(config_path, 'r') as f:
data = json.load(f)
if data.get("platform_type") == "bluesky":
return True
except:
pass
return False
# Only pre-import atproto if there are Bluesky accounts (takes ~35s)
# Skip on macOS - can cause startup crashes due to thread/GUI conflicts
_should_preimport_atproto = False
if platform.system() != "Darwin" and _has_bluesky_accounts():
_should_preimport_atproto = True
import application
from application import get_app
import os
# Redirect stderr to errors.log in config directory (not app directory)
# This is important for installed versions where app directory may be write-protected
if platform.system() != "Darwin":
def _get_config_dir():
"""Get the config directory for error logging."""
# Check for portable mode (userdata folder next to executable or in cwd)
if getattr(sys, 'frozen', False):
exe_dir = os.path.dirname(sys.executable)
userdata_path = os.path.join(exe_dir, "userdata")
if os.path.isdir(userdata_path):
return userdata_path
else:
# Running from source - check cwd
userdata_path = os.path.join(os.getcwd(), "userdata")
if os.path.isdir(userdata_path):
return userdata_path
# Standard config location
return os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), "FastSM")
try:
config_dir = _get_config_dir()
if not os.path.exists(config_dir):
os.makedirs(config_dir)
f = open(os.path.join(config_dir, "errors.log"), "a")
sys.stderr = f
except Exception:
pass # If we can't set up logging, continue anyway
import shutil
if os.path.exists(os.path.expandvars(r"%temp%\gen_py")):
shutil.rmtree(os.path.expandvars(r"%temp%\gen_py"))
import wx
wx_app = wx.App(redirect=False)
# Start atproto pre-import AFTER wx.App() is created to avoid thread/GUI conflicts
if _should_preimport_atproto:
def _preimport_atproto():
try:
import atproto
except:
pass
threading.Thread(target=_preimport_atproto, daemon=True).start()
# Prevent multiple instances of the app (not needed on Mac - macOS handles this)
if platform.system() != "Darwin":
instance_checker = wx.SingleInstanceChecker("FastSM-" + wx.GetUserId())
if instance_checker.IsAnotherRunning():
wx.MessageBox("Another instance of FastSM is already running.", "FastSM", wx.OK | wx.ICON_WARNING)
sys.exit(1)
try:
import speak
from GUI import main, theme
fastsm_app = get_app()
fastsm_app.load()
# Apply theme after prefs are loaded
theme.apply_theme(main.window)
if fastsm_app.prefs.window_shown:
main.window.Show()
else:
speak.speak("Welcome to FastSM! Main window hidden.")
wx_app.MainLoop()
except Exception as e:
import traceback
error_msg = f"FastSM failed to start:\n\n{type(e).__name__}: {e}\n\n{traceback.format_exc()}"
print(error_msg, file=sys.stderr)
try:
wx.MessageBox(error_msg, "FastSM Startup Error", wx.OK | wx.ICON_ERROR)
except:
pass
sys.exit(1)