-
+
diff --git a/src/web/api/photos.py b/src/web/api/photos.py
index aac1583..c956b85 100644
--- a/src/web/api/photos.py
+++ b/src/web/api/photos.py
@@ -363,3 +363,181 @@ def get_photo_image(photo_id: int, db: Session = Depends(get_db)) -> FileRespons
response.headers["Cache-Control"] = "public, max-age=3600"
return response
+
+@router.post("/{photo_id}/open-folder")
+def open_photo_folder(photo_id: int, db: Session = Depends(get_db)) -> dict:
+ """Open the folder containing the photo in the system file manager and select the file.
+
+ Matches desktop behavior and enhances it by selecting the specific file:
+ - Windows: uses explorer /select,"file_path"
+ - macOS: uses 'open -R' to reveal and select the file
+ - Linux: tries file manager-specific commands (nautilus, dolphin, etc.) or opens folder
+ """
+ import os
+ import sys
+ import subprocess
+ from src.web.db.models import Photo
+
+ photo = db.query(Photo).filter(Photo.id == photo_id).first()
+ if not photo:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Photo {photo_id} not found",
+ )
+
+ # Ensure we have absolute path
+ file_path = os.path.abspath(photo.path)
+ folder = os.path.dirname(file_path)
+
+ if not os.path.exists(file_path):
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Photo file not found: {file_path}",
+ )
+
+ if not os.path.exists(folder):
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Folder not found: {folder}",
+ )
+
+ try:
+ # Try using showinfm package first (better cross-platform support)
+ try:
+ from showinfm import show_in_file_manager
+ show_in_file_manager(file_path)
+ return {
+ "message": f"Opened folder and selected file: {os.path.basename(file_path)}",
+ "folder": folder,
+ "file": file_path
+ }
+ except ImportError:
+ # showinfm not installed, fall back to manual commands
+ pass
+ except Exception as e:
+ # showinfm failed, fall back to manual commands
+ pass
+
+ # Fallback: Open folder and select the file using platform-specific commands
+ if os.name == "nt": # Windows
+ # Windows: explorer /select,"file_path" opens folder and selects the file
+ subprocess.run(["explorer", "/select,", file_path], check=False)
+ elif sys.platform == "darwin": # macOS
+ # macOS: open -R reveals the file in Finder and selects it
+ subprocess.run(["open", "-R", file_path], check=False)
+ else: # Linux and others
+ # Linux: Try file manager-specific commands to select the file
+ # Try different file managers based on desktop environment
+ opened = False
+
+ # Detect desktop environment first
+ desktop_env = os.environ.get("XDG_CURRENT_DESKTOP", "").lower()
+
+ # Try Nautilus (GNOME) - supports --select option
+ if "gnome" in desktop_env or not desktop_env:
+ try:
+ result = subprocess.run(
+ ["nautilus", "--select", file_path],
+ check=False,
+ timeout=5
+ )
+ if result.returncode == 0:
+ opened = True
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ pass
+
+ # Try Nemo (Cinnamon/MATE) - doesn't support --select, but we can try folder with navigation
+ if not opened and ("cinnamon" in desktop_env or "mate" in desktop_env):
+ try:
+ # For Nemo, we can try opening the folder first, then using a script or
+ # just opening the folder (Nemo may focus on the file if we pass it)
+ # Try opening with the file path - Nemo will open the folder
+ file_uri = f"file://{file_path}"
+ result = subprocess.Popen(
+ ["nemo", file_uri],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL
+ )
+ # Nemo will open the folder - selection may not work perfectly
+ # This is a limitation of Nemo
+ opened = True
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ pass
+
+ # Try Dolphin (KDE) - supports --select option
+ if not opened and "kde" in desktop_env:
+ try:
+ result = subprocess.run(
+ ["dolphin", "--select", file_path],
+ check=False,
+ timeout=5
+ )
+ if result.returncode == 0:
+ opened = True
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ pass
+
+ # Try PCManFM (LXDE/LXQt) - supports --select option
+ if not opened and ("lxde" in desktop_env or "lxqt" in desktop_env):
+ try:
+ result = subprocess.run(
+ ["pcmanfm", "--select", file_path],
+ check=False,
+ timeout=5
+ )
+ if result.returncode == 0:
+ opened = True
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ pass
+
+ # Try Thunar (XFCE) - supports --select option
+ if not opened and "xfce" in desktop_env:
+ try:
+ result = subprocess.run(
+ ["thunar", "--select", file_path],
+ check=False,
+ timeout=5
+ )
+ if result.returncode == 0:
+ opened = True
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ pass
+
+ # If desktop-specific didn't work, try all file managers in order
+ if not opened:
+ file_managers = [
+ ("nautilus", ["--select", file_path]),
+ ("nemo", [f"file://{file_path}"]), # Nemo uses file:// URI
+ ("dolphin", ["--select", file_path]),
+ ("thunar", ["--select", file_path]),
+ ("pcmanfm", ["--select", file_path]),
+ ]
+
+ for fm_name, fm_args in file_managers:
+ try:
+ result = subprocess.run(
+ [fm_name] + fm_args,
+ check=False,
+ timeout=5
+ )
+ if result.returncode == 0:
+ opened = True
+ break
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ continue
+
+ # Fallback: try xdg-open with the folder (will open folder but won't select file)
+ if not opened:
+ subprocess.run(["xdg-open", folder], check=False)
+
+ return {
+ "message": f"Opened folder and selected file: {os.path.basename(file_path)}",
+ "folder": folder,
+ "file": file_path
+ }
+ except Exception as e:
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail=f"Failed to open folder: {str(e)}",
+ )
+