From e4aa3b65726f96fd87621c6e459e9a493bb517fd Mon Sep 17 00:00:00 2001 From: miruka Date: Mon, 2 Dec 2019 02:57:47 -0400 Subject: [PATCH] Handle upload errors --- TODO.md | 2 ++ src/python/matrix_client.py | 61 ++++++++++++++++++++++--------------- src/python/models/items.py | 16 ++++++---- src/qml/Chat/UploadsBar.qml | 34 ++++++++++++++++++--- src/themes/Default.qpl | 8 +++-- 5 files changed, 83 insertions(+), 38 deletions(-) diff --git a/TODO.md b/TODO.md index 19ec9141..c1e6bb72 100644 --- a/TODO.md +++ b/TODO.md @@ -57,6 +57,7 @@ - Quote links color in room subtitles (e.g. "> http://foo.orgA)" ) - UI + - Standardize usage of punctuation - Way to open context menus without a right mouse button - `smartVerticalFlick()` gradual acceleration @@ -184,6 +185,7 @@ - Live-reloading accounts.json - nio + - Dedicated error for invalid password on key import - Running blocking DB function calls in executor - `AsyncClient.share_group_session`: send device batches concurrently diff --git a/src/python/matrix_client.py b/src/python/matrix_client.py index 624bf96f..f8407a4d 100644 --- a/src/python/matrix_client.py +++ b/src/python/matrix_client.py @@ -220,9 +220,15 @@ class MatrixClient(nio.AsyncClient): upload_item = Upload(path, total_size=size) self.models[Upload, room_id][upload_item.uuid] = upload_item - url, mime, crypt_dict = await self.upload( - path, filename=path.name, encrypt=encrypt, - ) + try: + url, mime, crypt_dict = await self.upload( + path, filename=path.name, encrypt=encrypt, + ) + except MatrixError as err: + upload_item.status = UploadStatus.Error + upload_item.error = type(err) + upload_item.error_args = err.args + return upload_item.status = UploadStatus.Caching await Media.from_existing_file(self.backend.media_cache, url, path) @@ -269,30 +275,35 @@ class MatrixClient(nio.AsyncClient): else: upload_item.status = UploadStatus.UploadingThumbnail - thumb_url, _, thumb_crypt_dict = await self.upload( - thumb_data, - filename = f"{path.stem}_sample{''.join(path.suffixes)}", - encrypt = encrypt, - ) - - upload_item.status = UploadStatus.CachingThumbnail - - await Thumbnail.from_bytes( - self.backend.media_cache, - thumb_url, - thumb_data, - wanted_size = (content["info"]["w"], content["info"]["h"]), - ) - - if encrypt: - content["info"]["thumbnail_file"] = { - "url": thumb_url, - **thumb_crypt_dict, - } + try: + thumb_url, _, thumb_crypt_dict = await self.upload( + thumb_data, + filename = + f"{path.stem}_sample{''.join(path.suffixes)}", + encrypt = encrypt, + ) + except MatrixError as err: + log.warning(f"Failed uploading thumbnail {path}: {err}") else: - content["info"]["thumbnail_url"] = thumb_url + upload_item.status = UploadStatus.CachingThumbnail - content["info"]["thumbnail_info"] = thumb_info._asdict() + await Thumbnail.from_bytes( + self.backend.media_cache, + thumb_url, + thumb_data, + wanted_size = (content["info"]["w"], + content["info"]["h"]), + ) + + if encrypt: + content["info"]["thumbnail_file"] = { + "url": thumb_url, + **thumb_crypt_dict, + } + else: + content["info"]["thumbnail_url"] = thumb_url + + content["info"]["thumbnail_info"] = thumb_info._asdict() elif kind == "audio": event_type = \ diff --git a/src/python/models/items.py b/src/python/models/items.py index ee441d56..0f961bdb 100644 --- a/src/python/models/items.py +++ b/src/python/models/items.py @@ -2,7 +2,7 @@ import re from dataclasses import dataclass, field from datetime import datetime from pathlib import Path -from typing import Any, Dict, List, Optional, Type +from typing import Any, Dict, List, Optional, Tuple, Type, Union from uuid import uuid4 import lxml # nosec @@ -13,6 +13,8 @@ from ..html_filter import HTML_FILTER from ..utils import AutoStrEnum, auto from .model_item import ModelItem +OptionalExceptionType = Union[Type[None], Type[Exception]] + @dataclass class Account(ModelItem): @@ -106,15 +108,17 @@ class UploadStatus(AutoStrEnum): Caching = auto() UploadingThumbnail = auto() CachingThumbnail = auto() - Failure = auto() + Error = auto() @dataclass class Upload(ModelItem): - filepath: Path = field() - status: UploadStatus = UploadStatus.Uploading - total_size: int = 0 - uploaded: int = 0 + filepath: Path = field() + status: UploadStatus = UploadStatus.Uploading + total_size: int = 0 + uploaded: int = 0 + error: OptionalExceptionType = type(None) + error_args: Tuple[Any, ...] = () uuid: str = field(init=False, default_factory=lambda: str(uuid4())) start_date: datetime = field(init=False, default_factory=datetime.now) diff --git a/src/qml/Chat/UploadsBar.qml b/src/qml/Chat/UploadsBar.qml index 40653e6b..1ce2a4e0 100644 --- a/src/qml/Chat/UploadsBar.qml +++ b/src/qml/Chat/UploadsBar.qml @@ -39,6 +39,10 @@ Rectangle { HLabel { id: filenameLabel elide: Text.ElideRight + + color: model.status === "Error" ? + theme.colors.errorText : theme.colors.text + text: model.status === "Uploading" ? qsTr("Uploading %1...").arg(fileName) : @@ -47,13 +51,25 @@ Rectangle { qsTr("Caching %1...").arg(fileName) : model.status === "UploadingThumbnail" ? - qsTr("Uploading %1 thumbnail...").arg(fileName) : + qsTr("Uploading thumbnail for %1...").arg(fileName) : model.status === "CachingThumbnail" ? - qsTr("Caching %1 thumbnail...").arg(fileName) : + qsTr("Caching thumbnail for %1...").arg(fileName) : - model.status === "Failure" ? - qsTr("Uploading %1 failed").arg(fileName) : + model.status === "Error" ? ( + model.error === "MatrixForbidden" ? + qsTr("Forbidden file type or quota exceeded: %1") + .arg(fileName) : + + model.error === "MatrixTooLarge" ? + qsTr("Too large for this server: %1") + .arg(fileName) : + + qsTr("Unknown error for %1: %2 - %3") + .arg(fileName) + .arg(model.error) + .arg(model.error_args) + ) : qsTr("Invalid status for %1: %2") .arg(fileName).arg(model.status) @@ -91,8 +107,18 @@ Rectangle { HProgressBar { id: progressBar + visible: Layout.maximumHeight !== 0 indeterminate: true + foregroundColor: + model.status === "Error" ? + theme.controls.progressBar.errorForeground : + theme.controls.progressBar.foreground + Layout.fillWidth: true + Layout.maximumHeight: + model.status === "Error" && indeterminate ? 0 : -1 + + Behavior on Layout.maximumHeight { HNumberAnimation {} } } } } diff --git a/src/themes/Default.qpl b/src/themes/Default.qpl index bc5a8c79..ef6c2f58 100644 --- a/src/themes/Default.qpl +++ b/src/themes/Default.qpl @@ -183,14 +183,16 @@ controls: int borderWidth: 2 progressBar: - int height: Math.max(2, spacing / 2) - color background: colors.inputBackground - color foreground: colors.accentBackground + int height: Math.max(2, spacing / 2) + color background: colors.inputBackground + color foreground: colors.accentBackground + color errorForeground: colors.negativeBackground circleProgressBar: int thickness: Math.max(2, spacing / 2) color background: colors.inputBackground color foreground: colors.accentBackground + color errorForeground: colors.negativeBackground color text: colors.text real indeterminateSpan: 0.5 // 0-1