158 lines
5.0 KiB
Python
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()
|