Exemplo n.º 1
0
    def _delete(self, path: FsPath, expect=None) -> None:
        if path.is_root():
            raise PermissionError(13, "Permission denied", str(path))
        parent_access, parent_manifest = self._retrieve_entry(path.parent)
        if not is_folderish_manifest(parent_manifest):
            raise NotADirectoryError(20, "Not a directory", str(path.parent))

        try:
            item_access = parent_manifest.children[path.name]
        except KeyError:
            raise FileNotFoundError(2, "No such file or directory", str(path))

        item_manifest = self.get_manifest(item_access)
        if is_folderish_manifest(item_manifest):
            if expect == "file":
                raise IsADirectoryError(21, "Is a directory", str(path))
            if item_manifest.children:
                raise OSError(39, "Directory not empty", str(path))
        elif expect == "folder":
            raise NotADirectoryError(20, "Not a directory", str(path))

        parent_manifest = parent_manifest.evolve_children_and_mark_updated(
            {path.name: None})
        self.set_manifest(parent_access, parent_manifest)
        self.event_bus.send("fs.entry.updated", id=parent_access.id)
Exemplo n.º 2
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)
Exemplo n.º 3
0
    async def _resolve_placeholders_in_path(self, path: FsPath, access: Access,
                                            manifest: LocalManifest) -> bool:
        """
        Returns: If an additional sync is needed
        """
        # Notes we sync recursively from children to parents, this is more
        # efficient given otherwise we would have to do:
        # 1) sync the parent
        # 2) sync the child
        # 3) re-sync the parent with the child

        is_placeholder = is_placeholder_manifest(manifest)
        if not is_placeholder:
            # Cannot have a non-placeholder with a placeholder parent, hence
            # we don't have to go any further.
            return manifest.need_sync

        else:
            if is_file_manifest(manifest):
                need_more_sync = await self._minimal_sync_file(
                    path, access, manifest)
            else:
                need_more_sync = await self._minimal_sync_folder(
                    path, access, manifest)

            # Once the entry is synced, we must sync it parent as well to have
            # the entry visible for other clients

            if not path.is_root():
                try:
                    parent_access, parent_manifest = self.local_folder_fs.get_entry(
                        path.parent)
                except FSManifestLocalMiss:
                    # Nothing to do if entry is no present locally
                    return False

                if is_placeholder_manifest(parent_manifest):
                    await self._resolve_placeholders_in_path(
                        path.parent, parent_access, parent_manifest)
                else:
                    await self._sync_folder(path.parent,
                                            parent_access,
                                            parent_manifest,
                                            recursive=False)

            if not need_more_sync:
                self.event_bus.send("fs.entry.synced",
                                    path=str(path),
                                    id=access.id)

            return need_more_sync
Exemplo n.º 4
0
    def stat(self, path: FsPath) -> dict:
        access, manifest = self._retrieve_entry_read_only(path)
        if is_file_manifest(manifest):
            return {
                "type": "file",
                "is_folder": False,
                "created": manifest.created,
                "updated": manifest.updated,
                "base_version": manifest.base_version,
                "is_placeholder": manifest.is_placeholder,
                "need_sync": manifest.need_sync,
                "size": manifest.size,
            }

        elif is_workspace_manifest(manifest):
            return {
                "type": "workspace",
                "is_folder": True,
                "created": manifest.created,
                "updated": manifest.updated,
                "base_version": manifest.base_version,
                "is_placeholder": manifest.is_placeholder,
                "need_sync": manifest.need_sync,
                "children": list(sorted(manifest.children.keys())),
                "creator": manifest.creator,
                "participants": list(manifest.participants),
            }
        else:
            return {
                "type": "root" if path.is_root() else "folder",
                "is_folder": True,
                "created": manifest.created,
                "updated": manifest.updated,
                "base_version": manifest.base_version,
                "is_placeholder": manifest.is_placeholder,
                "need_sync": manifest.need_sync,
                "children": list(sorted(manifest.children.keys())),
            }
Exemplo n.º 5
0
    def touch(self, path: FsPath) -> None:
        if path.is_root():
            raise FileExistsError(17, "File exists", str(path))

        if path.parent.is_root():
            raise PermissionError(
                13, "Permission denied (only workpace allowed at root level)",
                str(path))

        access, manifest = self._retrieve_entry(path.parent)
        if not is_folderish_manifest(manifest):
            raise NotADirectoryError(20, "Not a directory", str(path.parent))
        if path.name in manifest.children:
            raise FileExistsError(17, "File exists", str(path))

        child_access = ManifestAccess()
        child_manifest = LocalFileManifest(self.local_author)
        manifest = manifest.evolve_children_and_mark_updated(
            {path.name: child_access})
        self.set_manifest(access, manifest)
        self.set_manifest(child_access, child_manifest)
        self.event_bus.send("fs.entry.updated", id=access.id)
        self.event_bus.send("fs.entry.updated", id=child_access.id)
Exemplo n.º 6
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(),
            )

            # 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]
Exemplo n.º 7
0
def test_root(path, is_root):
    obj = FsPath(path)
    assert obj.is_root() is is_root
    assert "//" not in str(obj)
Exemplo n.º 8
0
    def _copy(self, src: FsPath, dst: FsPath, delete_src: bool) -> None:
        # The idea here is to consider a manifest never move around the fs
        # (i.e. a given access always points to the same path). This simplify
        # sync notifications handling and avoid ending up with two path
        # (possibly from different workspaces !) pointing to the same manifest
        # which would be weird for user experience.
        # Long story short, when moving/copying manifest, we must recursively
        # copy the manifests and create new accesses for them.

        parent_src = src.parent
        parent_dst = dst.parent

        # No matter what, cannot move or overwrite root
        if src.is_root():
            # Raise FileNotFoundError if parent_dst doesn't exists
            _, parent_dst_manifest = self._retrieve_entry(parent_dst)
            if not is_folderish_manifest(parent_dst_manifest):
                raise NotADirectoryError(20, "Not a directory",
                                         str(parent_dst))
            else:
                raise PermissionError(13, "Permission denied", str(src),
                                      str(dst))
        elif dst.is_root():
            # Raise FileNotFoundError if parent_src doesn't exists
            _, parent_src_manifest = self._retrieve_entry(src.parent)
            if not is_folderish_manifest(parent_src_manifest):
                raise NotADirectoryError(20, "Not a directory",
                                         str(src.parent))
            else:
                raise PermissionError(13, "Permission denied", str(src),
                                      str(dst))

        if src == dst:
            # Raise FileNotFoundError if doesn't exist
            src_access, src_ro_manifest = self._retrieve_entry_read_only(src)
            if is_workspace_manifest(src_ro_manifest):
                raise PermissionError(
                    13,
                    "Permission denied (cannot move/copy workpace, must rename it)",
                    str(src),
                    str(dst),
                )
            return

        if parent_src == parent_dst:
            parent_access, parent_manifest = self._retrieve_entry(parent_src)
            if not is_folderish_manifest(parent_manifest):
                raise NotADirectoryError(20, "Not a directory",
                                         str(parent_src))

            try:
                dst.relative_to(src)
            except ValueError:
                pass
            else:
                raise OSError(22, "Invalid argument", str(src), None, str(dst))

            try:
                src_access = parent_manifest.children[src.name]
            except KeyError:
                raise FileNotFoundError(2, "No such file or directory",
                                        str(src))

            existing_dst_access = parent_manifest.children.get(dst.name)
            src_manifest = self.get_manifest(src_access)

            if is_workspace_manifest(src_manifest):
                raise PermissionError(
                    13,
                    "Permission denied (cannot move/copy workpace, must rename it)",
                    str(src),
                    str(dst),
                )

            if existing_dst_access:
                existing_dst_manifest = self.get_manifest(existing_dst_access)
                if is_folderish_manifest(src_manifest):
                    if is_file_manifest(existing_dst_manifest):
                        raise NotADirectoryError(20, "Not a directory",
                                                 str(dst))
                    elif existing_dst_manifest.children:
                        raise OSError(39, "Directory not empty", str(dst))
                else:
                    if is_folderish_manifest(existing_dst_manifest):
                        raise IsADirectoryError(21, "Is a directory", str(dst))

            moved_access = self._recursive_manifest_copy(
                src_access, src_manifest)

            if not delete_src:
                parent_manifest = parent_manifest.evolve_children(
                    {dst.name: moved_access})
            else:
                parent_manifest = parent_manifest.evolve_children({
                    dst.name:
                    moved_access,
                    src.name:
                    None
                })
            self.set_manifest(parent_access, parent_manifest)
            self.event_bus.send("fs.entry.updated", id=parent_access.id)

        else:
            parent_src_access, parent_src_manifest = self._retrieve_entry(
                parent_src)
            if not is_folderish_manifest(parent_src_manifest):
                raise NotADirectoryError(20, "Not a directory",
                                         str(parent_src))

            parent_dst_access, parent_dst_manifest = self._retrieve_entry(
                parent_dst)
            if not is_folderish_manifest(parent_dst_manifest):
                raise NotADirectoryError(20, "Not a directory",
                                         str(parent_dst))

            try:
                dst.relative_to(src)
            except ValueError:
                pass
            else:
                raise OSError(22, "Invalid argument", str(src), None, str(dst))

            try:
                src_access = parent_src_manifest.children[src.name]
            except KeyError:
                raise FileNotFoundError(2, "No such file or directory",
                                        str(src))

            existing_dst_access = parent_dst_manifest.children.get(dst.name)
            src_manifest = self.get_manifest(src_access)

            if is_workspace_manifest(src_manifest):
                raise PermissionError(
                    13,
                    "Permission denied (cannot move/copy workpace, must rename it)",
                    str(src),
                    str(dst),
                )

            if existing_dst_access:
                existing_entry_manifest = self.get_manifest(
                    existing_dst_access)
                if is_folderish_manifest(src_manifest):
                    if is_file_manifest(existing_entry_manifest):
                        raise NotADirectoryError(20, "Not a directory",
                                                 str(dst))
                    elif existing_entry_manifest.children:
                        raise OSError(39, "Directory not empty", str(dst))
                else:
                    if is_folderish_manifest(existing_entry_manifest):
                        raise IsADirectoryError(21, "Is a directory", str(dst))

            moved_access = self._recursive_manifest_copy(
                src_access, src_manifest)

            parent_dst_manifest = parent_dst_manifest.evolve_children_and_mark_updated(
                {dst.name: moved_access})
            self.set_manifest(parent_dst_access, parent_dst_manifest)
            self.event_bus.send("fs.entry.updated", id=parent_dst_access.id)

            if delete_src:
                parent_src_manifest = parent_src_manifest.evolve_children_and_mark_updated(
                    {src.name: None})
                self.set_manifest(parent_src_access, parent_src_manifest)
                self.event_bus.send("fs.entry.updated",
                                    id=parent_src_access.id)