diff --git a/index.html b/index.html index 38763d8..cdfca83 100644 --- a/index.html +++ b/index.html @@ -341,6 +341,7 @@ mark{background:rgba(34,211,238,.28);color:#fff;border-radius:2px;padding:0 1px}
+
@@ -991,6 +992,20 @@ async function api(path, body){ return j; } +if($("btnBrowseFolder")){ + $("btnBrowseFolder").onclick = async ()=>{ + $("btnBrowseFolder").disabled = true; + const j = await api("/api/choose_folder"); + $("btnBrowseFolder").disabled = false; + if(j.ok && j.folder){ + $("folder").value = j.folder; + showToast("Selected folder: " + j.folder, "ok"); + const scanRes = await api("/api/scan",{folder: j.folder}); + if(scanRes.images!=null){ scanned=true; setBadge("idle"); } + } + }; +} + $("btnScan").onclick = async ()=>{ const j = await api("/api/scan",{folder:$("folder").value}); if(j.images!=null){ scanned=true; setBadge("idle"); } diff --git a/launch.command b/launch.command new file mode 100755 index 0000000..790b975 --- /dev/null +++ b/launch.command @@ -0,0 +1,20 @@ +#!/bin/bash +# PHOTON — Double-click macOS launcher +# Automatically starts the server and opens the console in your default web browser. + +CD_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$CD_DIR" + +echo "=======================================================" +echo " PHOTON — Local Photo Intelligence Console Launch " +echo "=======================================================" +echo "" +echo "Starting local HTTP server..." +python3 server.py & +SERVER_PID=$! + +sleep 1.5 +echo "Opening browser at http://localhost:8765 ..." +open "http://localhost:8765" + +wait $SERVER_PID diff --git a/server.py b/server.py index d06cc07..7d53039 100644 --- a/server.py +++ b/server.py @@ -214,6 +214,19 @@ def write_failures(): except Exception: pass +def resolve_portable_path(path): + if not path: + return path + if os.path.exists(path): + return path + with S.lock: + curr_folder = S.folder + if curr_folder and os.path.isdir(curr_folder): + cand = os.path.join(curr_folder, os.path.basename(path)) + if os.path.exists(cand): + return cand + return path + def load_journal(): n = 0 with S.lock: @@ -227,8 +240,9 @@ def load_journal(): continue try: rec = json.loads(line) + path = resolve_portable_path(rec.get("path", "")) with S.lock: - S.done_paths.add(rec["path"]) + S.done_paths.add(path) cat = rec.get("category") if cat not in S.cat_counts: S.cat_counts[cat] = 0 @@ -246,7 +260,6 @@ def trash_or_remove_file(path): if not os.path.exists(path): return True try: - # Try macOS Trash via AppleScript cmd = ["osascript", "-e", f'tell application "Finder" to delete POSIX file "{os.path.abspath(path)}"'] r = subprocess.run(cmd, capture_output=True, timeout=10) if r.returncode == 0: @@ -262,7 +275,6 @@ def trash_or_remove_file(path): def remove_photo_records(path): abs_path = os.path.abspath(path) - # 1. Remove symlink in _organized with S.lock: folder = S.folder org_root = os.path.join(folder, "_organized") @@ -278,7 +290,6 @@ def remove_photo_records(path): except Exception: pass - # 2. Clean thumbnail and view caches try: h = hashlib.md5(abs_path.encode()).hexdigest() t1 = os.path.join(tempfile.gettempdir(), "photon_thumbs", f"{h}.jpg") @@ -290,7 +301,6 @@ def remove_photo_records(path): except Exception: pass - # 3. Rewrite journal without this path if os.path.exists(JOURNAL): lines_to_keep = [] with open(JOURNAL, "r", encoding="utf-8") as f: @@ -308,7 +318,6 @@ def remove_photo_records(path): for l in lines_to_keep: f.write(l + "\n") - # 4. Remove from State with S.lock: if abs_path in S.done_paths: S.done_paths.remove(abs_path) @@ -356,7 +365,8 @@ def load_search_index(): continue try: rec = json.loads(line) - path = rec.get("path", "") + path = resolve_portable_path(rec.get("path", "")) + rec["path"] = path name = os.path.basename(path) rec["name"] = name rec["_ext"] = os.path.splitext(name)[1].lower() @@ -1678,6 +1688,22 @@ class Handler(BaseHTTPRequestHandler): self._json({"images": len(images), "videos": len(videos), "sidecars": sidecars, "alreadyDone": done}) + elif self.path == "/api/choose_folder": + try: + cmd = ["osascript", "-e", 'POSIX path of (choose folder with prompt "Select Photo Folder to Organize:")'] + r = subprocess.run(cmd, capture_output=True, timeout=60) + if r.returncode == 0: + chosen = r.stdout.decode("utf-8").strip() + if chosen and os.path.isdir(chosen): + with S.lock: + S.folder = chosen + save_folder_config(chosen) + self._json({"ok": True, "folder": chosen}) + return + self._json({"error": "No folder selected"}, 400) + except Exception as e: + self._json({"error": str(e)}, 500) + elif self.path == "/api/start": with S.lock: if S.status == "running":