Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F8394574
D4795.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
5 KB
Subscribers
None
D4795.diff
View Options
diff --git a/swh/fuse/cache.py b/swh/fuse/cache.py
--- a/swh/fuse/cache.py
+++ b/swh/fuse/cache.py
@@ -187,6 +187,12 @@
)
await self.conn.commit()
+ async def remove(self, swhid: SWHID) -> None:
+ await self.conn.execute(
+ "delete from metadata_cache where swhid=?", (str(swhid),),
+ )
+ await self.conn.commit()
+
class BlobCache(AbstractCache):
""" The blob cache map SWHIDs of type `cnt` to the bytes of their archived
@@ -227,6 +233,12 @@
)
await self.conn.commit()
+ async def remove(self, swhid: SWHID) -> None:
+ await self.conn.execute(
+ "delete from blob_cache where swhid=?", (str(swhid),),
+ )
+ await self.conn.commit()
+
class HistoryCache(AbstractCache):
""" The history cache map SWHIDs of type `rev` to a list of `rev` SWHIDs
@@ -374,7 +386,7 @@
return self.lru_cache.get(direntry.inode, None)
def set(self, direntry: FuseDirEntry, entries: List[FuseEntry]) -> None:
- if isinstance(direntry, (CacheDir, OriginDir)):
+ if isinstance(direntry, (CacheDir, CacheDir.ArtifactShardBySwhid, OriginDir)):
# The `cache/` and `origin/` directories are populated on the fly
pass
elif (
diff --git a/swh/fuse/fs/entry.py b/swh/fuse/fs/entry.py
--- a/swh/fuse/fs/entry.py
+++ b/swh/fuse/fs/entry.py
@@ -26,7 +26,11 @@
RDONLY_FILE = S_IFREG | 0o444
RDONLY_DIR = S_IFDIR | 0o555
- SYMLINK = S_IFLNK | 0o444
+ RDONLY_LNK = S_IFLNK | 0o444
+
+ # `cache/` sub-directories need the write permission in order to invalidate
+ # cached artifacts using `rm {SWHID}`
+ RDWR_DIR = S_IFDIR | 0o755
@dataclass
@@ -57,6 +61,9 @@
raise NotImplementedError
+ async def unlink(self, name: str) -> None:
+ raise NotImplementedError
+
def get_relative_root_path(self) -> str:
return "../" * (self.depth - 1)
@@ -132,7 +139,7 @@
target: path to symlink target
"""
- mode: int = field(init=False, default=int(EntryMode.SYMLINK))
+ mode: int = field(init=False, default=int(EntryMode.RDONLY_LNK))
target: Union[str, bytes, Path]
async def size(self) -> int:
diff --git a/swh/fuse/fs/mountpoint.py b/swh/fuse/fs/mountpoint.py
--- a/swh/fuse/fs/mountpoint.py
+++ b/swh/fuse/fs/mountpoint.py
@@ -176,6 +176,16 @@
target=Path(root_path, f"archive/{swhid}{JSON_SUFFIX}"),
)
+ async def unlink(self, name: str) -> None:
+ try:
+ if name.endswith(JSON_SUFFIX):
+ name = name[: -len(JSON_SUFFIX)]
+ swhid = parse_swhid(name)
+ await self.fuse.cache.metadata.remove(swhid)
+ await self.fuse.cache.blob.remove(swhid)
+ except ValidationError:
+ raise
+
async def compute_entries(self) -> AsyncIterator[FuseEntry]:
prefixes = set()
async for swhid in self.fuse.cache.get_cached_swhids():
@@ -185,7 +195,7 @@
yield self.create_child(
CacheDir.ArtifactShardBySwhid,
name=prefix,
- mode=int(EntryMode.RDONLY_DIR),
+ mode=int(EntryMode.RDWR_DIR),
prefix=prefix,
)
diff --git a/swh/fuse/fuse.py b/swh/fuse/fuse.py
--- a/swh/fuse/fuse.py
+++ b/swh/fuse/fuse.py
@@ -309,6 +309,26 @@
assert isinstance(entry, FuseSymlinkEntry)
return os.fsencode(entry.get_target())
+ async def unlink(
+ self, parent_inode: int, name: str, _ctx: pyfuse3.RequestContext
+ ) -> None:
+ """ Remove a file """
+
+ name = os.fsdecode(name)
+ parent_entry = self.inode2entry(parent_inode)
+ self.logger.debug(
+ "unlink(parent_name=%s, parent_inode=%d, name=%s)",
+ parent_entry.name,
+ parent_inode,
+ name,
+ )
+
+ try:
+ await parent_entry.unlink(name)
+ except Exception as err:
+ self.logger.exception("Cannot unlink: %s", err)
+ raise pyfuse3.FUSEError(errno.ENOENT)
+
async def main(swhids: List[SWHID], root_path: Path, conf: Dict[str, Any]) -> None:
""" swh-fuse CLI entry-point """
diff --git a/swh/fuse/tests/test_cache.py b/swh/fuse/tests/test_cache.py
new file mode 100644
--- /dev/null
+++ b/swh/fuse/tests/test_cache.py
@@ -0,0 +1,33 @@
+# Copyright (C) 2020 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+import os
+
+from swh.fuse.tests.data.config import REGULAR_FILE
+from swh.model.identifiers import parse_swhid
+
+
+def test_cache_artifact(fuse_mntdir):
+ assert os.listdir(fuse_mntdir / "cache") == ["origin"]
+
+ (fuse_mntdir / "archive" / REGULAR_FILE).is_file()
+
+ swhid = parse_swhid(REGULAR_FILE)
+ assert os.listdir(fuse_mntdir / "cache") == [swhid.object_id[:2], "origin"]
+
+
+def test_purge_artifact(fuse_mntdir):
+ DEFAULT_CACHE_CONTENT = ["origin"]
+
+ assert os.listdir(fuse_mntdir / "cache") == DEFAULT_CACHE_CONTENT
+
+ # Access a content artifact...
+ (fuse_mntdir / "archive" / REGULAR_FILE).is_file()
+ assert os.listdir(fuse_mntdir / "cache") != DEFAULT_CACHE_CONTENT
+ # ... and remove it from cache
+ swhid = parse_swhid(REGULAR_FILE)
+ os.unlink(fuse_mntdir / "cache" / swhid.object_id[:2] / str(swhid))
+
+ assert os.listdir(fuse_mntdir / "cache") == DEFAULT_CACHE_CONTENT
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Jun 3, 7:26 PM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3216144
Attached To
D4795: FUSE: cache: add support to remove individual objects
Event Timeline
Log In to Comment