Exemple #1
0
async def create_folder(conn: DBAnyConn, namespace: StrOrPath,
                        path: StrOrPath) -> None:
    """
    Create a folder with any missing parents of the target path.

    Args:
        conn (DBAnyConn): Database connection.
        namespace (StrOrPath): Namespace where to create folder to.
        path (StrOrPath): Path in the namespace to create the folder.

    Raises:
        FileAlreadyExists: If folder at target path already exists.
        NotADirectory: If one of the parents is not a directory.
    """
    paths = [str(path)] + [str(p) for p in PurePath(path).parents]

    parents = await get_many(conn, namespace, paths)
    assert len(parents) > 0, f"No home folder in a namespace: '{namespace}'"

    if any(not p.is_folder() for p in parents):
        raise errors.NotADirectory()
    if parents[-1].path.lower() == str(path).lower():
        raise errors.FileAlreadyExists()

    paths_lower = [p.lower() for p in paths]
    index = paths_lower.index(parents[-1].path.lower())

    for p in reversed(paths[:index]):
        try:
            await create(conn, namespace, p, mediatype=mediatypes.FOLDER)
        except (errors.FileAlreadyExists, errors.MissingParent):
            pass
Exemple #2
0
 def makedirs(self, ns_path: StrOrPath, path: StrOrPath) -> None:
     fullpath = self._joinpath(self.location, ns_path, path)
     try:
         os.makedirs(fullpath, exist_ok=True)
     except FileExistsError as exc:
         raise errors.FileAlreadyExists() from exc
     except NotADirectoryError as exc:
         raise errors.NotADirectory() from exc
Exemple #3
0
async def list_folder(
    conn: DBAnyConn,
    namespace: StrOrPath,
    path: StrOrPath,
    *,
    with_trash: bool = False,
) -> list[File]:
    """
    Return folder contents.

    To list home folder, use '.'.

    Args:
        conn (DBAnyConn): Database connection.
        namespace (StrOrPath): Namespace where a folder located.
        path (StrOrPath): Path to a folder in this namespace.
        with_trash (bool, optional): Whether to include Trash folder. Defaults to False.

    Raises:
        FileNotFound: If folder at this path does not exists.
        NotADirectory: If path points to a file.

    Returns:
        List[File]: List of all files/folders in a folder with a target path.
    """
    path = str(path)

    parent = await get(conn, namespace, path)
    if not parent.mediatype == mediatypes.FOLDER:
        raise errors.NotADirectory()

    filter_clause = ""
    if path == ".":
        filter_clause = "AND .path != '.'"
        if not with_trash:
            filter_clause += " AND .path != 'Trash'"

    query = f"""
        SELECT
            File {{
                id, name, path, size, mtime, mediatype: {{ name }},
            }}
        FILTER
            .namespace.path = <str>$namespace
            AND
            .path LIKE <str>$path ++ '%'
            AND
            .path NOT LIKE <str>$path ++ '%/%'
            {filter_clause}
        ORDER BY
            .mediatype.name = '{mediatypes.FOLDER}' DESC
        THEN
            str_lower(.path) ASC
    """

    path = "" if path == "." else f"{path}/"
    files = await conn.query(query, namespace=str(namespace), path=path)
    return [from_db(file) for file in files]
Exemple #4
0
 def move(
     self,
     ns_path: StrOrPath,
     from_path: StrOrPath,
     to_path: StrOrPath,
 ) -> None:
     source = self._joinpath(self.location, ns_path, from_path)
     destination = self._joinpath(self.location, ns_path, to_path)
     try:
         shutil.move(source, destination)
     except FileNotFoundError as exc:
         raise errors.FileNotFound() from exc
     except NotADirectoryError as exc:
         raise errors.NotADirectory() from exc
Exemple #5
0
    def save(
        self,
        ns_path: StrOrPath,
        path: StrOrPath,
        content: IO[bytes],
    ) -> StorageFile:
        content.seek(0)
        fullpath = self._joinpath(self.location, ns_path, path)

        try:
            with open(fullpath, "wb") as buffer:
                shutil.copyfileobj(content, buffer)
        except NotADirectoryError as exc:
            raise errors.NotADirectory() from exc

        return self._from_path(ns_path, fullpath)
Exemple #6
0
    def iterdir(self, ns_path: StrOrPath,
                path: StrOrPath) -> Iterator[StorageFile]:
        dir_path = self._joinpath(self.location, ns_path, path)
        try:
            entries = os.scandir(dir_path)
        except FileNotFoundError as exc:
            raise errors.FileNotFound() from exc
        except NotADirectoryError as exc:
            raise errors.NotADirectory() from exc

        for entry in entries:
            try:
                yield self._from_entry(ns_path, entry)
            except FileNotFoundError:
                if entry.is_symlink():
                    continue
                raise  # pragma: no cover
Exemple #7
0
async def move(
    conn: DBAnyConn,
    namespace: StrOrPath,
    path: StrOrPath,
    next_path: StrOrPath,
) -> File:
    """
    Move a file or folder to a different location in the target Namespace.
    If the source path is a folder all its contents will be moved.

    Args:
        conn (DBAnyConn): Database connection.
        namespace (StrOrPath): Namespace where a file is located.
        path (StrOrPath): Path to be moved.
        next_path (StrOrPath): Path that is the destination.

    Raises:
        errors.FileAlreadyExists: If some file already at the destination path.
        errors.FileNotFound: If source or destination path does not exists.
        errors.NotADirectory: If one of the 'next_path' parents is not a folder.

    Returns:
        File: Moved file.
    """
    path = PurePath(path)
    next_path = PurePath(next_path)

    # this call also ensures path exists
    target = await get(conn, namespace, path)

    next_parent = await get(conn, namespace, next_path.parent)
    if not next_parent.is_folder():
        raise errors.NotADirectory()

    # restore original parent casing
    next_path = PurePath(next_parent.path) / next_path.name
    if str(path).lower() != str(next_path).lower():
        if await exists(conn, namespace, next_path):
            raise errors.FileAlreadyExists()

    to_decrease = set(_lowered(path.parents)).difference(
        _lowered(next_path.parents))
    to_increase = set(_lowered(next_path.parents)).difference(
        _lowered(path.parents))

    query = """
        FOR item IN {array_unpack(<array<json>>$data)}
        UNION (
            UPDATE
                File
            FILTER
                str_lower(.path) IN {array_unpack(<array<str>>item['parents'])}
                AND
                .namespace.path = <str>$namespace
            SET {
                size := .size + <int64>item['size']
            }
        )
    """

    file = await _move_file(conn, target.id, next_path)
    if target.is_folder():
        await _move_folder_content(conn, namespace, path, next_path)

    await conn.query(
        query,
        namespace=str(namespace),
        data=[
            json.dumps({
                "size": sign * target.size,
                "parents": [str(p) for p in parents]
            }) for sign, parents in zip((-1, 1), (to_decrease, to_increase))
        ])

    return file
Exemple #8
0
async def create(
    conn: DBAnyConn,
    namespace: StrOrPath,
    path: StrOrPath,
    *,
    size: int = 0,
    mtime: float = None,
    mediatype: str = mediatypes.OCTET_STREAM,
) -> File:
    """
    Create a new file.

    If the file size is greater than zero, then size of all parents updated accordingly.

    Args:
        conn (DBAnyConn): Connection to a database.
        namespace (StrOrPath): Namespace path where a file should be created.
        path (StrOrPath): Path to a file to create.
        size (int, optional): File size. Defaults to 0.
        mtime (float, optional): Time of last modification. Defaults to current time.
        mediatype (str, optional): Media type. Defaults to 'application/octet-stream'.

    Raises:
        FileAlreadyExists: If file in a target path already exists.
        MissingParent: If target path does not have a parent.
        NotADirectory: If parent path is not a directory.

    Returns:
        File: Created file.
    """
    namespace = PurePath(namespace)
    path = PurePath(path)
    mtime = mtime or time.time()

    try:
        parent = await get(conn, namespace, path.parent)
    except errors.FileNotFound as exc:
        raise errors.MissingParent() from exc
    else:
        if not parent.is_folder():
            raise errors.NotADirectory()

    query = """
        SELECT (
            INSERT File {
                name := <str>$name,
                path := <str>$path,
                size := <int64>$size,
                mtime := <float64>$mtime,
                mediatype := (
                    INSERT MediaType {
                        name := <str>$mediatype
                    }
                    UNLESS CONFLICT ON .name
                    ELSE (
                        SELECT
                            MediaType
                        FILTER
                            .name = <str>$mediatype
                    )
                ),
                namespace := (
                    SELECT
                        Namespace
                    FILTER
                        .path = <str>$namespace
                    LIMIT 1
                ),
            }
        ) { id, name, path, size, mtime, mediatype: { name } }
    """

    params = {
        "name": (namespace / path).name,
        "path": normpath(joinpath(parent.path, path.name)) if parent else ".",
        "size": size,
        "mtime": mtime,
        "mediatype": mediatype,
        "namespace": str(namespace),
    }

    try:
        file = await conn.query_required_single(query, **params)
    except edgedb.ConstraintViolationError as exc:
        raise errors.FileAlreadyExists() from exc

    if size:
        await inc_size_batch(conn, namespace, path.parents, size)

    return from_db(file)