Add self-healing path relocation for multi-disk portability, macOS native folder picker, and double-clickable launch.command

This commit is contained in:
drjones
2026-07-20 21:18:12 -07:00
parent 82e93972c1
commit 2d3ef6a06c
3 changed files with 68 additions and 7 deletions

View File

@@ -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":