reorder
This commit is contained in:
@@ -120,6 +120,39 @@ async def _track_activity(request, call_next):
|
||||
_last_request_time = time.time()
|
||||
return await call_next(request)
|
||||
|
||||
def _sync_preloaded_images():
|
||||
"""Update PRELOADED_IMAGES in car.html (both source and output) to match current DB state."""
|
||||
try:
|
||||
output_dir = _load_output_dir()
|
||||
paths = [
|
||||
os.path.join(_HERE, "car.html"),
|
||||
os.path.join(output_dir, "car.html")
|
||||
]
|
||||
|
||||
persons = database.list_persons(include_archived=False)
|
||||
# Only include if file actually exists on disk
|
||||
db_images = [p[0] for p in persons if os.path.exists(os.path.join(output_dir, p[0]))]
|
||||
|
||||
# Sort by mtime, newest first
|
||||
db_images.sort(key=lambda x: os.path.getmtime(os.path.join(output_dir, x)), reverse=True)
|
||||
|
||||
images_json = json.dumps(db_images, indent=12).strip()
|
||||
# Format for JS insertion
|
||||
images_json = images_json.replace('\n', '\n ')
|
||||
pattern = r'// --- HYDRATION_START ---.*?// --- HYDRATION_END ---'
|
||||
replacement = f'// --- HYDRATION_START ---\n const PRELOADED_IMAGES = {images_json};\n // --- HYDRATION_END ---'
|
||||
|
||||
for p in paths:
|
||||
if not os.path.exists(p): continue
|
||||
with open(p, 'r') as f:
|
||||
content = f.read()
|
||||
new_content = re.sub(pattern, replacement, content, flags=re.DOTALL)
|
||||
with open(p, 'w') as f:
|
||||
f.write(new_content)
|
||||
print(f"[static] Updated {p} with {len(db_images)} preloaded images")
|
||||
except Exception as e:
|
||||
print(f"[static] Failed to update car.html hydration: {e}")
|
||||
|
||||
def _sync_frontend():
|
||||
for name in ["car.html", "trash.html"]:
|
||||
src = os.path.join(_HERE, name)
|
||||
@@ -168,7 +201,7 @@ def _load_faceswap_model_path() -> str:
|
||||
|
||||
|
||||
def _run_consistency_check():
|
||||
"""Identifies DB records with missing files and files on disk with no DB record."""
|
||||
"""Identifies DB records with missing files, files on disk with no DB record, and archived items."""
|
||||
try:
|
||||
output_dir = _load_output_dir()
|
||||
data_dir = os.path.join(output_dir, "_data")
|
||||
@@ -176,8 +209,17 @@ def _run_consistency_check():
|
||||
|
||||
persons = database.list_persons(include_archived=True)
|
||||
missing_files = []
|
||||
archived_items = []
|
||||
for p in persons:
|
||||
filename = p[0]
|
||||
is_archived = bool(p[14]) if p[14] else False
|
||||
if is_archived:
|
||||
archived_items.append({
|
||||
"filename": filename,
|
||||
"group_id": p[2],
|
||||
"name": p[1]
|
||||
})
|
||||
|
||||
fpath = os.path.join(output_dir, filename)
|
||||
if not os.path.exists(fpath):
|
||||
missing_files.append({
|
||||
@@ -201,10 +243,11 @@ def _run_consistency_check():
|
||||
report = {
|
||||
"timestamp": time.time(),
|
||||
"missing_files": missing_files,
|
||||
"untracked_files": untracked_files
|
||||
"untracked_files": untracked_files,
|
||||
"archived_items": archived_items
|
||||
}
|
||||
_write_json(os.path.join(data_dir, "inconsistencies.json"), report)
|
||||
print(f"[consistency] check complete: {len(missing_files)} missing, {len(untracked_files)} untracked")
|
||||
print(f"[consistency] check complete: {len(missing_files)} missing, {len(untracked_files)} untracked, {len(archived_items)} archived")
|
||||
return report
|
||||
except Exception as e:
|
||||
print(f"[consistency] check failed: {e}")
|
||||
@@ -1236,10 +1279,16 @@ def _write_all_static() -> None:
|
||||
|
||||
# images.json — all images (frontend filters by .archived field)
|
||||
db_images = []
|
||||
archived_count = 0
|
||||
for p in persons:
|
||||
fpath = os.path.join(output_dir, p[0])
|
||||
if not os.path.exists(fpath):
|
||||
continue
|
||||
|
||||
is_archived = bool(p[14]) if p[14] else False
|
||||
if is_archived:
|
||||
archived_count += 1
|
||||
|
||||
db_images.append({
|
||||
"filename": p[0],
|
||||
"name": p[1],
|
||||
@@ -1255,8 +1304,9 @@ def _write_all_static() -> None:
|
||||
"has_clothing": p[11],
|
||||
"content_type": p[12] or "image",
|
||||
"faceswap_source_video": p[13],
|
||||
"archived": bool(p[14]) if p[14] else False,
|
||||
"archived": is_archived,
|
||||
})
|
||||
print(f"[static] write_all: {len(db_images)} total images, {archived_count} archived")
|
||||
try:
|
||||
db_images.sort(
|
||||
key=lambda x: os.path.getmtime(os.path.join(output_dir, x["filename"])),
|
||||
@@ -1293,6 +1343,9 @@ def _write_all_static() -> None:
|
||||
_write_json(os.path.join(data_dir, "config.json"), json.load(_f))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Update car.html PRELOADED_IMAGES
|
||||
_sync_preloaded_images()
|
||||
|
||||
except Exception as e:
|
||||
print(f"[static] write_all error: {e}")
|
||||
@@ -1617,8 +1670,11 @@ def update_config(update: ConfigUpdate):
|
||||
return {"seed": conf["seed"]}
|
||||
|
||||
|
||||
class GroupArchiveRequest(BaseModel):
|
||||
filenames: list[str]
|
||||
|
||||
class RepairRequest(BaseModel):
|
||||
action: str # "delete_record", "import_file"
|
||||
action: str # "delete_record", "import_file", "restore", "delete_permanently"
|
||||
filename: str
|
||||
|
||||
class BatchRequest(BaseModel):
|
||||
@@ -2309,7 +2365,7 @@ def get_inconsistencies(run_now: bool = False):
|
||||
|
||||
@app.post("/db/repair")
|
||||
def db_repair(req: RepairRequest):
|
||||
"""Repair a specific inconsistency."""
|
||||
"""Repair a specific inconsistency or manage archived items."""
|
||||
output_dir = _load_output_dir()
|
||||
if req.action == "delete_record":
|
||||
database.delete_person(req.filename)
|
||||
@@ -2325,6 +2381,30 @@ def db_repair(req: RepairRequest):
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"status": "imported", "filename": req.filename}
|
||||
elif req.action == "restore":
|
||||
database.set_archived(req.filename, False)
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"status": "restored", "filename": req.filename}
|
||||
elif req.action == "delete_permanently":
|
||||
person = database.get_person(req.filename)
|
||||
# In person table, p[5] is filepath.
|
||||
# Wait, let me check the column order in database.py
|
||||
# list_persons: filename, name, group_id, clip_description, prompt, pose, sort_order, group_name, hidden, has_background, source_refs, has_clothing, content_type, faceswap_source_video, archived, face_embedding
|
||||
# get_person: returns same?
|
||||
if person:
|
||||
# We need the absolute path. get_person returns relative path usually or we construct it.
|
||||
# Actually database.py upsert_person stores filepath if provided.
|
||||
# Let's check database.get_person.
|
||||
pass
|
||||
|
||||
fpath = os.path.join(output_dir, req.filename)
|
||||
if os.path.exists(fpath):
|
||||
_move_to_trash(fpath)
|
||||
database.delete_person(req.filename)
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"status": "deleted_permanently", "filename": req.filename}
|
||||
else:
|
||||
raise HTTPException(400, "Invalid action")
|
||||
|
||||
@@ -2574,6 +2654,7 @@ def archive_image(filename: str):
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"filename": filename, "archived": True}
|
||||
|
||||
|
||||
@@ -2584,6 +2665,7 @@ def unarchive_image(filename: str):
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"filename": filename, "archived": False}
|
||||
|
||||
|
||||
@@ -2744,6 +2826,53 @@ def delete_image(filename: str):
|
||||
return {"status": "deleted", "filename": filename}
|
||||
|
||||
|
||||
@app.post("/groups/{group_id:path}/archive")
|
||||
def archive_group(group_id: str, req: GroupArchiveRequest = None):
|
||||
try:
|
||||
filenames = req.filenames if req else []
|
||||
# If no filenames provided in body, try to find them by group_id
|
||||
if not filenames:
|
||||
files = database.get_group_files(group_id)
|
||||
filenames = [f[0] for f in files]
|
||||
|
||||
# Still nothing? Archive the group_id as a filename fallback
|
||||
if not filenames:
|
||||
filenames = [group_id]
|
||||
|
||||
updated = database.set_filenames_archived(filenames, True)
|
||||
count = len(updated)
|
||||
print(f"[archive] Archived group {group_id} ({count} items): {updated}")
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"status": "archived", "group_id": group_id, "count": count, "filenames": updated}
|
||||
|
||||
|
||||
@app.post("/groups/{group_id:path}/unarchive")
|
||||
def unarchive_group(group_id: str, req: GroupArchiveRequest = None):
|
||||
try:
|
||||
filenames = req.filenames if req else []
|
||||
if not filenames:
|
||||
# For unarchive, we might not have the files in the group yet if they are archived
|
||||
# but database.list_persons(include_archived=True) would find them.
|
||||
# Actually database.get_group_files(group_id) also finds them as it doesn't filter.
|
||||
files = database.get_group_files(group_id)
|
||||
filenames = [f[0] for f in files]
|
||||
|
||||
if not filenames:
|
||||
filenames = [group_id]
|
||||
|
||||
updated = database.set_filenames_archived(filenames, False)
|
||||
count = len(updated)
|
||||
print(f"[unarchive] Unarchived group {group_id} ({count} items): {updated}")
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"status": "unarchived", "group_id": group_id, "count": count, "filenames": updated}
|
||||
|
||||
|
||||
@app.delete("/groups/{group_id:path}")
|
||||
def delete_group(group_id: str):
|
||||
files = database.get_group_files(group_id)
|
||||
|
||||
Reference in New Issue
Block a user