diff --git a/README.md b/README.md index bc2c814..fb10fdc 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,12 @@ Interactive docs: http://localhost:8100/docs | POST | `/runs` | Start a rendering run | | GET | `/runs` | List all runs | | GET | `/runs/{run_id}` | Get run status | +| DELETE | `/runs/{run_id}` | Delete a run | | GET | `/cache` | List cached content hashes | | GET | `/cache/{hash}` | Download cached content | +| DELETE | `/cache/{hash}` | Delete cached content | | POST | `/cache/import?path=` | Import local file to cache | +| POST | `/cache/upload` | Upload file to cache | | GET | `/assets` | List known assets | ### Start a run @@ -55,6 +58,24 @@ curl -X POST http://localhost:8100/runs \ curl http://localhost:8100/runs/{run_id} ``` +### Delete a run + +```bash +curl -X DELETE http://localhost:8100/runs/{run_id} \ + -H "Authorization: Bearer " +``` + +Note: Failed runs can always be deleted. Completed runs can only be deleted if their outputs haven't been published to L2. + +### Delete cached content + +```bash +curl -X DELETE http://localhost:8100/cache/{hash} \ + -H "Authorization: Bearer " +``` + +Note: Items that are inputs/outputs of runs, or published to L2, cannot be deleted. + ## Storage - **Cache**: `~/.artdag/cache/` (content-addressed files) diff --git a/cache_manager.py b/cache_manager.py index da94050..d7b425b 100644 --- a/cache_manager.py +++ b/cache_manager.py @@ -243,33 +243,46 @@ class L1CacheManager: def get_by_content_hash(self, content_hash: str) -> Optional[Path]: """Get cached file path by content_hash.""" + logger.info(f"get_by_content_hash({content_hash[:16]}...) - cache_dir={self.cache_dir}, nodes_dir={self.cache.cache_dir}") + # Check index first (new cache structure) node_id = self._content_index.get(content_hash) if node_id: + logger.info(f" Found in content_index: node_id={node_id[:16]}...") path = self.cache.get(node_id) if path and path.exists(): + logger.info(f" Found via index: {path}") return path + logger.info(f" Index entry but path not found: {path}") # For uploads, node_id == content_hash, so try direct lookup # This works even if cache index hasn't been reloaded + logger.info(f" Trying direct lookup with content_hash as node_id...") path = self.cache.get(content_hash) + logger.info(f" cache.get({content_hash[:16]}...) returned: {path}") if path and path.exists(): + logger.info(f" Found via direct lookup: {path}") self._content_index[content_hash] = content_hash self._save_content_index() return path # Scan cache entries (fallback for new structure) + logger.info(f" Trying find_by_content_hash...") entry = self.cache.find_by_content_hash(content_hash) if entry and entry.output_path.exists(): + logger.info(f" Found via scan: {entry.output_path}") self._content_index[content_hash] = entry.node_id self._save_content_index() return entry.output_path # Check legacy location (files stored directly as CACHE_DIR/{content_hash}) legacy_path = self.cache_dir / content_hash + logger.info(f" Checking legacy path: {legacy_path}, exists={legacy_path.exists()}") if legacy_path.exists() and legacy_path.is_file(): + logger.info(f" Found at legacy location: {legacy_path}") return legacy_path + logger.info(f" NOT FOUND anywhere") return None def has_content(self, content_hash: str) -> bool: diff --git a/server.py b/server.py index f1d06f6..d7191da 100644 --- a/server.py +++ b/server.py @@ -280,6 +280,42 @@ async def root(): return HOME_HTML +@app.get("/debug/cache/{content_hash}") +async def debug_cache(content_hash: str): + """Debug endpoint to check cache status for a content hash.""" + import os + + result = { + "content_hash": content_hash, + "cache_dir": str(cache_manager.cache_dir), + "nodes_dir": str(cache_manager.cache.cache_dir), + "in_content_index": content_hash in cache_manager._content_index, + "node_id_from_index": cache_manager._content_index.get(content_hash), + } + + # Check various locations + locations = { + "legacy_direct": cache_manager.cache_dir / content_hash, + "nodes_dir": cache_manager.cache.cache_dir / content_hash, + } + + for name, path in locations.items(): + result[f"{name}_path"] = str(path) + result[f"{name}_exists"] = path.exists() + if path.exists() and path.is_dir(): + result[f"{name}_contents"] = [f.name for f in path.iterdir()] + + # Check if artdag cache has it + result["artdag_cache_get"] = str(cache_manager.cache.get(content_hash)) + + # Check via cache_manager + found_path = cache_manager.get_by_content_hash(content_hash) + result["cache_manager_path"] = str(found_path) if found_path else None + result["has_content"] = cache_manager.has_content(content_hash) + + return result + + @app.post("/runs", response_model=RunStatus) async def create_run(request: RunRequest, username: str = Depends(get_required_user)): """Start a new rendering run. Requires authentication."""