Пример #1
0
async def delete_immediately(
    db_client: DBClient,
    namespace: Namespace,
    path: StrOrPath,
) -> File:
    """
    Permanently delete a file or a folder with all of its contents.

    Args:
        db_client (DBClient): Database client.
        namespace (Namespace): Namespace where file/folder should be deleted.
        path (StrOrPath): Path to a file/folder to delete.

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

    Returns:
        File: Deleted file.
    """
    async for tx in db_client.transaction():  # pragma: no branch
        async with tx:
            file = await crud.file.delete(tx, namespace.path, path)

    await storage.delete(namespace.path, path),
    return file
Пример #2
0
async def save_file(
    db_client: DBClient,
    namespace: Namespace,
    path: StrOrPath,
    content: IO[bytes],
) -> File:
    """
    Save file to storage and database.

    If file name is already taken, then file will be saved under a new name.
    For example - if target name 'f.txt' is taken, then new name will be 'f (1).txt'.

    Args:
        db_client (DBClient): Database client.
        namespace (Namespace): Namespace where a file should be saved.
        path (StrOrPath): Path where a file will be saved.
        content (IO): Actual file.

    Raises:
        NotADirectory: If one of the path parents is not a folder.

    Returns:
        File: Saved file.
    """
    parent = os.path.normpath(os.path.dirname(path))

    if not await crud.file.exists(db_client, namespace.path, parent):
        await create_folder(db_client, namespace, parent)

    next_path = await crud.file.next_path(db_client, namespace.path, path)

    storage_file = await storage.save(namespace.path, next_path, content)

    mediatype = mediatypes.guess(next_path, content)
    dhash = hashes.dhash(content, mediatype=mediatype)

    async for tx in db_client.transaction():  # pragma: no branch
        async with tx:
            file = await crud.file.create(
                tx,
                namespace.path,
                next_path,
                size=storage_file.size,
                mediatype=mediatype,
            )
            if dhash is not None:
                await crud.fingerprint.create(
                    tx,
                    file.id,
                    fp=dhash,
                )

    return file
Пример #3
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
Пример #4
0
async def create_account(
    db_client: DBClient,
    username: str,
    password: str,
    *,
    email: str | None = None,
    first_name: str = "",
    last_name: str = "",
    superuser: bool = False,
) -> Account:
    """
    Create a new user, namespace, home and trash folders.

    Args:
        db_client (DBClient): Database client.
        username (str): Username for a new user.
        password (str): Plain-text password.
        email (str | None, optional): Email. Defaults to None.
        first_name (str, optional): First name. Defaults to "".
        last_name (str, optional): Last name. Defaults to "".
        superuser (bool, optional): Whether user is super user or not. Defaults to
            False.

    Raises:
        UserAlreadyExists: If user with this username or email already exists.

    Returns:
        Account: A freshly created account.
    """
    username = username.lower()
    await storage.makedirs(username, config.TRASH_FOLDER_NAME)

    async for tx in db_client.transaction():  # pragma: no branch
        async with tx:
            user = await crud.user.create(tx,
                                          username,
                                          password,
                                          superuser=superuser)
            namespace = await crud.namespace.create(tx, username, user.id)
            await crud.file.create_home_folder(tx, namespace.path)
            await crud.file.create_folder(tx, namespace.path,
                                          config.TRASH_FOLDER_NAME)
            account = await crud.account.create(tx,
                                                username,
                                                email=email,
                                                first_name=first_name,
                                                last_name=last_name)
    return account
Пример #5
0
async def tx(request: FixtureRequest, session_db_client: DBClient):
    """Yield a transaction and rollback it after each test."""
    marker = request.node.get_closest_marker("database")
    if not marker:
        raise RuntimeError("Access to database without `database` marker!")

    if marker.kwargs.get("transaction", False):
        raise RuntimeError(
            "Can't use `tx` fixture with `transaction=True` option")

    async for transaction in session_db_client.transaction():
        transaction._managed = True
        try:
            yield transaction
        finally:
            await transaction._exit(Exception, None)
Пример #6
0
async def migrate(conn: DBClient, schema: str) -> None:
    """
    Run migration to a target schema in a new transaction.

    Args:
        conn (DBClient): Connection to a database.
        schema (str): Schema to migrate to.
    """
    async for tx in conn.transaction():
        async with tx:
            await tx.execute(f"""
                START MIGRATION TO {{
                    {schema}
                }};
                POPULATE MIGRATION;
                COMMIT MIGRATION;
            """)
Пример #7
0
async def empty_trash(db_client: DBClient, namespace: Namespace) -> File:
    """
    Delete all files and folders in the Trash folder within a target Namespace.

    Args:
        db_client (DBClient): Database client.
        namespace (Namespace): Namespace where Trash folder should be emptied.

    Returns:
        File: Trash folder.
    """
    ns_path = namespace.path
    files = await crud.file.list_folder(db_client, ns_path,
                                        config.TRASH_FOLDER_NAME)
    async for tx in db_client.transaction():  # pragma: no branch
        async with tx:
            await crud.file.empty_trash(tx, ns_path)

    for file in files:
        await storage.delete(ns_path, file.path)

    return await crud.file.get(db_client, ns_path, config.TRASH_FOLDER_NAME)