Implement temporary storage monitoring and clean up system maintenance features

This commit is contained in:
2026-06-29 08:10:53 -04:00
parent 71f7b89099
commit c653bd1eed
3 changed files with 302 additions and 0 deletions
+132
View File
@@ -1074,5 +1074,137 @@ def get_upload_file(filename: str):
raise HTTPException(status_code=404, detail="File not found.")
return FileResponse(file_path)
class CleanupTempRequest(BaseModel):
paths: List[str] | None = None
def get_dir_stats(path: str) -> tuple[int, int]:
total_size = 0
total_files = 0
if not os.path.exists(path):
return 0, 0
for root, dirs, files in os.walk(path):
for f in files:
fp = os.path.join(root, f)
try:
if os.path.exists(fp):
total_size += os.path.getsize(fp)
total_files += 1
except Exception:
pass
return total_size, total_files
@app.get("/api/system/temp-info")
def get_temp_info():
temp_folders = []
# 1. Collect all potential temp root directories
# Default temp root
temp_roots = {upscaler.TEMP_DIR}
# Custom temp roots from jobs db
for job in jobs_db.values():
if getattr(job, "temp_dir", None):
temp_roots.add(job.temp_dir)
# 2. Scan folders inside the temp roots
seen_paths = set()
for root_dir in temp_roots:
if not os.path.exists(root_dir):
continue
try:
for item in os.listdir(root_dir):
item_path = os.path.join(root_dir, item)
if not os.path.isdir(item_path):
continue
if item_path in seen_paths:
continue
seen_paths.add(item_path)
# Check if it corresponds to a job_id (which is usually a UUID)
job_id = item
job = jobs_db.get(job_id)
size_bytes, file_count = get_dir_stats(item_path)
if job:
job_status = job.status
video_name = os.path.basename(job.video_path)
else:
job_status = "orphaned"
video_name = "Unknown Video (Orphaned)"
temp_folders.append({
"job_id": job_id,
"path": item_path,
"size_bytes": size_bytes,
"file_count": file_count,
"status": job_status,
"video_name": video_name
})
except Exception as e:
print(f"Error scanning temp root {root_dir}: {e}")
return {"temp_folders": temp_folders}
@app.post("/api/system/temp-cleanup")
def cleanup_temp(req: CleanupTempRequest):
import shutil
cleaned = []
errors = []
# Identify active job IDs
active_statuses = ["analyzing", "extracting", "upscaling", "restoring_faces", "interpolating", "assembling"]
active_job_ids = {job.job_id for job in jobs_db.values() if job.status in active_statuses}
# Get current temp folder status
temp_info = get_temp_info()
folders = temp_info["temp_folders"]
target_paths = req.paths if req.paths is not None else [f["path"] for f in folders]
for f in folders:
path = f["path"]
if path not in target_paths:
continue
# Safety checks:
if f["job_id"] in active_job_ids:
errors.append(f"Cannot clean up active job {f['job_id']}")
continue
resolved_path = os.path.realpath(path)
is_sub_of_temp = resolved_path.startswith(os.path.realpath(upscaler.TEMP_DIR))
if not is_sub_of_temp:
for job in jobs_db.values():
if getattr(job, "temp_dir", None):
if resolved_path.startswith(os.path.realpath(job.temp_dir)):
is_sub_of_temp = True
break
if not is_sub_of_temp:
errors.append(f"Safety constraint: Path {path} is not in a valid temp root.")
continue
try:
if os.path.exists(path):
shutil.rmtree(path)
cleaned.append(path)
# Update DB state if needed
job = jobs_db.get(f["job_id"])
if job and job.status in ["paused", "interrupted", "failed"]:
job.status = "interrupted"
job.progress = 0.0
job.current_frame = 0
job.eta = "Temp files cleaned. Will restart on resume."
else:
cleaned.append(path)
except Exception as e:
errors.append(f"Error removing {path}: {str(e)}")
save_jobs_db()
return {"cleaned": cleaned, "errors": errors}
# Mount static folder
app.mount("/", StaticFiles(directory=os.path.join(upscaler.BASE_DIR, "static"), html=True), name="static")
+134
View File
@@ -2084,6 +2084,140 @@ async function initializeApp() {
}
});
}
// Temporary storage maintenance handlers
const refreshTempBtn = document.getElementById("refresh-temp-btn");
if (refreshTempBtn) {
refreshTempBtn.addEventListener("click", loadTempInfo);
}
const cleanupTempBtn = document.getElementById("cleanup-temp-btn");
if (cleanupTempBtn) {
cleanupTempBtn.addEventListener("click", async () => {
if (confirm("Are you sure you want to clean up all inactive temporary directories?\nThis will clear any completed, cancelled, failed, or orphaned job frames. Paused or interrupted jobs will have to restart from the beginning on resume.")) {
try {
const cleanRes = await fetch("/api/system/temp-cleanup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ paths: null })
});
if (cleanRes.ok) {
loadTempInfo();
loadQueue();
} else {
alert("Cleanup failed.");
}
} catch (err) {
console.error(err);
}
}
});
}
// Refresh queue button should also refresh temp files info
const refreshQueueBtn = document.getElementById("refresh-queue-btn");
if (refreshQueueBtn) {
refreshQueueBtn.addEventListener("click", loadTempInfo);
}
// Load initial temp info
loadTempInfo();
}
async function loadTempInfo() {
const listContainer = document.getElementById("temp-files-list");
const totalSizeBadge = document.getElementById("temp-total-size");
const folderCountBadge = document.getElementById("temp-folder-count");
if (!listContainer) return;
try {
const res = await fetch("/api/system/temp-info");
if (!res.ok) return;
const data = await res.json();
const folders = data.temp_folders || [];
let totalSize = 0;
folders.forEach(f => totalSize += (f.size_bytes || 0));
if (totalSizeBadge) totalSizeBadge.textContent = formatBytes(totalSize);
if (folderCountBadge) folderCountBadge.textContent = folders.length;
if (folders.length === 0) {
listContainer.innerHTML = `
<div style="text-align: center; padding: 2rem; border: 1px dashed var(--border-color); border-radius: 12px; color: var(--text-muted);">
<p>No temporary directories found.</p>
</div>
`;
return;
}
listContainer.innerHTML = "";
folders.forEach(f => {
const item = document.createElement("div");
item.className = "queue-item";
const sizeStr = formatBytes(f.size_bytes || 0);
const isActive = ["analyzing", "extracting", "upscaling", "restoring_faces", "interpolating", "assembling"].includes(f.status);
let statusLabel = f.status.toUpperCase();
let statusClass = f.status;
if (f.status === "orphaned") {
statusLabel = "Orphaned (No Job)";
statusClass = "interrupted";
}
item.innerHTML = `
<div style="display: flex; align-items: center; gap: 1rem; width: 100%; flex-wrap: wrap;">
<div class="queue-item-details" style="flex: 1; min-width: 200px;">
<div class="queue-item-title" style="word-break: break-all; font-size: 0.88rem;">
<i class="fa-solid fa-folder-open" style="color: var(--primary);"></i> ${f.path}
</div>
<div class="queue-item-meta" style="margin-top: 0.25rem;">
<span><i class="fa-solid fa-file-video"></i> Video: ${f.video_name}</span>
<span>Size: ${sizeStr} (${f.file_count} files)</span>
</div>
</div>
<div class="queue-item-status-bar" style="border: none; padding: 0;">
<span class="status-badge ${statusClass}" style="font-size: 0.7rem; padding: 0.15rem 0.5rem; margin-right: 0.5rem;">${statusLabel}</span>
<div class="queue-item-actions">
<button class="btn btn-danger btn-small btn-delete-temp" data-path="${f.path}" ${isActive ? 'disabled style="opacity: 0.5; cursor: not-allowed;" title="Cannot delete active job frames"' : ''}>
<i class="fa-solid fa-trash"></i> Delete
</button>
</div>
</div>
</div>
`;
const deleteBtn = item.querySelector(".btn-delete-temp");
if (deleteBtn && !isActive) {
deleteBtn.addEventListener("click", async (e) => {
e.stopPropagation();
if (confirm(`Are you sure you want to delete this temporary folder?\nPath: ${f.path}`)) {
try {
const cleanRes = await fetch("/api/system/temp-cleanup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ paths: [f.path] })
});
if (cleanRes.ok) {
loadTempInfo();
loadQueue();
} else {
alert("Failed to delete temp folder.");
}
} catch (err) {
console.error(err);
}
}
});
}
listContainer.appendChild(item);
});
} catch (err) {
console.error("Error loading temp files info:", err);
}
}
initializeApp();
+36
View File
@@ -564,6 +564,42 @@
</div>
</section>
<!-- Temporary Storage & Maintenance Section -->
<section class="maintenance-section glass-card" style="margin-top: 3rem;">
<div class="queue-header">
<h2><i class="fa-solid fa-hard-drive"></i> Temporary Storage & Maintenance</h2>
<div style="display: flex; gap: 0.5rem;">
<button type="button" id="refresh-temp-btn" class="btn btn-secondary btn-small">
<i class="fa-solid fa-arrows-rotate"></i> Refresh
</button>
<button type="button" id="cleanup-temp-btn" class="btn btn-danger btn-small">
<i class="fa-solid fa-broom"></i> Clean Up Inactive Temp
</button>
</div>
</div>
<p style="color: var(--text-muted); font-size: 0.9rem; margin-bottom: 1.5rem;">
Monitor temporary image frames created during upscaling. Running jobs cannot be cleared.
</p>
<div class="temp-stats-summary" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem;">
<div class="glass-card" style="padding: 1rem; background: rgba(255,255,255,0.01); text-align: center;">
<span style="font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; display: block;">Total Temp Size</span>
<strong id="temp-total-size" style="font-size: 1.5rem; color: var(--primary);">0 Bytes</strong>
</div>
<div class="glass-card" style="padding: 1rem; background: rgba(255,255,255,0.01); text-align: center;">
<span style="font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; display: block;">Temp Folders Count</span>
<strong id="temp-folder-count" style="font-size: 1.5rem; color: var(--primary);">0</strong>
</div>
</div>
<div id="temp-files-list" style="display: flex; flex-direction: column; gap: 0.75rem;">
<!-- Temp files populated dynamically -->
<div style="text-align: center; padding: 2rem; border: 1px dashed var(--border-color); border-radius: 12px; color: var(--text-muted);">
<p>No temporary directories found.</p>
</div>
</div>
</section>
<!-- Completed Video Gallery -->
<section class="gallery-section glass-card" style="margin-top: 3rem;">
<div class="gallery-header">