Source code for osmium_chat.file
from pathlib import Path
from typing import TYPE_CHECKING
from osmium_protos import (
PB_DownloadFilePart,
PB_File,
PB_FileRef,
PB_FileRefMediaFileRef,
)
if TYPE_CHECKING:
from osmium_chat.client import Client
__all__: tuple[str, ...] = (
"File",
)
# 1 MiB per download request.
_DOWNLOAD_CHUNK_SIZE: int = 1024 * 1024
[docs]
class File:
"""A file attachment on a received message.
Wraps the :class:`~osmium_protos.PB_File` metadata the server sends
alongside a message. Call :meth:`download` to fetch the raw bytes.
"""
__slots__: tuple[str, ...] = (
"file_id",
"region",
"size",
"mimetype",
"filename",
"_client",
)
def __init__(self, file: PB_File, client: "Client") -> None:
"""Build a file attachment from a protobuf payload.
:param file: The raw ``PB_File`` to read fields from.
:param client: The client used to download the file.
"""
self.file_id: int = file.file_id
self.region: int = file.region
self.size: int = file.size
self.mimetype: str = file.mimetype
self.filename: str | None = file.filename
self._client = client
[docs]
async def download(self) -> bytes:
"""Fetch and return the full file contents as :class:`bytes`.
Requests the file in :data:`_DOWNLOAD_CHUNK_SIZE`-byte windows and
reassembles them in order.
:returns: The complete file bytes.
:raises RequestError: If the gateway rejects any part request.
"""
file_ref = PB_FileRef(media_file=PB_FileRefMediaFileRef(file_id=self.file_id))
chunks: list[bytes] = []
offset = 0
while offset < self.size:
length = min(_DOWNLOAD_CHUNK_SIZE, self.size - offset)
result = await self._client.request(PB_DownloadFilePart(
file_ref=file_ref,
offset=offset,
length=length,
))
part = result.file_part
if part is None or not part.data:
break
chunks.append(part.data)
offset += len(part.data)
return b"".join(chunks)
[docs]
async def save(self, path: "str | Path | None" = None) -> Path:
"""Download the file and write it to disk.
:param path: Destination path. Pass a directory to write
``<dir>/<filename>`` using the server-provided name, or a full file
path to control the exact location. Defaults to the current working
directory with the server-provided name (or ``file_<id>`` when the
server did not supply one).
:returns: The :class:`~pathlib.Path` the file was written to.
:raises RequestError: If the gateway rejects any part request.
"""
dest = Path(path) if path is not None else Path(".")
if dest.is_dir():
dest = dest / (self.filename or f"file_{self.file_id}")
dest.write_bytes(await self.download())
return dest