Stock-Tool/launcher.py

158 lines
5.0 KiB
Python

"""
Stock Screener Launcher
=======================
Startup sequence:
1. Generate HWID
2. Load stored license key — show activation dialog if none found
3. Verify license against Supabase (with 30-min offline grace period)
4. Silently check for and apply updates
5. Load and run screener_gui.py
Source file hygiene
-------------------
On every launch, the launcher scans the parent folder of the exe for stray
copies of screener_gui.py or stock_screener.py and removes them (pulling in
any newer version first so no edits are lost).
"""
import importlib.util
import os
import shutil
import sys
import tkinter as tk
from tkinter import messagebox
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _exe_dir() -> str:
if getattr(sys, "frozen", False):
return os.path.dirname(sys.executable)
return os.path.dirname(os.path.abspath(__file__))
_SOURCE_FILES = ("screener_gui.py", "stock_screener.py")
def _sync_source_files(exe_dir: str) -> None:
parent_dir = os.path.dirname(exe_dir)
if parent_dir == exe_dir:
return
for fname in _SOURCE_FILES:
stray = os.path.join(parent_dir, fname)
if not os.path.exists(stray):
continue
live = os.path.join(exe_dir, fname)
if not os.path.exists(live) or os.path.getmtime(stray) > os.path.getmtime(live):
try:
shutil.copy2(stray, live)
except OSError:
pass
try:
os.remove(stray)
except OSError:
pass
def _fatal(title: str, message: str) -> None:
"""Show an error dialog and exit."""
root = tk.Tk()
root.withdraw()
messagebox.showerror(title, message)
root.destroy()
sys.exit(1)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
exe_dir = _exe_dir()
_sync_source_files(exe_dir)
# ------------------------------------------------------------------
# Step 1: HWID
# ------------------------------------------------------------------
try:
from hwid import get_hwid
hwid = get_hwid()
except Exception as exc:
_fatal("Ultimate Investment Tool", f"Could not generate hardware ID:\n\n{exc}")
return # unreachable, but satisfies linters
# ------------------------------------------------------------------
# Step 2: Load or activate license
# ------------------------------------------------------------------
import license_check
license_key = license_check.load_license_key(hwid)
if license_key is None:
# First launch — show activation dialog
import activation_dialog
activated = activation_dialog.run_activation()
if not activated:
sys.exit(0) # user closed the window
# Reload after successful save
license_key = license_check.load_license_key(hwid)
if license_key is None:
_fatal("Ultimate Investment Tool", "Activation failed. Please restart and try again.")
return
# ------------------------------------------------------------------
# Step 3: Verify license on every launch
# ------------------------------------------------------------------
valid, reason = license_check.verify_license(license_key, hwid)
if not valid:
# License invalid — clear stored key so they can re-enter on next launch
license_check.delete_license_key()
_fatal(
"Ultimate Investment Tool — License Error",
f"{reason}\n\nThe application will now close."
)
return
# ------------------------------------------------------------------
# Step 4: Silent update check
# ------------------------------------------------------------------
try:
import updater
updater.check_and_apply_update()
# If an update was applied, updater restarts the process — we never reach here.
except Exception:
pass # never block the app over a failed update check
# ------------------------------------------------------------------
# Step 5: Load screener_gui.py
# ------------------------------------------------------------------
script = os.path.join(exe_dir, "screener_gui.py")
if os.path.exists(script):
if exe_dir not in sys.path:
sys.path.insert(0, exe_dir)
spec = importlib.util.spec_from_file_location("screener_gui_live", script)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
mod.main()
return
# Fallback: frozen version bundled inside the exe
try:
import screener_gui
screener_gui.main()
except Exception as exc:
_fatal(
"Ultimate Investment Tool",
f"Could not start.\n\n{exc}\n\n"
"Make sure screener_gui.py is in the same folder as this exe."
)
if __name__ == "__main__":
main()