From df3f1fb64542303233a6f972d3a98a6ac707bd52 Mon Sep 17 00:00:00 2001 From: miruka Date: Sun, 23 Aug 2020 16:57:53 -0400 Subject: [PATCH] Remove all Backend "get_any_client"-using methods thumbnail() and download() were remaining. Use a direct and carefully chosen MatrixClient's methods instead to avoid problems mentioned in the previous commit 7502c1. --- TODO.md | 1 + src/backend/backend.py | 103 ++++++------------ src/backend/matrix_client.py | 15 +-- src/backend/media_cache.py | 83 ++++++++------ src/backend/nio_callbacks.py | 9 +- src/gui/Base/HAvatar.qml | 4 +- src/gui/Base/HMxcImage.qml | 8 +- src/gui/MainPane/AccountDelegate.qml | 1 + src/gui/MainPane/RoomDelegate.qml | 1 + src/gui/Pages/AccountSettings/Account.qml | 1 + src/gui/Pages/AddChat/CreateRoom.qml | 1 + src/gui/Pages/AddChat/CurrentUserAvatar.qml | 2 +- .../CompletableUserDelegate.qml | 1 + src/gui/Pages/Chat/Banners/Banner.qml | 1 + src/gui/Pages/Chat/Composer/Composer.qml | 1 + src/gui/Pages/Chat/RoomHeader.qml | 1 + .../RoomPane/MemberView/MemberDelegate.qml | 1 + .../RoomPane/MemberView/MemberProfile.qml | 1 + src/gui/Pages/Chat/RoomPane/SettingsView.qml | 1 + src/gui/Pages/Chat/Timeline/EventContent.qml | 1 + src/gui/Pages/Chat/Timeline/EventImage.qml | 1 + src/gui/Pages/Chat/Timeline/EventList.qml | 2 + .../ImageViewerPopup/ImageViewerPopup.qml | 4 + .../Popups/ImageViewerPopup/ViewerCanvas.qml | 2 + 24 files changed, 117 insertions(+), 129 deletions(-) diff --git a/TODO.md b/TODO.md index c195dd83..b766e93a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ # TODO +- composer state.json writingUserId problem? - refresh server list button - global presence control diff --git a/src/backend/backend.py b/src/backend/backend.py index d7f099a1..90b3aa7b 100644 --- a/src/backend/backend.py +++ b/src/backend/backend.py @@ -349,46 +349,7 @@ class Backend: failures += 1 - async def get_any_client(self) -> MatrixClient: - """Return any healthy syncing `MatrixClient` registered in model.""" - - failures = 0 - - while True: - for client in self.clients.values(): - if client.healthy: - return client - - if failures and failures % 300 == 0: - log.warn( - "No healthy client found after %ds, stack trace:\n%s", - failures / 10, traceback.format_stack(), - ) - - await asyncio.sleep(0.1) - failures += 1 - - - # Client functions that don't need authentification - - async def thumbnail( - self, server_name: str, media_id: str, width: int, height: int, - ) -> nio.ThumbnailResponse: - """Return thumbnail for a matrix media.""" - - args = (server_name, media_id, width, height) - client = await self.get_any_client() - return await client.thumbnail(*args) - - - async def download( - self, server_name: str, media_id: str, - ) -> nio.DownloadResponse: - """Return the content of a matrix media.""" - - client = await self.get_any_client() - return await client.download(server_name, media_id) - + # Multi-client Matrix functions async def update_room_read_marker( self, room_id: str, event_id: str, @@ -416,6 +377,37 @@ class Backend: await asyncio.gather(*[update(c) for c in self.clients.values()]) + async def verify_device( + self, user_id: str, device_id: str, ed25519_key: str, + ) -> None: + """Mark a device as verified on all our accounts.""" + + for client in self.clients.values(): + try: + device = client.device_store[user_id][device_id] + except KeyError: + continue + + if device.ed25519 == ed25519_key: + client.verify_device(device) + + + async def blacklist_device( + self, user_id: str, device_id: str, ed25519_key: str, + ) -> None: + """Mark a device as blacklisted on all our accounts.""" + + for client in self.clients.values(): + try: + # This won't include the client's current device, as expected + device = client.device_store[user_id][device_id] + except KeyError: + continue + + if device.ed25519 == ed25519_key: + client.blacklist_device(device) + + # General functions async def get_config_dir(self) -> Path: @@ -463,37 +455,6 @@ class Backend: self.models["all_rooms"].set_account_collapse(user_id, collapse) - async def verify_device( - self, user_id: str, device_id: str, ed25519_key: str, - ) -> None: - """Mark a device as verified on all our accounts.""" - - for client in self.clients.values(): - try: - device = client.device_store[user_id][device_id] - except KeyError: - continue - - if device.ed25519 == ed25519_key: - client.verify_device(device) - - - async def blacklist_device( - self, user_id: str, device_id: str, ed25519_key: str, - ) -> None: - """Mark a device as blacklisted on all our accounts.""" - - for client in self.clients.values(): - try: - # This won't include the client's current device, as expected - device = client.device_store[user_id][device_id] - except KeyError: - continue - - if device.ed25519 == ed25519_key: - client.blacklist_device(device) - - async def _ping_homeserver( self, session: aiohttp.ClientSession, homeserver_url: str, ) -> None: diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 94b9d5d6..5dc620fe 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -210,18 +210,6 @@ class MatrixClient(nio.AsyncClient): ) - @property - def healthy(self) -> bool: - """Return whether we're syncing and last sync was successful.""" - - task = self.sync_task - - if not task or not self.first_sync_date or self.last_sync_error: - return False - - return not task.done() - - @property def default_device_name(self) -> str: """Device name to set at login if the user hasn't set a custom one.""" @@ -734,7 +722,7 @@ class MatrixClient(nio.AsyncClient): upload_item.status = UploadStatus.Caching local_media = await Media.from_existing_file( - self.backend.media_cache, url, path, + self.backend.media_cache, self.user_id, url, path, ) kind = (mime or "").split("/")[0] @@ -816,6 +804,7 @@ class MatrixClient(nio.AsyncClient): await Thumbnail.from_bytes( self.backend.media_cache, + self.user_id, thumb_url, path.name, thumb_data, diff --git a/src/backend/media_cache.py b/src/backend/media_cache.py index afbfaa1e..396173b5 100644 --- a/src/backend/media_cache.py +++ b/src/backend/media_cache.py @@ -49,29 +49,31 @@ class MediaCache: async def get_media( self, - mxc: str, - title: str, - crypt_dict: CryptDict = None, + client_user_id: str, + mxc: str, + title: str, + crypt_dict: CryptDict = None, ) -> Path: """Return `Media.get()`'s result. Intended for QML.""" - return await Media(self, mxc, title, crypt_dict).get() + return await Media(self, client_user_id, mxc, title, crypt_dict).get() async def get_thumbnail( self, - mxc: str, - title: str, - width: int, - height: int, - crypt_dict: CryptDict = None, + client_user_id: str, + mxc: str, + title: str, + width: int, + height: int, + crypt_dict: CryptDict = None, ) -> Path: """Return `Thumbnail.get()`'s result. Intended for QML.""" - thumb = Thumbnail( - # QML sometimes pass float sizes, which matrix API doesn't like. - self, mxc, title, crypt_dict, (round(width), round(height)), - ) + # QML sometimes pass float sizes, which matrix API doesn't like. + size = (round(width), round(height)) + + thumb = Thumbnail(self, client_user_id, mxc, title, crypt_dict, size) return await thumb.get() @@ -79,10 +81,11 @@ class MediaCache: class Media: """A matrix media file.""" - cache: "MediaCache" = field() - mxc: str = field() - title: str = field() - crypt_dict: CryptDict = field(repr=False) + cache: "MediaCache" = field() + client_user_id: str = field() + mxc: str = field() + title: str = field() + crypt_dict: CryptDict = field(repr=False) def __post_init__(self) -> None: @@ -154,7 +157,7 @@ class Media: parsed = urlparse(self.mxc) - resp = await self.cache.backend.download( + resp = await self.cache.backend.clients[self.client_user_id].download( server_name = parsed.netloc, media_id = parsed.path.lstrip("/"), ) @@ -183,15 +186,18 @@ class Media: @classmethod async def from_existing_file( cls, - cache: "MediaCache", - mxc: str, - existing: Path, - overwrite: bool = False, + cache: "MediaCache", + client_user_id: str, + mxc: str, + existing: Path, + overwrite: bool = False, **kwargs, ) -> "Media": """Copy an existing file to cache and return a `Media` for it.""" - media = cls(cache, mxc, existing.name, {}, **kwargs) # type: ignore + media = cls( + cache, client_user_id, mxc, existing.name, {}, **kwargs, + ) # type: ignore media.local_path.parent.mkdir(parents=True, exist_ok=True) if not media.local_path.exists() or overwrite: @@ -204,16 +210,19 @@ class Media: @classmethod async def from_bytes( cls, - cache: "MediaCache", - mxc: str, - filename: str, - data: bytes, - overwrite: bool = False, + cache: "MediaCache", + client_user_id: str, + mxc: str, + filename: str, + data: bytes, + overwrite: bool = False, **kwargs, ) -> "Media": """Create a cached file from bytes data and return a `Media` for it.""" - media = cls(cache, mxc, filename, {}, **kwargs) # type: ignore + media = cls( + cache, client_user_id, mxc, filename, {}, **kwargs, + ) # type: ignore media.local_path.parent.mkdir(parents=True, exist_ok=True) if not media.local_path.exists() or overwrite: @@ -230,11 +239,12 @@ class Media: class Thumbnail(Media): """The thumbnail of a matrix media, which is a media itself.""" - cache: "MediaCache" = field() - mxc: str = field() - title: str = field() - crypt_dict: CryptDict = field(repr=False) - wanted_size: Size = field() + cache: "MediaCache" = field() + client_user_id: str = field() + mxc: str = field() + title: str = field() + crypt_dict: CryptDict = field(repr=False) + wanted_size: Size = field() server_size: Optional[Size] = field(init=False, repr=False, default=None) @@ -322,16 +332,17 @@ class Thumbnail(Media): """Return the (decrypted) media file's content from the server.""" parsed = urlparse(self.mxc) + client = self.cache.backend.clients[self.client_user_id] if self.crypt_dict: # Matrix makes encrypted thumbs only available through the download # end-point, not the thumbnail one - resp = await self.cache.backend.download( + resp = await client.download( server_name = parsed.netloc, media_id = parsed.path.lstrip("/"), ) else: - resp = await self.cache.backend.thumbnail( + resp = await client.thumbnail( server_name = parsed.netloc, media_id = parsed.path.lstrip("/"), width = self.wanted_size[0], diff --git a/src/backend/nio_callbacks.py b/src/backend/nio_callbacks.py index 5492126b..e35954ad 100644 --- a/src/backend/nio_callbacks.py +++ b/src/backend/nio_callbacks.py @@ -192,10 +192,11 @@ class NioCallbacks: try: media_local_path: Union[Path, str] = await Media( - cache = self.client.backend.media_cache, - mxc = ev.url, - title = ev.body, - crypt_dict = media_crypt_dict, + cache = self.client.backend.media_cache, + client_user_id = self.user_id, + mxc = ev.url, + title = ev.body, + crypt_dict = media_crypt_dict, ).get_local() except FileNotFoundError: media_local_path = "" diff --git a/src/gui/Base/HAvatar.qml b/src/gui/Base/HAvatar.qml index e60cde74..ba4e47a5 100644 --- a/src/gui/Base/HAvatar.qml +++ b/src/gui/Base/HAvatar.qml @@ -9,6 +9,7 @@ Rectangle { property bool compact: false property string name + property alias clientUserId: avatarImage.clientUserId property alias mxc: avatarImage.mxc property alias title: avatarImage.title @@ -60,11 +61,11 @@ Rectangle { HMxcImage { id: avatarImage anchors.fill: parent - showProgressBar: false visible: Boolean(sourceOverride || mxc) z: 2 sourceSize.width: parent.width sourceSize.height: parent.height + showProgressBar: false fillMode: Image.PreserveAspectCrop animatedFillMode: AnimatedImage.PreserveAspectCrop animate: false @@ -95,6 +96,7 @@ Rectangle { id: avatarToolTipImage fillMode: Image.PreserveAspectCrop animatedFillMode: AnimatedImage.PreserveAspectCrop + clientUserId: avatarImage.clientUserId mxc: avatarImage.mxc title: avatarImage.title diff --git a/src/gui/Base/HMxcImage.qml b/src/gui/Base/HMxcImage.qml index 48be678a..bd6a527d 100644 --- a/src/gui/Base/HMxcImage.qml +++ b/src/gui/Base/HMxcImage.qml @@ -6,6 +6,7 @@ import "../PythonBridge" HImage { id: image + property string clientUserId property string mxc property string title property string sourceOverride: "" @@ -38,9 +39,10 @@ HImage { } const method = image.thumbnail ? "get_thumbnail" : "get_media" - const args = image.thumbnail ? - [image.mxc, image.title, w, h, cryptDict] : - [image.mxc, image.title, cryptDict] + const args = + image.thumbnail ? + [clientUserId, image.mxc, image.title, w, h, cryptDict] : + [clientUserId, image.mxc, image.title, cryptDict] getFuture = py.callCoro("media_cache." + method, args, path => { if (! image) return diff --git a/src/gui/MainPane/AccountDelegate.qml b/src/gui/MainPane/AccountDelegate.qml index c88a51ef..18e4b818 100644 --- a/src/gui/MainPane/AccountDelegate.qml +++ b/src/gui/MainPane/AccountDelegate.qml @@ -57,6 +57,7 @@ HTile { HUserAvatar { id: avatar + clientUserId: model.id userId: model.id displayName: model.display_name mxc: model.avatar_url diff --git a/src/gui/MainPane/RoomDelegate.qml b/src/gui/MainPane/RoomDelegate.qml index ca7f03b2..283d1eb2 100644 --- a/src/gui/MainPane/RoomDelegate.qml +++ b/src/gui/MainPane/RoomDelegate.qml @@ -48,6 +48,7 @@ HTile { HRoomAvatar { id: avatar + clientUserId: model.for_account roomId: model.id displayName: model.display_name mxc: model.avatar_url diff --git a/src/gui/Pages/AccountSettings/Account.qml b/src/gui/Pages/AccountSettings/Account.qml index b6e60bfa..ac34b7ae 100644 --- a/src/gui/Pages/AccountSettings/Account.qml +++ b/src/gui/Pages/AccountSettings/Account.qml @@ -93,6 +93,7 @@ HFlickableColumnPage { property bool changed: Boolean(sourceOverride) + clientUserId: page.userId userId: page.userId displayName: nameField.item.text mxc: account ? account.avatar_url : "" diff --git a/src/gui/Pages/AddChat/CreateRoom.qml b/src/gui/Pages/AddChat/CreateRoom.qml index 083b4d8d..0e34385a 100644 --- a/src/gui/Pages/AddChat/CreateRoom.qml +++ b/src/gui/Pages/AddChat/CreateRoom.qml @@ -69,6 +69,7 @@ HFlickableColumnPage { HRoomAvatar { id: avatar + clientUserId: page.userId roomId: "" displayName: nameField.item.text diff --git a/src/gui/Pages/AddChat/CurrentUserAvatar.qml b/src/gui/Pages/AddChat/CurrentUserAvatar.qml index 39d7a74a..e37521b6 100644 --- a/src/gui/Pages/AddChat/CurrentUserAvatar.qml +++ b/src/gui/Pages/AddChat/CurrentUserAvatar.qml @@ -8,7 +8,7 @@ HUserAvatar { property QtObject account - // userId: (set me) + clientUserId: userId displayName: account ? account.display_name : "" mxc: account ? account.avatar_url : "" diff --git a/src/gui/Pages/Chat/AutoCompletion/CompletableUserDelegate.qml b/src/gui/Pages/Chat/AutoCompletion/CompletableUserDelegate.qml index 1c4f503b..f357b1d6 100644 --- a/src/gui/Pages/Chat/AutoCompletion/CompletableUserDelegate.qml +++ b/src/gui/Pages/Chat/AutoCompletion/CompletableUserDelegate.qml @@ -16,6 +16,7 @@ HTile { HUserAvatar { id: avatar + clientUserId: chat.userId userId: model.id displayName: model.display_name mxc: model.avatar_url diff --git a/src/gui/Pages/Chat/Banners/Banner.qml b/src/gui/Pages/Chat/Banners/Banner.qml index 2aa2c7d7..26233988 100644 --- a/src/gui/Pages/Chat/Banners/Banner.qml +++ b/src/gui/Pages/Chat/Banners/Banner.qml @@ -40,6 +40,7 @@ Rectangle { HUserAvatar { id: bannerAvatar + clientUserId: chat.userId anchors.centerIn: parent radius: 0 } diff --git a/src/gui/Pages/Chat/Composer/Composer.qml b/src/gui/Pages/Chat/Composer/Composer.qml index 1e72fee8..cde75bf4 100644 --- a/src/gui/Pages/Chat/Composer/Composer.qml +++ b/src/gui/Pages/Chat/Composer/Composer.qml @@ -25,6 +25,7 @@ Rectangle { HUserAvatar { id: avatar radius: 0 + clientUserId: messageArea.writingUserId userId: messageArea.writingUserId mxc: diff --git a/src/gui/Pages/Chat/RoomHeader.qml b/src/gui/Pages/Chat/RoomHeader.qml index d41f53d2..5c0eaf6d 100644 --- a/src/gui/Pages/Chat/RoomHeader.qml +++ b/src/gui/Pages/Chat/RoomHeader.qml @@ -52,6 +52,7 @@ Rectangle { HRoomAvatar { id: avatar + clientUserId: chat.userId roomId: chat.roomId displayName: chat.roomInfo.display_name mxc: chat.roomInfo.avatar_url diff --git a/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml b/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml index 6f86035d..57499671 100644 --- a/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml +++ b/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml @@ -23,6 +23,7 @@ HTile { HUserAvatar { id: avatar + clientUserId: chat.userId userId: model.id displayName: model.display_name mxc: model.avatar_url diff --git a/src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml b/src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml index 7e771ad1..34233061 100644 --- a/src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml +++ b/src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml @@ -61,6 +61,7 @@ HListView { root.currentIndex = 0 HUserAvatar { + clientUserId: chat.userId userId: member.id displayName: member.display_name mxc: member.avatar_url diff --git a/src/gui/Pages/Chat/RoomPane/SettingsView.qml b/src/gui/Pages/Chat/RoomPane/SettingsView.qml index 3b0a9851..f3ffdceb 100644 --- a/src/gui/Pages/Chat/RoomPane/SettingsView.qml +++ b/src/gui/Pages/Chat/RoomPane/SettingsView.qml @@ -82,6 +82,7 @@ HFlickableColumnPage { HRoomAvatar { id: avatar + clientUserId: chat.userId roomId: chat.roomId displayName: nameField.item.text || chat.roomInfo.display_name mxc: chat.roomInfo.avatar_url diff --git a/src/gui/Pages/Chat/Timeline/EventContent.qml b/src/gui/Pages/Chat/Timeline/EventContent.qml index 257b58e1..60f6aae8 100644 --- a/src/gui/Pages/Chat/Timeline/EventContent.qml +++ b/src/gui/Pages/Chat/Timeline/EventContent.qml @@ -94,6 +94,7 @@ HRowLayout { HUserAvatar { id: avatar + clientUserId: chat.userId userId: model.sender_id displayName: model.sender_name mxc: model.sender_avatar diff --git a/src/gui/Pages/Chat/Timeline/EventImage.qml b/src/gui/Pages/Chat/Timeline/EventImage.qml index 23d42cb7..f24ccbb0 100644 --- a/src/gui/Pages/Chat/Timeline/EventImage.qml +++ b/src/gui/Pages/Chat/Timeline/EventImage.qml @@ -60,6 +60,7 @@ HMxcImage { height: fitSize.height horizontalAlignment: Image.AlignLeft + clientUserId: chat.userId title: thumbnail ? loader.thumbnailTitle : loader.title animated: eventList.isAnimated(loader.singleMediaInfo) thumbnail: ! animated && loader.thumbnailMxc diff --git a/src/gui/Pages/Chat/Timeline/EventList.qml b/src/gui/Pages/Chat/Timeline/EventList.qml index 2da22dca..89289044 100644 --- a/src/gui/Pages/Chat/Timeline/EventList.qml +++ b/src/gui/Pages/Chat/Timeline/EventList.qml @@ -416,6 +416,7 @@ Rectangle { window.makePopup( "Popups/ImageViewerPopup/ImageViewerPopup.qml", { + clientUserId: chat.userId, thumbnailTitle: getThumbnailTitle(event), thumbnailMxc: event.thumbnail_url, thumbnailPath: eventList.thumbnailCachedPaths[event.id], @@ -459,6 +460,7 @@ Rectangle { print("Downloading " + event.media_url + " ...") const args = [ + chat.userId, event.media_url, event.media_title, JSON.parse(event.media_crypt_dict), diff --git a/src/gui/Popups/ImageViewerPopup/ImageViewerPopup.qml b/src/gui/Popups/ImageViewerPopup/ImageViewerPopup.qml index 2e7285ce..41b183c1 100644 --- a/src/gui/Popups/ImageViewerPopup/ImageViewerPopup.qml +++ b/src/gui/Popups/ImageViewerPopup/ImageViewerPopup.qml @@ -8,14 +8,18 @@ import "../../Base" HPopup { id: popup + property string clientUserId + property string thumbnailTitle property string thumbnailMxc property string thumbnailPath: "" property var thumbnailCryptDict + property string fullTitle property string fullMxc property var fullCryptDict property int fullFileSize + property size overallSize property bool alternateScaling: false diff --git a/src/gui/Popups/ImageViewerPopup/ViewerCanvas.qml b/src/gui/Popups/ImageViewerPopup/ViewerCanvas.qml index 7c109548..0a536139 100644 --- a/src/gui/Popups/ImageViewerPopup/ViewerCanvas.qml +++ b/src/gui/Popups/ImageViewerPopup/ViewerCanvas.qml @@ -107,6 +107,7 @@ HFlickable { Math.min(window.height, viewer.overallSize.height) + clientUserId: viewer.clientUserId showPauseButton: false showProgressBar: false pause: viewer.imagesPaused @@ -152,6 +153,7 @@ HFlickable { HMxcImage { id: full anchors.fill: parent + clientUserId: viewer.clientUserId thumbnail: false showPauseButton: false pause: viewer.imagesPaused