diff --git a/update.py b/update.py index e006788..3fe00af 100644 --- a/update.py +++ b/update.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 -"""One command to set up AND update the Eve watchers — `python update.py`. +"""Self-installing / self-updating entry point — `python update.py`. -This folder is a clone of the public repo - https://git.armoredarmadillo.com/brockdarnold/eve-watcher.git -so updates are just a `git pull`. Run this any time to: +Run it once (the install one-liner does this for you). From then on a Windows +Scheduled Task runs it automatically **at logon and once a day**, so the watchers +stay up to date and running with zero further thought. Each run it: - 1. create config.ini from the example on first run (your webhook is prefilled; - config.ini is gitignored so your edits + OCR snip survive every update), - 2. pull the latest watcher code, - 3. install/upgrade python deps, - 4. (re)start the headless watchers. + 1. makes config.ini on first run (webhook prefilled; gitignored so your edits + and OCR snip survive every update), + 2. `git pull`s the latest watcher code, + 3. installs/upgrades deps only when something changed, + 4. (re)starts the watchers ONLY if the code changed or they aren't running — + so the daily run never interrupts an active session for nothing, + 5. ensures the daily+logon Scheduled Task exists (idempotent). -First time on a new PC: +First time on a new PC (or just paste the install one-liner): git clone https://git.armoredarmadillo.com/brockdarnold/eve-watcher.git cd eve-watcher python update.py - python eve_orehold_watcher.py --snip # one-time: aim OCR at the ore-hold bar - -After that, to get my latest changes, just: python update.py + python eve_orehold_watcher.py --snip # one-time GUI step for OCR hold alerts """ import configparser import os @@ -27,6 +27,8 @@ import sys HERE = os.path.dirname(os.path.abspath(__file__)) WIN = os.name == "nt" +TASK = "EveWatcher" +WATCHER_SCRIPTS = ("eve_combat_watcher.py", "eve_chat_watcher.py", "eve_orehold_watcher.py") def run(cmd, **kw): @@ -34,26 +36,38 @@ def run(cmd, **kw): return subprocess.run(cmd, cwd=HERE, **kw) +def ps(script): + """Run a PowerShell snippet, return (rc, stdout).""" + r = subprocess.run(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", + "-Command", script], cwd=HERE, capture_output=True, text=True) + return r.returncode, (r.stdout or "").strip() + + def ensure_config(): cfg = os.path.join(HERE, "config.ini") - ex = os.path.join(HERE, "config.ini.example") - if not os.path.exists(cfg): - shutil.copyfile(ex, cfg) - print("• created config.ini from config.ini.example (webhook prefilled).") + first = not os.path.exists(cfg) + if first: + shutil.copyfile(os.path.join(HERE, "config.ini.example"), cfg) + print("• created config.ini from example (webhook prefilled).") else: - print("• config.ini already exists — leaving your settings + snip untouched.") - return cfg + print("• config.ini exists — your settings + OCR snip left untouched.") + return cfg, first def git_pull(): + """Return True if new commits were pulled.""" if not os.path.isdir(os.path.join(HERE, ".git")): - print("• not a git clone — skipping pull. To get updates in future, install " - "via:\n git clone https://git.armoredarmadillo.com/brockdarnold/" - "eve-watcher.git") - return - if run(["git", "pull", "--ff-only"]).returncode != 0: - print("! git pull failed (local edits to tracked files?). Stash or commit " - "them, then re-run. Your config.ini is safe (gitignored).") + print("• not a git clone — can't auto-update. Reinstall via:\n" + " git clone https://git.armoredarmadillo.com/brockdarnold/eve-watcher.git") + return False + r = subprocess.run(["git", "pull", "--ff-only"], cwd=HERE, + capture_output=True, text=True) + out = (r.stdout + r.stderr).strip() + print(f" git: {out.splitlines()[-1] if out else '(no output)'}") + if r.returncode != 0: + print("! git pull failed (local edits to tracked files?). config.ini is safe.") + return False + return "Already up to date" not in out and "Already up-to-date" not in out def deps(): @@ -62,33 +76,85 @@ def deps(): run([sys.executable, "-m", "pip", "install", "--quiet", "-r", req]) -def restart_watchers(cfg_path): +def watchers_running(): if not WIN: - print("• non-Windows — start watchers manually (this is a Goliath/Windows tool).") + return 0 + _, out = ps("(Get-CimInstance Win32_Process -Filter \"Name='pythonw.exe'\" " + "| Where-Object { $_.CommandLine -like '*watcher*' }).Count") + try: + return int(out or "0") + except ValueError: + return 0 + + +def stop_watchers(): + ps("Get-CimInstance Win32_Process -Filter \"Name='pythonw.exe'\" | " + "Where-Object { $_.CommandLine -like '*watcher*' } | " + "ForEach-Object { Stop-Process -Id $_.ProcessId -Force }") + + +def start_watchers(cfg_path): + if not WIN: + print("• non-Windows — start watchers manually (this is a Windows tool).") return - # stop any running watchers, then relaunch via start-all.ps1 - subprocess.run(["powershell", "-NoProfile", "-Command", - "Get-Process pythonw -ErrorAction SilentlyContinue | Stop-Process -Force"], - cwd=HERE) + stop_watchers() run(["powershell", "-ExecutionPolicy", "Bypass", "-File", os.path.join(HERE, "start-all.ps1")]) - # nudge if OCR region not snipped yet cp = configparser.ConfigParser() cp.read(cfg_path) if cp.get("watcher", "mode", fallback="ocr") == "ocr" and \ not cp.get("ocr", "region", fallback="").strip(): - print("\n >> ONE-TIME STEP: the OCR hold/compress/stall watcher needs its " - "region.\n Run: python eve_orehold_watcher.py --snip\n" - " (drag a box around the Ore Hold fill bar; it persists after that.)") + print("\n >> ONE-TIME (per PC) for hold/compress/stall alerts, run once:\n" + " python eve_orehold_watcher.py --snip\n" + " (drag a box around the Ore Hold fill bar; saved forever after.)") + + +def ensure_task(): + """Register a daily + at-logon Scheduled Task so this runs itself forever. + Idempotent: only creates it if missing.""" + if not WIN: + return + rc, _ = ps(f"Get-ScheduledTask -TaskName '{TASK}' -ErrorAction SilentlyContinue") + if rc == 0: + print(f"• Scheduled Task '{TASK}' already set (auto-update at logon + daily).") + return + pyw = os.path.join(os.path.dirname(sys.executable), "pythonw.exe") + if not os.path.exists(pyw): + pyw = "pythonw.exe" + here = HERE.replace("'", "''") + pyw_e = pyw.replace("'", "''") + snippet = ( + f"$a = New-ScheduledTaskAction -Execute '{pyw_e}' " + f"-Argument '\"{here}\\update.py\"' -WorkingDirectory '{here}';" + "$t1 = New-ScheduledTaskTrigger -AtLogOn;" + "$t2 = New-ScheduledTaskTrigger -Daily -At 5am;" + "$s = New-ScheduledTaskSettingsSet -StartWhenAvailable " + "-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries " + "-ExecutionTimeLimit (New-TimeSpan -Hours 1);" + "$p = New-ScheduledTaskPrincipal -UserId $env:USERNAME " + "-LogonType Interactive -RunLevel Limited;" + f"Register-ScheduledTask -TaskName '{TASK}' -Action $a -Trigger $t1,$t2 " + "-Settings $s -Principal $p -Force | Out-Null") + rc, out = ps(snippet) + if rc == 0: + print(f"• Scheduled Task '{TASK}' created — auto-updates at logon + daily 5am. " + "You never have to run this again.") + else: + print(f"! couldn't register Scheduled Task: {out}") def main(): - print("=== Eve watcher setup / update ===") - cfg = ensure_config() - git_pull() - deps() - restart_watchers(cfg) - print("\nDone. Watchers running. Stop them with: Get-Process pythonw | Stop-Process") + print("=== Eve watcher setup / auto-update ===") + cfg, first = ensure_config() + changed = git_pull() + if changed or first: + deps() + if changed or first or watchers_running() == 0: + start_watchers(cfg) + else: + print("• up to date and watchers already running — nothing to restart.") + ensure_task() + print("\nDone. Hands-off from here: it updates + keeps itself running automatically.") if __name__ == "__main__":