Add server-side folder picker
New GET /api/browse endpoint lists subdirectories at any path. UI gets a folder icon button next to each path input that opens a browsable directory tree modal. Escape or Cancel closes it, clicking a folder navigates into it, Select confirms the choice. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
BIN
app/__pycache__/main.cpython-313.pyc
Normal file
BIN
app/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/scanner.cpython-313.pyc
Normal file
BIN
app/__pycache__/scanner.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/takeout.cpython-313.pyc
Normal file
BIN
app/__pycache__/takeout.cpython-313.pyc
Normal file
Binary file not shown.
43
app/main.py
43
app/main.py
@@ -22,9 +22,22 @@ from pydantic import BaseModel
|
||||
import scanner as sc
|
||||
|
||||
app = FastAPI(title="Duplicate Finder")
|
||||
templates = Jinja2Templates(directory="/app/templates")
|
||||
|
||||
app.mount("/static", StaticFiles(directory="/app/static"), name="static")
|
||||
# Resolve paths relative to this file so it works both in Docker and locally
|
||||
_BASE = Path(__file__).parent
|
||||
_TEMPLATES_DIR = (
|
||||
str(_BASE / "templates") if (_BASE / "templates").exists()
|
||||
else str(_BASE.parent / "templates") if (_BASE.parent / "templates").exists()
|
||||
else "/app/templates"
|
||||
)
|
||||
_STATIC_DIR = str(_BASE / "static")
|
||||
_STATIC_DIR = _STATIC_DIR if Path(_STATIC_DIR).exists() else "/app/static"
|
||||
# Ensure static dir exists
|
||||
Path(_STATIC_DIR).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
templates = Jinja2Templates(directory=_TEMPLATES_DIR)
|
||||
|
||||
app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
|
||||
|
||||
METHOD_META = {
|
||||
"sha256": {"color": "#378ADD", "label": "Exact copy"},
|
||||
@@ -502,6 +515,32 @@ def get_file_meta(file_id: int):
|
||||
|
||||
# ── Stats ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
@app.get("/api/browse")
|
||||
def browse(path: str = Query("/")):
|
||||
"""List subdirectories at the given path for the folder picker."""
|
||||
try:
|
||||
p = Path(path).resolve()
|
||||
except Exception:
|
||||
raise HTTPException(400, "Invalid path")
|
||||
if not p.exists() or not p.is_dir():
|
||||
raise HTTPException(404, "Path not found")
|
||||
|
||||
dirs = []
|
||||
try:
|
||||
for entry in sorted(p.iterdir()):
|
||||
if entry.is_dir() and not entry.name.startswith("."):
|
||||
dirs.append(entry.name)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
parent = str(p.parent) if p != p.parent else None
|
||||
return {
|
||||
"current": str(p),
|
||||
"parent": parent,
|
||||
"dirs": dirs,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/stats")
|
||||
def get_stats():
|
||||
con = get_db()
|
||||
|
||||
@@ -35,7 +35,9 @@ VIDEO_EXT = {
|
||||
|
||||
SUPPORTED_EXT = PHOTO_EXT | VIDEO_EXT
|
||||
|
||||
DB_PATH = "/data/dupfinder.db"
|
||||
_DATA_DIR = Path("/data") if Path("/data").exists() else Path(__file__).parent.parent / "data"
|
||||
_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
DB_PATH = str(_DATA_DIR / "dupfinder.db")
|
||||
|
||||
# Shared scan state (updated by background thread, read by status endpoint)
|
||||
scan_state = {
|
||||
|
||||
Reference in New Issue
Block a user