Exemple #1
0
 async def copytree(
     self,
     source_path: AnyPath,
     target_path: AnyPath,
     source_workspace: Optional["WorkspaceFS"] = None,
 ) -> None:
     source_path = FsPath(source_path)
     target_path = FsPath(target_path)
     source_workspace = source_workspace or self
     source_files = await source_workspace.listdir(source_path)
     await self.mkdir(target_path)
     for source_file in source_files:
         target_file = target_path / source_file.name
         if await source_workspace.is_dir(source_file):
             await self.copytree(
                 source_path=source_file,
                 target_path=target_file,
                 source_workspace=source_workspace,
             )
         elif await source_workspace.is_file(source_file):
             await self.copyfile(
                 source_path=source_file,
                 target_path=target_file,
                 source_workspace=source_workspace,
             )
Exemple #2
0
 async def is_file(self, path: AnyPath) -> bool:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     info = await self.transactions.entry_info(FsPath(path))
     return info["type"] == "file"
Exemple #3
0
    async def move(
        self,
        source: AnyPath,
        destination: AnyPath,
        source_workspace: Optional["WorkspaceFS"] = None,
    ) -> None:
        """
        Raises:
            FSError
        """
        source = FsPath(source)

        destination = FsPath(destination)
        real_destination = destination

        # Source workspace will be either the same workspace or another one (copy paste between two different workspace)
        source_workspace = source_workspace or self

        # Testing if we are trying to paste files from the same workspace
        if source_workspace is self:
            if source.parts == destination.parts[:len(source.parts)]:
                raise FSInvalidArgumentError(
                    f"Cannot move a directory {source} into itself {destination}"
                )
            try:
                if await self.is_dir(destination):
                    real_destination = destination / source.name
                    if await self.exists(real_destination):
                        raise FileExistsError
            # At this point, real_destination is the target either representing :
            # - the destination path if it didn't already exist,
            # - a new entry with the same name as source, but inside the destination directory
            except FileNotFoundError:
                pass

            # Rename if possible
            if source.parent == real_destination.parent:
                return await self.rename(source, real_destination)

        # Copy directory
        if await source_workspace.is_dir(source):
            await self.copytree(source_path=source,
                                target_path=real_destination,
                                source_workspace=source_workspace)
            await source_workspace.rmtree(source)
            return

        # Copy file
        await self.copyfile(source_path=source,
                            target_path=real_destination,
                            source_workspace=source_workspace)
        await source_workspace.unlink(source)
Exemple #4
0
 async def rename(self,
                  source: AnyPath,
                  destination: AnyPath,
                  overwrite: bool = True) -> None:
     """
     Raises:
         FSError
     """
     source = FsPath(source)
     destination = FsPath(destination)
     await self.transactions.entry_rename(source,
                                          destination,
                                          overwrite=overwrite)
Exemple #5
0
 async def path_id(self, path: AnyPath) -> EntryID:
     """
     Raises:
         FSError
     """
     info = await self.transactions.entry_info(FsPath(path))
     return cast(EntryID, info["id"])
Exemple #6
0
 async def rmdir(self, path: AnyPath) -> None:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     await self.transactions.folder_delete(path)
Exemple #7
0
 async def unlink(self, path: AnyPath) -> None:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     await self.transactions.file_delete(path)
Exemple #8
0
 async def truncate(self, path: AnyPath, length: int) -> None:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     await self.transactions.file_resize(path, length)
Exemple #9
0
    async def _lock_parent_manifest_from_path(
        self, path: FsPath
    ) -> AsyncIterator[Tuple[LocalFolderishManifests,
                             Optional[BaseLocalManifest]]]:
        # This is the most complicated locking scenario.
        # It requires locking the parent of the given entry and the entry itself
        # if it exists.

        # This is done in a two step process:
        # - 1. Lock the parent (it must exist). While the parent is locked, no
        #   children can be added, renamed or removed.
        # - 2. Lock the children if exists. It it doesn't, there is nothing to lock
        #   since the parent lock guarentees that it is not going to be added while
        #   using the context.

        # This double locking is only required for a single use case: the overwriting
        # of empty directory during a move. We have to make sure that no one adds
        # something to the directory while it is being overwritten.
        # If read/write locks were to be implemented, the parent would be write locked
        # and the child read locked. This means that despite locking two entries, only
        # a single entry is modified at a time.

        # Source is root
        if path.is_root():
            raise FSPermissionError(filename=str(path))

        # Loop over attempts
        while True:

            # Lock parent first
            async with self._lock_manifest_from_path(path.parent) as parent:

                # Parent is not a directory
                if not isinstance(
                        parent, (LocalFolderManifest, LocalWorkspaceManifest)):
                    raise FSNotADirectoryError(filename=path.parent)

                # Child doesn't exist
                if path.name not in parent.children:
                    yield parent, None
                    return

                # Child exists
                entry_id = parent.children[path.name]
                try:
                    async with self.local_storage.lock_manifest(
                            entry_id) as manifest:
                        yield parent, manifest
                        return

                # Child is not available
                except FSLocalMissError as exc:
                    assert exc.id == entry_id

            # Release the lock and download the child manifest
            await self._load_manifest(entry_id)
Exemple #10
0
 async def exists(self, path: AnyPath) -> bool:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     try:
         await self.transactions.entry_info(path)
     except (FileNotFoundError, NotADirectoryError):
         return False
     return True
Exemple #11
0
 async def iterdir(self, path: AnyPath) -> AsyncIterator[FsPath]:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     info = await self.transactions.entry_info(path)
     if "children" not in info:
         raise FSNotADirectoryError(filename=str(path))
     for child in cast(Dict[EntryName, EntryID], info["children"]):
         yield path / child
Exemple #12
0
 async def touch(self, path: AnyPath, exist_ok: bool = True) -> None:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     try:
         await self.transactions.file_create(path, open=False)
     except FileExistsError:
         if not exist_ok:
             raise
Exemple #13
0
 def decrypt_file_link_path(
         self, addr: BackendOrganizationFileLinkAddr) -> FsPath:
     """
     Raises: ValueError
     """
     workspace_entry = self.get_workspace_entry()
     try:
         raw_path = workspace_entry.key.decrypt(addr.encrypted_path)
     except CryptoError:
         raise ValueError("Cannot decrypt path")
     # FsPath raises ValueError, decode() raises UnicodeDecodeError which is a subclass of ValueError
     return FsPath(raw_path.decode("utf-8"))
Exemple #14
0
 async def rmtree(self, path: AnyPath) -> None:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     async for child in self.iterdir(path):
         if await self.is_dir(child):
             await self.rmtree(child)
         else:
             await self.unlink(child)
     await self.rmdir(path)
Exemple #15
0
 def generate_file_link(self,
                        path: AnyPath) -> BackendOrganizationFileLinkAddr:
     """
     Raises: Nothing
     """
     workspace_entry = self.get_workspace_entry()
     encrypted_path = workspace_entry.key.encrypt(
         str(FsPath(path)).encode("utf-8"))
     return BackendOrganizationFileLinkAddr.build(
         organization_addr=self.device.organization_addr,
         workspace_id=workspace_entry.id,
         encrypted_path=encrypted_path,
     )
    async def get_path_at_timestamp(self, entry_id: EntryID, timestamp: DateTime) -> FsPath:
        """
        Find a path for an entry_id at a specific timestamp.

        If the path is broken, will raise an EntryNotFound exception. All the other exceptions are
        thrown by the ManifestCache.

        Raises:
            FSError
            FSBackendOfflineError
            FSWorkspaceInMaintenance
            FSBadEncryptionRevision
            FSWorkspaceNoAccess
            EntryNotFound
        """
        # Get first manifest
        try:
            current_id = entry_id
            current_manifest, _ = await self.load(current_id, timestamp=timestamp)
        except FSRemoteManifestNotFound:
            raise EntryNotFound(entry_id)

        # Loop over parts
        parts = []
        while not isinstance(current_manifest, WorkspaceManifest):
            # Get the manifest
            try:
                current_manifest = cast(Union[FolderManifest, FileManifest], current_manifest)
                parent_manifest, _ = await self.load(current_manifest.parent, timestamp=timestamp)
                parent_manifest = cast(Union[FolderManifest, WorkspaceManifest], parent_manifest)
            except FSRemoteManifestNotFound:
                raise EntryNotFound(entry_id)

            # Find the child name
            for name, child_id in parent_manifest.children.items():
                if child_id == current_id:
                    parts.append(name)
                    break
            else:
                raise EntryNotFound(entry_id)

            # Continue until root is found
            current_id = current_manifest.parent
            current_manifest = parent_manifest

        # Return the path
        return FsPath("/" + "/".join(reversed([part.str for part in parts])))
Exemple #17
0
 async def mkdir(self,
                 path: AnyPath,
                 parents: bool = False,
                 exist_ok: bool = False) -> None:
     """
     Raises:
         FSError
     """
     path = FsPath(path)
     if path.is_root() and exist_ok:
         return
     try:
         await self.transactions.folder_create(path)
     except FileNotFoundError:
         if not parents or path.parent == path:
             raise
         await self.mkdir(path.parent, parents=True, exist_ok=True)
         await self.mkdir(path, parents=False, exist_ok=exist_ok)
     except FileExistsError:
         if not exist_ok or not await self.is_dir(path):
             raise
Exemple #18
0
 def __init__(self,
              transactions: EntryTransactions,
              path: AnyPath,
              mode: str = "rb"):
     self._fd: Optional[FileDescriptor] = None
     self._offset = 0
     self._state = FileState.INIT
     self._path = FsPath(path)
     self._transactions = transactions
     mode = mode.lower()
     # Preventing to open in write and read in same time or write and append or open with no mode
     if sum(c in mode for c in "rwax") != 1:
         raise ValueError(
             "must have exactly one of create/read/write/append mode")
     # Preventing to open with non-existant mode
     elif re.search("[^arwxb+]", mode) is not None:
         raise ValueError(f"invalid mode: '{mode}'")
     if "b" not in mode:
         raise NotImplementedError(
             "Text mode is not supported at the moment")
     self._mode = mode
Exemple #19
0
    async def entry_rename(self,
                           source: FsPath,
                           destination: FsPath,
                           overwrite: bool = True) -> Optional[EntryID]:
        # Check write rights
        self.check_write_rights(source)

        # Source is root
        if source.is_root():
            raise FSPermissionError(filename=source)

        # Destination is root
        if destination.is_root():
            raise FSPermissionError(filename=destination)

        # Cross-directory renaming is not supported
        if source.parent != destination.parent:
            raise FSCrossDeviceError(filename=source, filename2=destination)

        # Pre-fetch the source if necessary
        if overwrite:
            await self._get_manifest_from_path(source)

        # Fetch and lock
        async with self._lock_parent_manifest_from_path(destination) as (
                parent, child):

            # Source does not exist
            if source.name not in parent.children:
                raise FSFileNotFoundError(filename=source)
            source_entry_id = parent.children[source.name]

            # Source and destination are the same
            if source.name == destination.name:
                return None

            # Destination already exists
            if not overwrite and child is not None:
                raise FSFileExistsError(filename=destination)

            # Overwrite logic
            if overwrite and child is not None:
                source_manifest = await self._get_manifest(source_entry_id)

                # Overwrite a file
                if isinstance(source_manifest, LocalFileManifest):

                    # Destination is a folder
                    if isinstance(child, LocalFolderManifest):
                        raise FSIsADirectoryError(filename=destination)

                # Overwrite a folder
                if isinstance(source_manifest, LocalFolderManifest):

                    # Destination is not a folder
                    if not isinstance(child, LocalFolderManifest):
                        raise FSNotADirectoryError(filename=destination)

                    # Destination is not empty
                    if child.children:
                        raise FSDirectoryNotEmptyError(filename=destination)

            # Create new manifest
            new_parent = parent.evolve_children_and_mark_updated(
                {
                    destination.name: source_entry_id,
                    source.name: None
                },
                prevent_sync_pattern=self.local_storage.
                get_prevent_sync_pattern(),
                timestamp=self.device.timestamp(),
            )

            # Atomic change
            await self.local_storage.set_manifest(parent.id, new_parent)

        # Send event
        self._send_event(CoreEvent.FS_ENTRY_UPDATED, id=parent.id)

        # Return the entry id of the renamed entry
        return parent.children[source.name]
Exemple #20
0
 async def get_blocks_by_type(self,
                              path: AnyPath,
                              limit: int = 1000000000) -> BlockInfo:
     path = FsPath(path)
     return await self.transactions.entry_get_blocks_by_type(path, limit)
Exemple #21
0
 async def path_info(self, path: AnyPath) -> Dict[str, object]:
     """
     Raises:
         FSError
     """
     return await self.transactions.entry_info(FsPath(path))