download/thumbnail don't need authentification
This commit is contained in:
parent
4cc2ebf6e3
commit
73541ad7a5
1
TODO.md
1
TODO.md
|
@ -4,6 +4,7 @@
|
|||
- Handle upload errors: non existent path, path is a dir, file too big, etc
|
||||
- Show real progression for mxc thumbnail loadings, uploads and downloads
|
||||
|
||||
- Show reason under broken thumbnail icons
|
||||
- Support m.file thumbnails
|
||||
- Generate video thumbnails
|
||||
- GIFs can use the video player
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import logging as log
|
||||
from pathlib import Path
|
||||
from typing import Any, DefaultDict, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import hsluv
|
||||
|
@ -38,6 +39,10 @@ class Backend:
|
|||
self.get_profile_locks: DefaultDict[str, asyncio.Lock] = \
|
||||
DefaultDict(asyncio.Lock) # {user_id: lock}
|
||||
|
||||
from .media_cache import MediaCache
|
||||
cache_dir = Path(self.app.appdirs.user_cache_dir)
|
||||
self.media_cache = MediaCache(self, cache_dir)
|
||||
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{type(self).__name__}(clients={self.clients!r})"
|
||||
|
@ -168,28 +173,6 @@ class Backend:
|
|||
return (settings, ui_state, theme)
|
||||
|
||||
|
||||
async def get_profile(self, user_id: str) -> nio.ProfileGetResponse:
|
||||
if user_id in self.profile_cache:
|
||||
return self.profile_cache[user_id]
|
||||
|
||||
async with self.get_profile_locks[user_id]:
|
||||
while True:
|
||||
try:
|
||||
client = next(c for c in self.clients.values())
|
||||
break
|
||||
except StopIteration:
|
||||
# Retry after a bit if no client was present yet
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
response = await client.get_profile(user_id)
|
||||
|
||||
if isinstance(response, nio.ProfileGetError):
|
||||
raise MatrixError.from_nio(response)
|
||||
|
||||
self.profile_cache[user_id] = response
|
||||
return response
|
||||
|
||||
|
||||
async def get_flat_sidepane_data(self) -> List[Dict[str, Any]]:
|
||||
data = []
|
||||
|
||||
|
@ -210,3 +193,54 @@ class Backend:
|
|||
})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# Client functions that don't need authentification
|
||||
|
||||
async def _any_client(self) -> MatrixClient:
|
||||
while True:
|
||||
try:
|
||||
return next(c for c in self.clients.values())
|
||||
except StopIteration:
|
||||
# Retry after a bit if we don't have any clients yet
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
async def get_profile(self, user_id: str) -> nio.ProfileGetResponse:
|
||||
if user_id in self.profile_cache:
|
||||
return self.profile_cache[user_id]
|
||||
|
||||
async with self.get_profile_locks[user_id]:
|
||||
response = await (await self._any_client()).get_profile(user_id)
|
||||
|
||||
if isinstance(response, nio.ProfileGetError):
|
||||
raise MatrixError.from_nio(response)
|
||||
|
||||
self.profile_cache[user_id] = response
|
||||
return response
|
||||
|
||||
|
||||
async def thumbnail(
|
||||
self, server_name: str, media_id: str, width: int, height: int,
|
||||
) -> nio.ThumbnailResponse:
|
||||
|
||||
client = await self._any_client()
|
||||
response = await client.thumbnail(server_name, media_id, width, height)
|
||||
|
||||
if isinstance(response, nio.ThumbnailError):
|
||||
raise MatrixError.from_nio(response)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
async def download(
|
||||
self, server_name: str, media_id: str,
|
||||
) -> nio.DownloadResponse:
|
||||
|
||||
client = await self._any_client()
|
||||
response = await client.download(server_name, media_id)
|
||||
|
||||
if isinstance(response, nio.DownloadError):
|
||||
raise MatrixError.from_nio(response)
|
||||
|
||||
return response
|
||||
|
|
|
@ -80,10 +80,6 @@ class MatrixClient(nio.AsyncClient):
|
|||
|
||||
self.skipped_events: DefaultDict[str, int] = DefaultDict(lambda: 0)
|
||||
|
||||
from .media_cache import MediaCache
|
||||
cache_dir = Path(self.backend.app.appdirs.user_cache_dir)
|
||||
self.media_cache = MediaCache(self, cache_dir)
|
||||
|
||||
from .nio_callbacks import NioCallbacks
|
||||
self.nio_callbacks = NioCallbacks(self)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ from PIL import Image as PILImage
|
|||
|
||||
import nio
|
||||
|
||||
from .matrix_client import MatrixClient
|
||||
from .backend import Backend
|
||||
|
||||
CryptDict = Optional[Dict[str, Any]]
|
||||
Size = Tuple[int, int]
|
||||
|
@ -21,12 +21,6 @@ CONCURRENT_DOWNLOADS_LIMIT = asyncio.BoundedSemaphore(8)
|
|||
ACCESS_LOCKS: DefaultDict[str, asyncio.Lock] = DefaultDict(asyncio.Lock)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DownloadFailed(Exception):
|
||||
message: str = field()
|
||||
http_code: int = field()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Media:
|
||||
cache: "MediaCache" = field()
|
||||
|
@ -85,14 +79,11 @@ class Media:
|
|||
async def _get_remote_data(self) -> bytes:
|
||||
parsed = urlparse(self.mxc)
|
||||
|
||||
resp = await self.cache.client.download(
|
||||
resp = await self.cache.backend.download(
|
||||
server_name = parsed.netloc,
|
||||
media_id = parsed.path.lstrip("/"),
|
||||
)
|
||||
|
||||
if isinstance(resp, nio.DownloadError):
|
||||
raise DownloadFailed(resp.message, resp.status_code)
|
||||
|
||||
return await self._decrypt(resp.body)
|
||||
|
||||
|
||||
|
@ -181,21 +172,18 @@ class Thumbnail(Media):
|
|||
parsed = urlparse(self.mxc)
|
||||
|
||||
if self.crypt_dict:
|
||||
resp = await self.cache.client.download(
|
||||
resp = await self.cache.backend.download(
|
||||
server_name = parsed.netloc,
|
||||
media_id = parsed.path.lstrip("/"),
|
||||
)
|
||||
else:
|
||||
resp = await self.cache.client.thumbnail(
|
||||
resp = await self.cache.backend.thumbnail(
|
||||
server_name = parsed.netloc,
|
||||
media_id = parsed.path.lstrip("/"),
|
||||
width = self.wanted_size[0],
|
||||
height = self.wanted_size[1],
|
||||
)
|
||||
|
||||
if isinstance(resp, (nio.DownloadError, nio.ThumbnailError)):
|
||||
raise DownloadFailed(resp.message, resp.status_code)
|
||||
|
||||
decrypted = await self._decrypt(resp.body)
|
||||
|
||||
with io.BytesIO(decrypted) as img:
|
||||
|
@ -207,7 +195,7 @@ class Thumbnail(Media):
|
|||
|
||||
@dataclass
|
||||
class MediaCache:
|
||||
client: MatrixClient = field()
|
||||
backend: Backend = field()
|
||||
base_dir: Path = field()
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ Rectangle {
|
|||
theme.controls.avatar.background.opacity
|
||||
)
|
||||
|
||||
property string clientUserId
|
||||
property string name
|
||||
property alias mxc: avatarImage.mxc
|
||||
|
||||
|
@ -53,7 +52,6 @@ Rectangle {
|
|||
sourceSize.height: parent.height
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
animate: false
|
||||
clientUserId: avatar.clientUserId
|
||||
|
||||
HoverHandler { id: hoverHandler }
|
||||
|
||||
|
@ -74,7 +72,6 @@ Rectangle {
|
|||
contentItem: HMxcImage {
|
||||
id: avatarToolTipImage
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
clientUserId: avatar.clientUserId
|
||||
mxc: avatarImage.mxc
|
||||
|
||||
sourceSize.width: avatarToolTip.dimension
|
||||
|
|
|
@ -11,6 +11,7 @@ Image {
|
|||
(sourceSize.width + sourceSize.height) <= 512
|
||||
|
||||
|
||||
property bool broken: false
|
||||
property bool animate: true
|
||||
property bool animated: Utils.urlExtension(image.source) === "gif"
|
||||
property alias progressBar: progressBar
|
||||
|
@ -78,7 +79,7 @@ Image {
|
|||
|
||||
HIcon {
|
||||
anchors.centerIn: parent
|
||||
visible: image.status === Image.Error
|
||||
visible: broken || image.status === Image.Error
|
||||
svgName: "broken-image"
|
||||
dimension: Math.max(16, Math.min(parent.width, parent.height) * 0.2)
|
||||
colorize: theme.colors.negativeBackground
|
||||
|
|
|
@ -20,7 +20,6 @@ HImage {
|
|||
}
|
||||
|
||||
|
||||
property string clientUserId
|
||||
property string mxc
|
||||
property string sourceOverride: ""
|
||||
property bool thumbnail: true
|
||||
|
@ -53,12 +52,16 @@ HImage {
|
|||
let args = image.thumbnail ?
|
||||
[image.mxc, w, h, cryptDict] : [image.mxc, cryptDict]
|
||||
|
||||
py.callClientCoro(
|
||||
clientUserId, "media_cache." + method, args, path => {
|
||||
py.callCoro("media_cache." + method, args, path => {
|
||||
if (! image) return
|
||||
if (image.cachedPath != path) image.cachedPath = path
|
||||
show = image.visible
|
||||
}
|
||||
|
||||
image.broken = false
|
||||
image.show = image.visible
|
||||
|
||||
}, () => {
|
||||
image.broken = true
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ Rectangle {
|
|||
|
||||
HUserAvatar {
|
||||
id: bannerAvatar
|
||||
clientUserId: chatPage.userId
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,6 @@ Rectangle {
|
|||
|
||||
HUserAvatar {
|
||||
id: avatar
|
||||
clientUserId: chatPage.userId
|
||||
userId: writingUserId
|
||||
displayName: writingUserInfo.display_name
|
||||
mxc: writingUserInfo.avatar_url
|
||||
|
|
|
@ -23,7 +23,6 @@ Rectangle {
|
|||
|
||||
HRoomAvatar {
|
||||
id: avatar
|
||||
clientUserId: chatPage.userId
|
||||
displayName: chatPage.roomInfo.display_name
|
||||
mxc: chatPage.roomInfo.avatar_url
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
|
|
@ -7,7 +7,6 @@ HTileDelegate {
|
|||
backgroundColor: theme.chat.roomSidePane.member.background
|
||||
|
||||
image: HUserAvatar {
|
||||
clientUserId: chatPage.userId
|
||||
userId: model.user_id
|
||||
displayName: model.display_name
|
||||
mxc: model.avatar_url
|
||||
|
|
|
@ -57,7 +57,6 @@ HRowLayout {
|
|||
|
||||
HUserAvatar {
|
||||
id: avatar
|
||||
clientUserId: chatPage.userId
|
||||
userId: model.sender_id
|
||||
displayName: model.sender_name
|
||||
mxc: model.sender_avatar
|
||||
|
|
|
@ -10,7 +10,6 @@ HMxcImage {
|
|||
horizontalAlignment: Image.AlignLeft
|
||||
animated: loader.singleMediaInfo.media_mime === "image/gif" ||
|
||||
Utils.urlExtension(loader.mediaUrl) === "gif"
|
||||
clientUserId: chatPage.userId
|
||||
thumbnail: ! animated && loader.thumbnailMxc
|
||||
mxc: thumbnail ?
|
||||
(loader.thumbnailMxc || loader.mediaUrl) :
|
||||
|
|
|
@ -58,7 +58,6 @@ HGridLayout {
|
|||
property bool changed: Boolean(sourceOverride)
|
||||
|
||||
id: avatar
|
||||
clientUserId: accountSettings.userId
|
||||
userId: accountSettings.userId
|
||||
displayName: nameField.field.text
|
||||
mxc: accountInfo.avatar_url
|
||||
|
|
|
@ -55,7 +55,6 @@ HBox {
|
|||
|
||||
HRoomAvatar {
|
||||
id: avatar
|
||||
clientUserId: userId
|
||||
displayName: nameField.text
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
|
|
@ -2,8 +2,7 @@ import QtQuick 2.12
|
|||
import "../../Base"
|
||||
|
||||
HUserAvatar {
|
||||
clientUserId: addChatPage.userId
|
||||
userId: clientUserId
|
||||
userId: addChatPage.userId
|
||||
displayName: addChatPage.account ? addChatPage.account.display_name : ""
|
||||
mxc: addChatPage.account ? addChatPage.account.avatar_url : ""
|
||||
}
|
||||
|
|
|
@ -46,7 +46,6 @@ HTileDelegate {
|
|||
|
||||
|
||||
image: HUserAvatar {
|
||||
clientUserId: model.data.user_id
|
||||
userId: model.data.user_id
|
||||
displayName: model.data.display_name
|
||||
mxc: model.data.avatar_url
|
||||
|
|
|
@ -32,7 +32,6 @@ HTileDelegate {
|
|||
|
||||
|
||||
image: HRoomAvatar {
|
||||
clientUserId: model.user_id
|
||||
displayName: model.data.display_name
|
||||
mxc: model.data.avatar_url
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user