Delete Comfy video, image, and audio files from the desktop after they are saved to the library.
Audio never called purge, image stills used untagged names, and a locked file could abort the whole host-agent delete. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+129
-20
@@ -2,7 +2,7 @@ import http from 'node:http'
|
||||
import net from 'node:net'
|
||||
import { execFile, spawn } from 'node:child_process'
|
||||
import { promisify } from 'node:util'
|
||||
import { readdirSync, existsSync, rmSync, readFileSync, openSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs'
|
||||
import { readdirSync, existsSync, rmSync, readFileSync, openSync, mkdirSync, writeFileSync, unlinkSync, statSync } from 'node:fs'
|
||||
import { basename, dirname, join, resolve, relative, isAbsolute } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
|
||||
@@ -584,19 +584,6 @@ function rootsForType(type) {
|
||||
return [...outputRoots(), ...tempRoots()]
|
||||
}
|
||||
|
||||
function removeNamedFile(roots, filename, subfolders) {
|
||||
const deleted = []
|
||||
const subs = [...new Set((subfolders || []).map(item => String(item || '')))]
|
||||
for (const root of roots) {
|
||||
if (!existsSync(root)) continue
|
||||
for (const sub of subs) {
|
||||
const path = safeFile(root, sub, filename)
|
||||
if (removeFile(path)) deleted.push(path)
|
||||
}
|
||||
}
|
||||
return deleted
|
||||
}
|
||||
|
||||
function safeFile(root, subfolder, filename) {
|
||||
const name = basename(String(filename || ''))
|
||||
if (!name || name === '.' || name === '..') return null
|
||||
@@ -609,8 +596,91 @@ function safeFile(root, subfolder, filename) {
|
||||
|
||||
function removeFile(path) {
|
||||
if (!path || !existsSync(path)) return false
|
||||
rmSync(path, { force: true })
|
||||
return true
|
||||
try {
|
||||
rmSync(path, { force: true })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function fileStem(filename) {
|
||||
const name = basename(String(filename || ''))
|
||||
const dot = name.lastIndexOf('.')
|
||||
return dot > 0 ? name.slice(0, dot) : name
|
||||
}
|
||||
|
||||
function removeNamedFile(roots, filename, subfolders) {
|
||||
const deleted = []
|
||||
const name = basename(String(filename || ''))
|
||||
if (!name) return deleted
|
||||
const stem = fileStem(name)
|
||||
const subs = [...new Set((subfolders || []).map(item => String(item || '')))]
|
||||
for (const root of roots) {
|
||||
if (!existsSync(root)) continue
|
||||
for (const sub of subs) {
|
||||
const dir = sub ? join(root, String(sub).replace(/\.\./g, '')) : root
|
||||
const exact = safeFile(root, sub, name)
|
||||
if (removeFile(exact)) deleted.push(exact)
|
||||
if (!stem || !existsSync(dir)) continue
|
||||
let files = []
|
||||
try { files = readdirSync(dir) } catch { continue }
|
||||
for (const file of files) {
|
||||
if (file === name || file.startsWith(`${stem}.`)) {
|
||||
const path = join(dir, file)
|
||||
if (removeFile(path)) deleted.push(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deleted
|
||||
}
|
||||
|
||||
function defaultSweepPrefixes() {
|
||||
return ['MiniMax_H3', 'xAIGen', 'XAIgen', 'LTX23', 'AIGen', 'v2-krea', 'v2-generate']
|
||||
}
|
||||
|
||||
function looksLikeStudioStill(name) {
|
||||
return /^[0-9a-f]{12}(_p\d+)?(_\d{5})?_?\.(png|jpe?g|webp)$/i.test(String(name || ''))
|
||||
}
|
||||
|
||||
function isStudioOutputName(file, sub, prefixes) {
|
||||
if (looksLikeStudioStill(file) && (!sub || sub === 'image' || sub === 'still')) return true
|
||||
if (sub === 'audio') {
|
||||
return prefixes.some(prefix => file.startsWith(prefix)) || file.startsWith('ComfyUI')
|
||||
}
|
||||
if (!sub) {
|
||||
return prefixes.some(prefix => prefix !== 'LTX23' && file.startsWith(prefix))
|
||||
}
|
||||
return prefixes.some(prefix => file.startsWith(prefix))
|
||||
}
|
||||
|
||||
function sweepStaleStudioOutputs(prefixes, subfolders, maxAgeMs = 15 * 60 * 1000) {
|
||||
const deleted = []
|
||||
const names = [...new Set([...defaultSweepPrefixes(), ...(prefixes || [])])].map(item => String(item || '').trim()).filter(Boolean)
|
||||
const subs = [...new Set(['video', 'audio', 'image', 'still', ...(subfolders || [])])].map(item => String(item || ''))
|
||||
const cutoff = Date.now() - Math.max(60_000, Number(maxAgeMs) || 900_000)
|
||||
for (const root of outputRoots()) {
|
||||
if (!existsSync(root)) continue
|
||||
for (const sub of ['', ...subs]) {
|
||||
const dir = sub ? join(root, sub) : root
|
||||
if (!existsSync(dir)) continue
|
||||
let files = []
|
||||
try { files = readdirSync(dir) } catch { continue }
|
||||
for (const file of files) {
|
||||
if (!isStudioOutputName(file, sub, names)) continue
|
||||
const path = join(dir, file)
|
||||
try {
|
||||
const st = statSync(path)
|
||||
if (st.isDirectory() || st.mtimeMs > cutoff) continue
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
if (removeFile(path)) deleted.push(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return deleted
|
||||
}
|
||||
|
||||
function readJson(req) {
|
||||
@@ -649,25 +719,37 @@ function purgeDesktopFiles(body) {
|
||||
[video.subfolder, imageOut ? '' : 'video', '']
|
||||
))
|
||||
}
|
||||
const audio = body?.audio || null
|
||||
const audioName = String(audio?.filename || '')
|
||||
if (audioName) {
|
||||
deleted.push(...removeNamedFile(
|
||||
rootsForType(String(audio?.type || 'output')),
|
||||
audioName,
|
||||
[audio.subfolder, 'audio', '']
|
||||
))
|
||||
}
|
||||
const output = body?.output || null
|
||||
const outputName = String(output?.filename || '')
|
||||
if (outputName) {
|
||||
deleted.push(...removeNamedFile(
|
||||
rootsForType(String(output?.type || 'output')),
|
||||
outputName,
|
||||
[output.subfolder, '']
|
||||
[output.subfolder, 'image', 'still', '']
|
||||
))
|
||||
}
|
||||
deleted.push(...sweepStaleStudioOutputs(body?.sweepPrefixes, body?.sweepSubfolders, body?.sweepMaxAgeMs))
|
||||
const unique = [...new Set(deleted)]
|
||||
console.log(JSON.stringify({
|
||||
src: 'comfy-host-agent',
|
||||
event: 'purge',
|
||||
deleted: deleted.length,
|
||||
files: deleted.map((path) => basename(path)),
|
||||
deleted: unique.length,
|
||||
files: unique.map((path) => basename(path)),
|
||||
imageNames: names,
|
||||
video: videoName || null,
|
||||
audio: audioName || null,
|
||||
output: outputName || null
|
||||
}))
|
||||
return { ok: true, deleted }
|
||||
return { ok: true, deleted: unique }
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
@@ -760,14 +842,41 @@ const server = http.createServer(async (req, res) => {
|
||||
const body = await readJson(req)
|
||||
return json(res, 200, purgeDesktopFiles(body))
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/sweep') {
|
||||
const body = await readJson(req)
|
||||
const deleted = sweepStaleStudioOutputs(body?.sweepPrefixes, body?.sweepSubfolders, body?.sweepMaxAgeMs)
|
||||
console.log(JSON.stringify({
|
||||
src: 'comfy-host-agent',
|
||||
event: 'sweep',
|
||||
deleted: deleted.length,
|
||||
files: deleted.map((path) => basename(path))
|
||||
}))
|
||||
return json(res, 200, { ok: true, deleted })
|
||||
}
|
||||
json(res, 404, { ok: false, error: 'not found' })
|
||||
})
|
||||
|
||||
function logStartupSweep() {
|
||||
try {
|
||||
const deleted = sweepStaleStudioOutputs()
|
||||
if (!deleted.length) return
|
||||
console.log(JSON.stringify({
|
||||
src: 'comfy-host-agent',
|
||||
event: 'startup-sweep',
|
||||
deleted: deleted.length,
|
||||
files: deleted.map((path) => basename(path))
|
||||
}))
|
||||
} catch (error) {
|
||||
console.log(JSON.stringify({ src: 'comfy-host-agent', event: 'startup-sweep-error', error: String(error?.message || error) }))
|
||||
}
|
||||
}
|
||||
|
||||
server.listen(port, '0.0.0.0', async () => {
|
||||
ensureProxyListening()
|
||||
lastHealthyPort = await syncProxy() || 0
|
||||
lastProcessUp = Boolean(lastHealthyPort) || await processUp().catch(() => false)
|
||||
if (lastHealthyPort) await noteQueue(lastHealthyPort)
|
||||
logStartupSweep()
|
||||
console.log(JSON.stringify({
|
||||
src: 'comfy-host-agent',
|
||||
event: 'listen',
|
||||
|
||||
Reference in New Issue
Block a user