diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index eccb7a42..af34ee73 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -18,8 +18,8 @@ from functools import partial from pathlib import Path from tempfile import NamedTemporaryFile from typing import ( - TYPE_CHECKING, Any, ClassVar, Dict, List, NamedTuple, Optional, Set, Tuple, - Type, Union, + TYPE_CHECKING, Any, Callable, ClassVar, Coroutine, Dict, List, NamedTuple, + Optional, Set, Tuple, Type, Union, ) from urllib.parse import urlparse from uuid import UUID, uuid4 @@ -56,7 +56,10 @@ if sys.version_info >= (3, 7): else: current_task = asyncio.Task.current_task -CryptDict = Dict[str, Any] +CryptDict = Dict[str, Any] +PathCallable = Union[ + str, Path, Callable[[], Coroutine[None, None, Union[str, Path]]], +] REPLY_FALLBACK = ( "" @@ -565,13 +568,20 @@ class MatrixClient(nio.AsyncClient): prefix = datetime.now().strftime("%Y%m%d-%H%M%S.") with NamedTemporaryFile(prefix=prefix, suffix=".png") as temp: - async with aiofiles.open(temp.name, "wb") as file: - await file.write(image) - await self.send_file(room_id, temp.name) + async def get_path() -> Path: + with io.BytesIO(image) as inp, io.BytesIO() as buffer: + PILImage.open(inp).save(buffer, "PNG", optimize=True) + + async with aiofiles.open(temp.name, "wb") as file: + await file.write(buffer.getvalue()) + + return Path(temp.name) + + await self.send_file(room_id, get_path) - async def send_file(self, room_id: str, path: Union[Path, str]) -> None: + async def send_file(self, room_id: str, path: PathCallable) -> None: """Send a `m.file`, `m.image`, `m.audio` or `m.video` message.""" item_uuid = uuid4() @@ -579,22 +589,26 @@ class MatrixClient(nio.AsyncClient): try: await self._send_file(item_uuid, room_id, path) except (nio.TransferCancelledError, asyncio.CancelledError): - log.info("Deleting item for cancelled upload %s", item_uuid) - del self.upload_monitors[item_uuid] - del self.upload_tasks[item_uuid] - del self.models[room_id, "uploads"][str(item_uuid)] + self.upload_monitors.pop(item_uuid, None) + self.upload_tasks.pop(item_uuid, None) + self.models[room_id, "uploads"].pop(str(item_uuid), None) async def _send_file( - self, item_uuid: UUID, room_id: str, path: Union[Path, str], + self, item_uuid: UUID, room_id: str, path: PathCallable, ) -> None: """Upload and monitor a file + thumbnail and send the built event.""" # TODO: this function is way too complex, and most of it should be # refactored into nio. + self.upload_tasks[item_uuid] = current_task() # type: ignore + + upload_item = Upload(item_uuid) + self.models[room_id, "uploads"][str(item_uuid)] = upload_item + transaction_id = uuid4() - path = Path(path) + path = Path(await path() if callable(path) else path) encrypt = room_id in self.encrypted_rooms try: @@ -603,13 +617,12 @@ class MatrixClient(nio.AsyncClient): # This error will be caught again by the try block later below size = 0 - monitor = nio.TransferMonitor(size) - upload_item = Upload(item_uuid, path, total_size=size) - - self.models[room_id, "uploads"][str(item_uuid)] = upload_item + upload_item.set_fields( + status=UploadStatus.Uploading, filepath=path, total_size=size, + ) + monitor = nio.TransferMonitor(size) self.upload_monitors[item_uuid] = monitor - self.upload_tasks[item_uuid] = current_task() # type: ignore def on_transferred(transferred: int) -> None: upload_item.uploaded = transferred diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 1b80b13a..8904a193 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -311,6 +311,7 @@ class Member(ModelItem): class UploadStatus(AutoStrEnum): """Enum describing the status of an upload operation.""" + Preparing = auto() Uploading = auto() Caching = auto() Error = auto() @@ -321,7 +322,7 @@ class Upload(ModelItem): """Represent a running or failed file upload operation.""" id: UUID = field() - filepath: Path = field() + filepath: Path = Path("-") total_size: int = 0 uploaded: int = 0 @@ -329,7 +330,7 @@ class Upload(ModelItem): time_left: timedelta = timedelta(0) paused: bool = False - status: UploadStatus = UploadStatus.Uploading + status: UploadStatus = UploadStatus.Preparing error: OptionalExceptionType = type(None) error_args: Tuple[Any, ...] = () diff --git a/src/gui/Pages/Chat/FileTransfer/Transfer.qml b/src/gui/Pages/Chat/FileTransfer/Transfer.qml index d9dbd262..af6acd19 100644 --- a/src/gui/Pages/Chat/FileTransfer/Transfer.qml +++ b/src/gui/Pages/Chat/FileTransfer/Transfer.qml @@ -65,6 +65,9 @@ HColumnLayout { cancelPending ? qsTr("Cancelling...") : + status === "Preparing" ? + qsTr("Preparing file...") : + status === "Uploading" ? fileName :