diff --git a/swh/fuse/fs/artifact.py b/swh/fuse/fs/artifact.py index eb296f7..db05133 100644 --- a/swh/fuse/fs/artifact.py +++ b/swh/fuse/fs/artifact.py @@ -1,75 +1,93 @@ # 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 from typing import Any, AsyncIterator -from swh.fuse.fs.entry import ArtifactEntry, EntryMode +from swh.fuse.fs.entry import EntryMode, FuseEntry from swh.model.identifiers import CONTENT, DIRECTORY, SWHID # Avoid cycling import Fuse = "Fuse" -def typify(name: str, mode: int, fuse: Fuse, swhid: SWHID, prefetch: Any = None) -> Any: +class ArtifactEntry(FuseEntry): + """ FUSE virtual entry for a Software Heritage Artifact + + Attributes: + swhid: Software Heritage persistent identifier + prefetch: optional prefetched metadata used to set entry attributes + """ + + def __init__( + self, name: str, mode: int, fuse: Fuse, swhid: SWHID, prefetch: Any = None + ): + super().__init__(name, mode, fuse) + self.swhid = swhid + self.prefetch = prefetch + + +def typify( + name: str, mode: int, fuse: Fuse, swhid: SWHID, prefetch: Any = None +) -> ArtifactEntry: """ Create an artifact entry corresponding to the given artifact type """ - constructor = {CONTENT: Content, DIRECTORY: Directory} - return constructor[swhid.object_type](name, mode, fuse, swhid, prefetch) + getters = {CONTENT: Content, DIRECTORY: Directory} + return getters[swhid.object_type](name, mode, fuse, swhid, prefetch) class Content(ArtifactEntry): """ Software Heritage content artifact. Content leaves (AKA blobs) are represented on disks as regular files, containing the corresponding bytes, as archived. Note that permissions are associated to blobs only in the context of directories. Hence, when accessing blobs from the top-level `archive/` directory, the permissions of the `archive/SWHID` file will be arbitrary and not meaningful (e.g., `0x644`). """ async def content(self) -> bytes: return await self.fuse.get_blob(self.swhid) async def length(self) -> int: # When listing entries from a directory, the API already gave us information if self.prefetch: return self.prefetch["length"] return len(await self.content()) async def __aiter__(self): raise ValueError("Cannot iterate over a content type artifact") class Directory(ArtifactEntry): """ Software Heritage directory artifact. Directory nodes are represented as directories on the file-system, containing one entry for each entry of the archived directory. Entry names and other metadata, including permissions, will correspond to the archived entry metadata. Note that the FUSE mount is read-only, no matter what the permissions say. So it is possible that, in the context of a directory, a file is presented as writable, whereas actually writing to it will fail with `EPERM`. """ async def __aiter__(self) -> AsyncIterator[ArtifactEntry]: metadata = await self.fuse.get_metadata(self.swhid) for entry in metadata: yield typify( name=entry["name"], # Use default read-only permissions for directories, and # archived permissions for contents mode=( entry["perms"] if entry["target"].object_type == CONTENT else int(EntryMode.RDONLY_DIR) ), fuse=self.fuse, swhid=entry["target"], # The directory API has extra info we can use to set attributes # without additional Software Heritage API call prefetch=entry, ) diff --git a/swh/fuse/fs/entry.py b/swh/fuse/fs/entry.py index e16a7d5..bb2866a 100644 --- a/swh/fuse/fs/entry.py +++ b/swh/fuse/fs/entry.py @@ -1,67 +1,48 @@ # 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 from enum import IntEnum from stat import S_IFDIR, S_IFREG -from typing import Any - -from swh.model.identifiers import SWHID # Avoid cycling import Fuse = "Fuse" class EntryMode(IntEnum): """ Default entry mode and permissions for the FUSE. The FUSE mount is always read-only, even if permissions contradict this statement (in a context of a directory, entries are listed with permissions taken from the archive). """ RDONLY_FILE = S_IFREG | 0o444 RDONLY_DIR = S_IFDIR | 0o555 class FuseEntry: """ Main wrapper class to manipulate virtual FUSE entries Attributes: name: entry filename mode: entry permission mode fuse: internal reference to the main FUSE class inode: unique integer identifying the entry """ def __init__(self, name: str, mode: int, fuse: Fuse): self.name = name self.mode = mode self.fuse = fuse self.inode = fuse._alloc_inode(self) async def length(self) -> int: return 0 async def content(self): return None async def __aiter__(self): return None - - -class ArtifactEntry(FuseEntry): - """ FUSE virtual entry for a Software Heritage Artifact - - Attributes: - swhid: Software Heritage persistent identifier - prefetch: optional prefetched metadata used to set entry attributes - """ - - def __init__( - self, name: str, mode: int, fuse: Fuse, swhid: SWHID, prefetch: Any = None - ): - super().__init__(name, mode, fuse) - self.swhid = swhid - self.prefetch = prefetch