Implement ownership model for all cached content deletion
- cache_service.delete_content: Remove user's ownership link first, only delete actual file if no other owners remain - cache_manager.discard_activity_outputs_only: Check if outputs and intermediates are used by other activities before deleting - run_service.discard_run: Now cleans up run outputs/intermediates (only if not shared by other runs) - home.py clear_user_data: Use ownership model for effects and media deletion instead of directly deleting files The ownership model ensures: 1. Multiple users can "own" the same cached content 2. Deleting removes the user's ownership link (item_types entry) 3. Actual files only deleted when no owners remain (garbage collection) 4. Shared intermediates between runs are preserved Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -690,11 +690,26 @@ class L1CacheManager:
|
||||
return True, "Activity discarded"
|
||||
return False, "Failed to discard"
|
||||
|
||||
def _is_used_by_other_activities(self, node_id: str, exclude_activity_id: str) -> bool:
|
||||
"""Check if a node is used by any activity other than the excluded one."""
|
||||
for other_activity in self.activity_store.list():
|
||||
if other_activity.activity_id == exclude_activity_id:
|
||||
continue
|
||||
# Check if used as input, output, or intermediate
|
||||
if node_id in other_activity.input_ids:
|
||||
return True
|
||||
if node_id == other_activity.output_id:
|
||||
return True
|
||||
if node_id in other_activity.intermediate_ids:
|
||||
return True
|
||||
return False
|
||||
|
||||
def discard_activity_outputs_only(self, activity_id: str) -> tuple[bool, str]:
|
||||
"""
|
||||
Discard an activity, deleting only outputs and intermediates.
|
||||
|
||||
Inputs (cache items, configs) are preserved.
|
||||
Outputs/intermediates used by other activities are preserved.
|
||||
|
||||
Returns:
|
||||
(success, message) tuple
|
||||
@@ -711,21 +726,31 @@ class L1CacheManager:
|
||||
if pinned:
|
||||
return False, f"Output is pinned ({reason})"
|
||||
|
||||
# Delete output
|
||||
if activity.output_id:
|
||||
entry = self.cache.get_entry(activity.output_id)
|
||||
if entry:
|
||||
# Remove from cache
|
||||
self.cache.remove(activity.output_id)
|
||||
# Remove from content index (Redis + local)
|
||||
self._del_content_index(entry.cid)
|
||||
# Delete from legacy dir if exists
|
||||
legacy_path = self.legacy_dir / entry.cid
|
||||
if legacy_path.exists():
|
||||
legacy_path.unlink()
|
||||
deleted_outputs = 0
|
||||
preserved_shared = 0
|
||||
|
||||
# Delete intermediates
|
||||
# Delete output (only if not used by other activities)
|
||||
if activity.output_id:
|
||||
if self._is_used_by_other_activities(activity.output_id, activity_id):
|
||||
preserved_shared += 1
|
||||
else:
|
||||
entry = self.cache.get_entry(activity.output_id)
|
||||
if entry:
|
||||
# Remove from cache
|
||||
self.cache.remove(activity.output_id)
|
||||
# Remove from content index (Redis + local)
|
||||
self._del_content_index(entry.cid)
|
||||
# Delete from legacy dir if exists
|
||||
legacy_path = self.legacy_dir / entry.cid
|
||||
if legacy_path.exists():
|
||||
legacy_path.unlink()
|
||||
deleted_outputs += 1
|
||||
|
||||
# Delete intermediates (only if not used by other activities)
|
||||
for node_id in activity.intermediate_ids:
|
||||
if self._is_used_by_other_activities(node_id, activity_id):
|
||||
preserved_shared += 1
|
||||
continue
|
||||
entry = self.cache.get_entry(node_id)
|
||||
if entry:
|
||||
self.cache.remove(node_id)
|
||||
@@ -733,11 +758,16 @@ class L1CacheManager:
|
||||
legacy_path = self.legacy_dir / entry.cid
|
||||
if legacy_path.exists():
|
||||
legacy_path.unlink()
|
||||
deleted_outputs += 1
|
||||
|
||||
# Remove activity record (inputs remain in cache)
|
||||
self.activity_store.remove(activity_id)
|
||||
|
||||
return True, "Activity discarded (outputs only)"
|
||||
msg = f"Activity discarded (deleted {deleted_outputs} outputs"
|
||||
if preserved_shared > 0:
|
||||
msg += f", preserved {preserved_shared} shared items"
|
||||
msg += ")"
|
||||
return True, msg
|
||||
|
||||
def cleanup_intermediates(self) -> int:
|
||||
"""Delete all intermediate cache entries (reconstructible)."""
|
||||
|
||||
Reference in New Issue
Block a user