reorder
This commit is contained in:
@@ -120,33 +120,40 @@ async def _track_activity(request, call_next):
|
||||
_last_request_time = time.time()
|
||||
return await call_next(request)
|
||||
|
||||
def _sync_car_html():
|
||||
src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "car.html")
|
||||
if not os.path.exists(src):
|
||||
return
|
||||
try:
|
||||
dest = os.path.join(_load_output_dir(), "car.html")
|
||||
shutil.copy2(src, dest)
|
||||
print(f"[car.html] synced → {dest}")
|
||||
except Exception as e:
|
||||
print(f"[car.html] sync warning: {e}")
|
||||
def _sync_frontend():
|
||||
for name in ["car.html", "trash.html"]:
|
||||
src = os.path.join(_HERE, name)
|
||||
if not os.path.exists(src):
|
||||
continue
|
||||
try:
|
||||
dest = os.path.join(_load_output_dir(), name)
|
||||
shutil.copy2(src, dest)
|
||||
print(f"[{name}] synced → {dest}")
|
||||
except Exception as e:
|
||||
print(f"[{name}] sync warning: {e}")
|
||||
|
||||
|
||||
def _watch_car_html():
|
||||
src = os.path.join(os.path.dirname(os.path.abspath(__file__)), "car.html")
|
||||
last_mtime = os.path.getmtime(src) if os.path.exists(src) else 0
|
||||
def _watch_frontend():
|
||||
files = ["car.html", "trash.html"]
|
||||
last_mtimes = {}
|
||||
for name in files:
|
||||
src = os.path.join(_HERE, name)
|
||||
if os.path.exists(src):
|
||||
last_mtimes[name] = os.path.getmtime(src)
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
try:
|
||||
mtime = os.path.getmtime(src)
|
||||
if mtime != last_mtime:
|
||||
last_mtime = mtime
|
||||
dest = os.path.join(_load_output_dir(), "car.html")
|
||||
shutil.copy2(src, dest)
|
||||
print(f"[car.html] change detected → synced to {dest}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for name in files:
|
||||
src = os.path.join(_HERE, name)
|
||||
if not os.path.exists(src): continue
|
||||
try:
|
||||
mtime = os.path.getmtime(src)
|
||||
if mtime != last_mtimes.get(name):
|
||||
last_mtimes[name] = mtime
|
||||
dest = os.path.join(_load_output_dir(), name)
|
||||
shutil.copy2(src, dest)
|
||||
print(f"[{name}] change detected → synced to {dest}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _load_wireframe_dir() -> str:
|
||||
with open(CONFIG_PATH, "r") as f:
|
||||
@@ -160,6 +167,60 @@ def _load_faceswap_model_path() -> str:
|
||||
return os.path.expanduser(conf.get("faceswap_model", "~/.insightface/models/inswapper_128.onnx"))
|
||||
|
||||
|
||||
def _run_consistency_check():
|
||||
"""Identifies DB records with missing files and files on disk with no DB record."""
|
||||
try:
|
||||
output_dir = _load_output_dir()
|
||||
data_dir = os.path.join(output_dir, "_data")
|
||||
os.makedirs(data_dir, exist_ok=True)
|
||||
|
||||
persons = database.list_persons(include_archived=True)
|
||||
missing_files = []
|
||||
for p in persons:
|
||||
filename = p[0]
|
||||
fpath = os.path.join(output_dir, filename)
|
||||
if not os.path.exists(fpath):
|
||||
missing_files.append({
|
||||
"filename": filename,
|
||||
"group_id": p[2],
|
||||
"name": p[1]
|
||||
})
|
||||
|
||||
# Untracked files
|
||||
db_filenames = set(p[0] for p in persons)
|
||||
untracked_files = []
|
||||
if os.path.isdir(output_dir):
|
||||
for f in os.listdir(output_dir):
|
||||
if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp', '.mp4')):
|
||||
# Skip internal folders and data files
|
||||
if f.startswith('_') or f == 'car.html' or f == 'trash.html':
|
||||
continue
|
||||
if f not in db_filenames:
|
||||
untracked_files.append(f)
|
||||
|
||||
report = {
|
||||
"timestamp": time.time(),
|
||||
"missing_files": missing_files,
|
||||
"untracked_files": untracked_files
|
||||
}
|
||||
_write_json(os.path.join(data_dir, "inconsistencies.json"), report)
|
||||
print(f"[consistency] check complete: {len(missing_files)} missing, {len(untracked_files)} untracked")
|
||||
return report
|
||||
except Exception as e:
|
||||
print(f"[consistency] check failed: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _consistency_check_daemon():
|
||||
"""Runs daily consistency check."""
|
||||
# Wait for startup
|
||||
time.sleep(30)
|
||||
while True:
|
||||
_run_consistency_check()
|
||||
# Sleep for 24 hours
|
||||
time.sleep(86400)
|
||||
|
||||
|
||||
def _idle_turntable_daemon():
|
||||
"""
|
||||
Background daemon: when the API has been idle > IDLE_THRESHOLD seconds,
|
||||
@@ -308,9 +369,10 @@ def on_startup():
|
||||
database.migrate_schema()
|
||||
except Exception as e:
|
||||
print(f"DB migration warning: {e}")
|
||||
_sync_car_html()
|
||||
threading.Thread(target=_watch_car_html, daemon=True).start()
|
||||
_sync_frontend()
|
||||
threading.Thread(target=_watch_frontend, daemon=True).start()
|
||||
threading.Thread(target=_idle_turntable_daemon, daemon=True).start()
|
||||
threading.Thread(target=_consistency_check_daemon, daemon=True).start()
|
||||
# Mount wireframe static dir for browser video preview
|
||||
try:
|
||||
wf_dir = _load_wireframe_dir()
|
||||
@@ -1558,6 +1620,10 @@ def update_config(update: ConfigUpdate):
|
||||
return {"prompt": conf["prompt"], "seed": conf["seed"]}
|
||||
|
||||
|
||||
class RepairRequest(BaseModel):
|
||||
action: str # "delete_record", "import_file"
|
||||
filename: str
|
||||
|
||||
class BatchRequest(BaseModel):
|
||||
filenames: list[str]
|
||||
prompt: str | list[str]
|
||||
@@ -2232,6 +2298,39 @@ def get_similar(filename: str, limit: int = 10):
|
||||
return {"filename": filename, "similar": similar}
|
||||
|
||||
|
||||
@app.get("/db/inconsistencies")
|
||||
def get_inconsistencies(run_now: bool = False):
|
||||
"""Return the last consistency report, optionally running a new check."""
|
||||
if run_now:
|
||||
return _run_consistency_check()
|
||||
output_dir = _load_output_dir()
|
||||
path = os.path.join(output_dir, "_data", "inconsistencies.json")
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as f:
|
||||
return json.load(f)
|
||||
return _run_consistency_check()
|
||||
|
||||
@app.post("/db/repair")
|
||||
def db_repair(req: RepairRequest):
|
||||
"""Repair a specific inconsistency."""
|
||||
output_dir = _load_output_dir()
|
||||
if req.action == "delete_record":
|
||||
database.delete_person(req.filename)
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"status": "deleted", "filename": req.filename}
|
||||
elif req.action == "import_file":
|
||||
fpath = os.path.join(output_dir, req.filename)
|
||||
if not os.path.exists(fpath):
|
||||
raise HTTPException(404, "File not found on disk")
|
||||
# Basic import: just register it in DB
|
||||
database.upsert_person(req.filename, filepath=fpath, group_id=naming.get_base_name(req.filename))
|
||||
_invalidate_static()
|
||||
_run_consistency_check()
|
||||
return {"status": "imported", "filename": req.filename}
|
||||
else:
|
||||
raise HTTPException(400, "Invalid action")
|
||||
|
||||
@app.post("/db/cleanup")
|
||||
def db_cleanup():
|
||||
"""Delete DB records for files that no longer exist on disk."""
|
||||
|
||||
Reference in New Issue
Block a user