예제 #1
0
async def get_by_id(conn: DBAnyConn, file_id: StrOrUUID) -> File:
    """
    Return a file by ID.

    Args:
        conn (DBAnyConn): Database connection.
        file_id (StrOrUUID): File ID.

    Raises:
        errors.FileNotFound: If file with a given ID does not exists.

    Returns:
        File: File with a target ID.
    """
    query = """
        SELECT
            File {
                id, name, path, size, mtime, mediatype: { name }
            }
        FILTER
            .id = <uuid>$file_id
    """
    try:
        return from_db(await conn.query_required_single(query,
                                                        file_id=file_id))
    except edgedb.NoDataError as exc:
        raise errors.FileNotFound() from exc
예제 #2
0
async def get(conn: DBAnyConn, namespace: StrOrPath, path: StrOrPath) -> File:
    """
    Return file with a target path.

    Args:
        conn (DBAnyConn): Database connection.
        namespace (StrOrPath): Namespace where to look for a file.
        path (StrOrPath): Path to a file.

    Raises:
        FileNotFound: If file with a target path does not exists.

    Returns:
        File: File with a target path.
    """
    query = """
        SELECT
            File {
                id, name, path, size, mtime, mediatype: { name }
            }
        FILTER
            str_lower(.path) = str_lower(<str>$path)
            AND
            .namespace.path = <str>$namespace
    """
    try:
        return from_db(await
                       conn.query_required_single(query,
                                                  namespace=str(namespace),
                                                  path=str(path)))
    except edgedb.NoDataError as exc:
        raise errors.FileNotFound() from exc
예제 #3
0
 def delete(self, ns_path: StrOrPath, path: StrOrPath) -> None:
     fullpath = self._joinpath(self.location, ns_path, path)
     try:
         if os.path.isdir(fullpath):
             shutil.rmtree(fullpath)
         else:
             os.unlink(fullpath)
     except FileNotFoundError as exc:
         raise errors.FileNotFound() from exc
예제 #4
0
async def move(
    db_client: DBClient,
    namespace: Namespace,
    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:
        db_client (DBClient): Database client.
        namespace (Namespace): Namespace, where file/folder should be moved.
        path (StrOrPath): Path to be moved.
        next_path (StrOrPath): Path that is the destination.

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

    Returns:
        File: Moved file/folder.
    """
    path = str(path)
    next_path = str(next_path)

    assert path.lower() not in (".", config.TRASH_FOLDER_NAME.lower()), (
        "Can't move Home or Trash folder.")
    assert not next_path.lower().startswith(f"{path.lower()}/"), (
        "Can't move to itself.")

    if not await crud.file.exists(db_client, namespace.path, path):
        raise errors.FileNotFound() from None

    next_parent = os.path.normpath(os.path.dirname(next_path))
    if not await crud.file.exists(db_client, namespace.path, next_parent):
        raise errors.MissingParent() from None

    if path.lower() != next_path.lower():
        if await crud.file.exists(db_client, namespace.path, next_path):
            raise errors.FileAlreadyExists() from None

    await storage.move(namespace.path, path, next_path)

    async for tx in db_client.transaction():  # pragma: no branch
        async with tx:
            file = await crud.file.move(tx, namespace.path, path, next_path)
    return file
예제 #5
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
예제 #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
예제 #7
0
async def delete(conn: DBAnyConn, namespace: StrOrPath,
                 path: StrOrPath) -> File:
    """
    Permanently delete file or a folder with all of its contents and decrease size
    of the parents accordingly.

    Args:
        conn (DBAnyConn): Database connection.
        namespace (StrOrPath): Namespace where to delete a file.
        path (StrOrPath): Path to a file.

    Raises:
        FileNotFound: If file/folder with a given path does not exists.

    Returns:
        File: Deleted file.
    """

    query = """
        SELECT (
            DELETE
                File
            FILTER
                .namespace.path = <str>$namespace
                AND (
                    str_lower(.path) = str_lower(<str>$path)
                    OR
                    str_lower(.path) LIKE str_lower(<str>$path) ++ '/%'
                )
        ) { id, name, path, size, mtime, mediatype: { name } }
    """

    try:
        file = from_db((await conn.query(query,
                                         namespace=str(namespace),
                                         path=str(path)))[0])
    except IndexError as exc:
        raise errors.FileNotFound() from exc

    await inc_size_batch(conn,
                         namespace,
                         PurePath(path).parents,
                         size=-file.size)

    return file
예제 #8
0
async def create(conn: DBAnyConn, file_id: StrOrUUID, fp: int) -> None:
    """
    Save file fingerprint to the database.

    The fingerprint is stored as four 16-bit parts of original fingerprint.

    Args:
        conn (DBAnyConn): Database connection.
        file_id (StrOrUUID): File to associate fingerprint with.
        fp (int): A 64-bit fingerprint.

    Raises:
        errors.FingerprintAlreadyExists: If there is already a fingerprint for a file.
        errors.FileNotFound: If a file with specified file ID doesn't exist.
    """

    query = """
        INSERT Fingerprint {
            part1 := <int32>$part1,
            part2 := <int32>$part2,
            part3 := <int32>$part3,
            part4 := <int32>$part4,
            file := (
                SELECT File
                FILTER .id = <uuid>$file_id
                LIMIT 1
            )
        }
    """

    parts = _split_int8_by_int2(fp)

    try:
        await conn.query_required_single(
            query,
            file_id=file_id,
            part1=parts[0],
            part2=parts[1],
            part3=parts[2],
            part4=parts[3],
        )
    except edgedb.ConstraintViolationError as exc:
        raise errors.FingerprintAlreadyExists() from exc
    except edgedb.MissingRequiredError as exc:
        raise errors.FileNotFound() from exc
예제 #9
0
    def thumbnail(
        self,
        ns_path: StrOrPath,
        path: StrOrPath,
        size: int,
    ) -> tuple[int, IO[bytes]]:
        fullpath = self._joinpath(self.location, ns_path, path)
        buffer = BytesIO()
        try:
            with Image.open(fullpath) as im:
                im.thumbnail((size, size))
                exif_transpose(im).save(buffer, im.format)
        except FileNotFoundError as exc:
            raise errors.FileNotFound() from exc
        except IsADirectoryError as exc:
            raise errors.IsADirectory(f"Path '{path}' is a directory") from exc
        except UnidentifiedImageError as exc:
            msg = f"Can't generate thumbnail for a file: '{path}'"
            raise errors.ThumbnailUnavailable(msg) from exc

        size = buffer.seek(0, 2)
        buffer.seek(0)

        return size, buffer
예제 #10
0
 def size(self, ns_path: StrOrPath, path: StrOrPath) -> int:
     fullpath = self._joinpath(self.location, ns_path, path)
     try:
         return os.lstat(fullpath).st_size
     except FileNotFoundError as exc:
         raise errors.FileNotFound() from exc
예제 #11
0
 def get_modified_time(self, ns_path: StrOrPath, path: StrOrPath) -> float:
     fullpath = self._joinpath(self.location, ns_path, path)
     try:
         return os.lstat(fullpath).st_mtime
     except FileNotFoundError as exc:
         raise errors.FileNotFound() from exc
예제 #12
0
async def create_batch(
    conn: DBAnyConn,
    namespace: StrOrPath,
    fingerprints: Iterable[tuple[StrOrPath, int] | None],
) -> None:
    """
    Create fingerprints for multiple files in the same namespace at once.

    Args:
        conn (DBAnyConn): Database connection.
        namespace (StrOrPath): Files namespace.
        fingerprints (Iterable[tuple[StrOrPath, int]): Tuple, where the first element
            is a file path, and the second one is a fingerprint.

    Raises:
        errors.FingerprintAlreadyExists: If fingerprints for a file already exists.
        errors.FileNotFound: If file not found in a given namespace.
    """

    query = """
        WITH
            fingerprints := array_unpack(<array<json>>$fingerprints),
            namespace := (
                SELECT
                    Namespace
                FILTER
                    .path = <str>$ns_path
                LIMIT 1
            ),
        FOR fp in {fingerprints}
        UNION (
            INSERT Fingerprint {
                part1 := <int32>fp['part1'],
                part2 := <int32>fp['part2'],
                part3 := <int32>fp['part3'],
                part4 := <int32>fp['part4'],
                file := (
                    SELECT
                        File
                    FILTER
                        .namespace = namespace
                        AND
                        .path = <str>fp['path']
                    LIMIT 1
                )
            }
        )
    """

    data = []
    fingerprints = (fp for fp in fingerprints if fp is not None)
    for path, fingerprint in fingerprints:  # type: ignore
        parts = _split_int8_by_int2(fingerprint)
        data.append(
            orjson.dumps({
                "path": str(path),
                "part1": parts[0],
                "part2": parts[1],
                "part3": parts[2],
                "part4": parts[3],
            }).decode())

    try:
        await conn.query(query, ns_path=str(namespace), fingerprints=data)
    except edgedb.ConstraintViolationError as exc:
        raise errors.FingerprintAlreadyExists() from exc
    except edgedb.MissingRequiredError as exc:
        raise errors.FileNotFound() from exc