Exemplo n.º 1
0
    async def folder_delete(self, path: FsPath) -> EntryID:
        # Check write rights
        self.check_write_rights(path)

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

            # Entry doesn't exist
            if child is None:
                raise FSFileNotFoundError(filename=path)

            # Not a directory
            if not isinstance(child, (LocalFolderManifest, LocalWorkspaceManifest)):
                raise FSNotADirectoryError(filename=path)

            # Directory not empty
            if child.children:
                raise FSDirectoryNotEmptyError(filename=path)

            # Create new manifest
            new_parent = parent.evolve_children_and_mark_updated(
                {path.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 removed folder
        return child.id
Exemplo n.º 2
0
    async def _entry_id_from_path(
            self, path: FsPath) -> Tuple[EntryID, Optional[EntryID]]:
        """Returns a tuple (entry_id, confinement_point).

        The confinement point corresponds to the entry id of the folderish manifest
        that contains a child with a confined name in the corresponding path.

        If the entry is not confined, the confinement point is `None`.
        """
        # Root entry_id and manifest
        entry_id = self.workspace_id
        confinement_point = None

        # Follow the path
        for name in path.parts:
            manifest = await self._load_manifest(entry_id)
            if not isinstance(manifest,
                              (LocalFolderManifest, LocalWorkspaceManifest)):
                raise FSNotADirectoryError(filename=path)
            try:
                entry_id = manifest.children[name]
            except (AttributeError, KeyError):
                raise FSFileNotFoundError(filename=path)
            if entry_id in manifest.local_confinement_points:
                confinement_point = manifest.id

        # Return both entry_id and confined status
        return entry_id, confinement_point
Exemplo n.º 3
0
    async def folder_delete(self, path: FsPath) -> EntryID:
        # Check write rights
        self.check_write_rights(path)

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

            # Entry doesn't exist
            if child is None:
                raise FSFileNotFoundError(filename=path)

            # Not a directory
            if not is_folderish_manifest(child):
                raise FSNotADirectoryError(filename=path)

            # Directory not empty
            if child.children:
                raise FSDirectoryNotEmptyError(filename=path)

            # Create new manifest
            new_parent = parent.evolve_children_and_mark_updated(
                {path.name: None})

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

        # Send event
        self._send_event("fs.entry.updated", id=parent.id)

        # Return the entry id of the removed folder
        return child.id
Exemplo n.º 4
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.º 5
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[str, EntryID], info["children"]):
         yield path / child
Exemplo n.º 6
0
    async def _lock_manifest_from_path(self, path: FsPath) -> LocalManifest:
        # Root entry_id and manifest
        entry_id = self.workspace_id

        # Follow the path
        for name in path.parts:
            manifest = await self._load_manifest(entry_id)
            if is_file_manifest(manifest):
                raise FSNotADirectoryError(filename=path)
            try:
                entry_id = manifest.children[name]
            except (AttributeError, KeyError):
                raise FSFileNotFoundError(filename=path)

        # Lock entry
        async with self._load_and_lock_manifest(entry_id) as manifest:
            yield manifest
Exemplo n.º 7
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.º 8
0
    async def file_conflict(
        self,
        entry_id: EntryID,
        local_manifest: Union[LocalFolderManifest, LocalFileManifest],
        remote_manifest: BaseRemoteManifest,
    ) -> None:
        # This is the only transaction that affects more than one manifests
        # That's because the local version of the file has to be registered in the
        # parent as a new child while the remote version has to be set as the actual
        # version. In practice, this should not be an issue.

        # Lock parent then child
        parent_id = local_manifest.parent
        async with self.local_storage.lock_manifest(
                parent_id) as parent_manifest:

            # Not a folderish manifest
            if not isinstance(parent_manifest,
                              (LocalFolderManifest, LocalWorkspaceManifest)):
                raise FSNotADirectoryError(parent_id)

            async with self.local_storage.lock_manifest(
                    entry_id) as current_manifest:

                # Not a file manifest
                if not isinstance(current_manifest, LocalFileManifest):
                    raise FSIsADirectoryError(entry_id)

                # Make sure the file still exists
                filename = get_filename(parent_manifest, entry_id)
                if filename is None:
                    return

                # Copy blocks
                new_blocks = []
                for chunks in current_manifest.blocks:
                    new_chunks = []
                    for chunk in chunks:
                        data = await self.local_storage.get_chunk(chunk.id)
                        new_chunk = Chunk.new(chunk.start, chunk.stop)
                        await self.local_storage.set_chunk(new_chunk.id, data)
                        if len(chunks) == 1:
                            new_chunk = new_chunk.evolve_as_block(data)
                        new_chunks.append(chunk)
                    new_blocks.append(tuple(new_chunks))

                # Prepare
                prevent_sync_pattern = self.local_storage.get_prevent_sync_pattern(
                )
                new_name = get_conflict_filename(
                    filename, list(parent_manifest.children),
                    remote_manifest.author)
                new_manifest = LocalFileManifest.new_placeholder(
                    self.local_author,
                    parent=parent_id).evolve(size=current_manifest.size,
                                             blocks=tuple(new_blocks))
                new_parent_manifest = parent_manifest.evolve_children_and_mark_updated(
                    {new_name: new_manifest.id},
                    prevent_sync_pattern=prevent_sync_pattern)
                other_manifest = BaseLocalManifest.from_remote(
                    remote_manifest, prevent_sync_pattern=prevent_sync_pattern)

                # Set manifests
                await self.local_storage.set_manifest(new_manifest.id,
                                                      new_manifest,
                                                      check_lock_status=False)
                await self.local_storage.set_manifest(parent_id,
                                                      new_parent_manifest)
                await self.local_storage.set_manifest(entry_id, other_manifest)

                self._send_event(CoreEvent.FS_ENTRY_UPDATED,
                                 id=new_manifest.id)
                self._send_event(CoreEvent.FS_ENTRY_UPDATED, id=parent_id)
                self._send_event(
                    CoreEvent.FS_ENTRY_FILE_CONFLICT_RESOLVED,
                    id=entry_id,
                    backup_id=new_manifest.id,
                )