fix(product-suite): allow completed source image removal
This commit is contained in:
+67
-10
@@ -871,11 +871,68 @@ def remove_asset_if_unused(asset_id, path=None, conn=None):
|
||||
return asset
|
||||
|
||||
|
||||
def remove_original_assets_if_unused(project_id, asset_ids, path=None, conn=None):
|
||||
"""Atomically remove unreferenced original assets from one project.
|
||||
def _job_requires_original_assets(status, recovery_action):
|
||||
"""Return whether a job can still need its frozen source/reference assets."""
|
||||
|
||||
if status in {"pending", "submitted", "running"}:
|
||||
return True
|
||||
return recovery_action in {JOB_RECOVERY_RESUME, JOB_RECOVERY_REGENERATE}
|
||||
|
||||
|
||||
def _original_asset_delete_blocker(database, project_id, asset_ids):
|
||||
"""Return a user-facing blocker for deleting project original assets, if any."""
|
||||
|
||||
selected_ids = {int(asset_id) for asset_id in asset_ids}
|
||||
placeholders = ",".join("?" for _ in selected_ids)
|
||||
selection = database.execute(
|
||||
f"""
|
||||
SELECT 1 FROM image_studio_selections
|
||||
WHERE project_id = ? AND asset_id IN ({placeholders})
|
||||
LIMIT 1
|
||||
""",
|
||||
[int(project_id), *selected_ids],
|
||||
).fetchone()
|
||||
if selection is not None:
|
||||
return "选中的图片已被终选引用,请先从终选中移除后再删除"
|
||||
|
||||
jobs = database.execute(
|
||||
"""
|
||||
SELECT id, status, recovery_action, source_asset_id, reference_asset_ids
|
||||
FROM image_studio_jobs
|
||||
WHERE project_id = ?
|
||||
ORDER BY id
|
||||
""",
|
||||
(int(project_id),),
|
||||
).fetchall()
|
||||
for job in jobs:
|
||||
status = str(job["status"] or "")
|
||||
recovery_action = str(job["recovery_action"] or "")
|
||||
if status not in JOB_STATUSES or recovery_action not in JOB_RECOVERY_ACTIONS:
|
||||
return "生成任务状态无效,无法确认图片引用,不能删除"
|
||||
try:
|
||||
reference_ids = _parse_reference_asset_ids(
|
||||
job["reference_asset_ids"],
|
||||
job["source_asset_id"],
|
||||
)
|
||||
except db.DbError:
|
||||
return "生成任务参考图快照无效,无法确认图片引用,不能删除"
|
||||
if not _job_requires_original_assets(status, recovery_action):
|
||||
continue
|
||||
source_asset_id = job["source_asset_id"]
|
||||
source_selected = (
|
||||
source_asset_id is not None and int(source_asset_id) in selected_ids
|
||||
)
|
||||
if source_selected or selected_ids.intersection(reference_ids):
|
||||
return "选中的图片仍被可继续处理的生成任务引用,请先完成、停止或放弃该任务后再删除"
|
||||
return None
|
||||
|
||||
|
||||
def remove_original_assets_if_unused(project_id, asset_ids, path=None, conn=None):
|
||||
"""Atomically remove original assets that no runnable job or selection needs.
|
||||
|
||||
Completed, non-retryable jobs remain as history but do not block removal.
|
||||
Local files are intentionally retained. If any requested asset is invalid or
|
||||
referenced, no asset row is removed.
|
||||
still needed, no asset row is removed.
|
||||
"""
|
||||
|
||||
project_id = int(project_id)
|
||||
@@ -906,13 +963,13 @@ def remove_original_assets_if_unused(project_id, asset_ids, path=None, conn=None
|
||||
if set(by_id) != set(ordered_ids):
|
||||
raise db.DbError("选中的商品原图不存在或不属于当前项目")
|
||||
|
||||
referenced_ids = [
|
||||
asset_id
|
||||
for asset_id in ordered_ids
|
||||
if asset_reference_counts(asset_id, conn=database)["total"]
|
||||
]
|
||||
if referenced_ids:
|
||||
raise db.DbError("选中的图片正在被生成任务或终选引用,不能移除")
|
||||
blocker = _original_asset_delete_blocker(
|
||||
database,
|
||||
project_id,
|
||||
ordered_ids,
|
||||
)
|
||||
if blocker:
|
||||
raise db.DbError(blocker)
|
||||
|
||||
database.execute(
|
||||
f"""
|
||||
|
||||
Reference in New Issue
Block a user