download/thumbnail don't need authentification

This commit is contained in:
miruka 2019-11-12 09:10:00 -04:00
parent 4cc2ebf6e3
commit 73541ad7a5
18 changed files with 74 additions and 65 deletions

View File

@ -4,6 +4,7 @@
- Handle upload errors: non existent path, path is a dir, file too big, etc - 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 real progression for mxc thumbnail loadings, uploads and downloads
- Show reason under broken thumbnail icons
- Support m.file thumbnails - Support m.file thumbnails
- Generate video thumbnails - Generate video thumbnails
- GIFs can use the video player - GIFs can use the video player

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
import logging as log import logging as log
from pathlib import Path
from typing import Any, DefaultDict, Dict, List, Optional, Tuple, Union from typing import Any, DefaultDict, Dict, List, Optional, Tuple, Union
import hsluv import hsluv
@ -38,6 +39,10 @@ class Backend:
self.get_profile_locks: DefaultDict[str, asyncio.Lock] = \ self.get_profile_locks: DefaultDict[str, asyncio.Lock] = \
DefaultDict(asyncio.Lock) # {user_id: 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: def __repr__(self) -> str:
return f"{type(self).__name__}(clients={self.clients!r})" return f"{type(self).__name__}(clients={self.clients!r})"
@ -168,28 +173,6 @@ class Backend:
return (settings, ui_state, theme) 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]]: async def get_flat_sidepane_data(self) -> List[Dict[str, Any]]:
data = [] data = []
@ -210,3 +193,54 @@ class Backend:
}) })
return data 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

View File

@ -80,10 +80,6 @@ class MatrixClient(nio.AsyncClient):
self.skipped_events: DefaultDict[str, int] = DefaultDict(lambda: 0) 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 from .nio_callbacks import NioCallbacks
self.nio_callbacks = NioCallbacks(self) self.nio_callbacks = NioCallbacks(self)

View File

@ -12,7 +12,7 @@ from PIL import Image as PILImage
import nio import nio
from .matrix_client import MatrixClient from .backend import Backend
CryptDict = Optional[Dict[str, Any]] CryptDict = Optional[Dict[str, Any]]
Size = Tuple[int, int] Size = Tuple[int, int]
@ -21,12 +21,6 @@ CONCURRENT_DOWNLOADS_LIMIT = asyncio.BoundedSemaphore(8)
ACCESS_LOCKS: DefaultDict[str, asyncio.Lock] = DefaultDict(asyncio.Lock) ACCESS_LOCKS: DefaultDict[str, asyncio.Lock] = DefaultDict(asyncio.Lock)
@dataclass
class DownloadFailed(Exception):
message: str = field()
http_code: int = field()
@dataclass @dataclass
class Media: class Media:
cache: "MediaCache" = field() cache: "MediaCache" = field()
@ -85,14 +79,11 @@ class Media:
async def _get_remote_data(self) -> bytes: async def _get_remote_data(self) -> bytes:
parsed = urlparse(self.mxc) parsed = urlparse(self.mxc)
resp = await self.cache.client.download( resp = await self.cache.backend.download(
server_name = parsed.netloc, server_name = parsed.netloc,
media_id = parsed.path.lstrip("/"), media_id = parsed.path.lstrip("/"),
) )
if isinstance(resp, nio.DownloadError):
raise DownloadFailed(resp.message, resp.status_code)
return await self._decrypt(resp.body) return await self._decrypt(resp.body)
@ -181,21 +172,18 @@ class Thumbnail(Media):
parsed = urlparse(self.mxc) parsed = urlparse(self.mxc)
if self.crypt_dict: if self.crypt_dict:
resp = await self.cache.client.download( resp = await self.cache.backend.download(
server_name = parsed.netloc, server_name = parsed.netloc,
media_id = parsed.path.lstrip("/"), media_id = parsed.path.lstrip("/"),
) )
else: else:
resp = await self.cache.client.thumbnail( resp = await self.cache.backend.thumbnail(
server_name = parsed.netloc, server_name = parsed.netloc,
media_id = parsed.path.lstrip("/"), media_id = parsed.path.lstrip("/"),
width = self.wanted_size[0], width = self.wanted_size[0],
height = self.wanted_size[1], 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) decrypted = await self._decrypt(resp.body)
with io.BytesIO(decrypted) as img: with io.BytesIO(decrypted) as img:
@ -207,7 +195,7 @@ class Thumbnail(Media):
@dataclass @dataclass
class MediaCache: class MediaCache:
client: MatrixClient = field() backend: Backend = field()
base_dir: Path = field() base_dir: Path = field()

View File

@ -15,7 +15,6 @@ Rectangle {
theme.controls.avatar.background.opacity theme.controls.avatar.background.opacity
) )
property string clientUserId
property string name property string name
property alias mxc: avatarImage.mxc property alias mxc: avatarImage.mxc
@ -53,7 +52,6 @@ Rectangle {
sourceSize.height: parent.height sourceSize.height: parent.height
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
animate: false animate: false
clientUserId: avatar.clientUserId
HoverHandler { id: hoverHandler } HoverHandler { id: hoverHandler }
@ -74,7 +72,6 @@ Rectangle {
contentItem: HMxcImage { contentItem: HMxcImage {
id: avatarToolTipImage id: avatarToolTipImage
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
clientUserId: avatar.clientUserId
mxc: avatarImage.mxc mxc: avatarImage.mxc
sourceSize.width: avatarToolTip.dimension sourceSize.width: avatarToolTip.dimension

View File

@ -11,6 +11,7 @@ Image {
(sourceSize.width + sourceSize.height) <= 512 (sourceSize.width + sourceSize.height) <= 512
property bool broken: false
property bool animate: true property bool animate: true
property bool animated: Utils.urlExtension(image.source) === "gif" property bool animated: Utils.urlExtension(image.source) === "gif"
property alias progressBar: progressBar property alias progressBar: progressBar
@ -78,7 +79,7 @@ Image {
HIcon { HIcon {
anchors.centerIn: parent anchors.centerIn: parent
visible: image.status === Image.Error visible: broken || image.status === Image.Error
svgName: "broken-image" svgName: "broken-image"
dimension: Math.max(16, Math.min(parent.width, parent.height) * 0.2) dimension: Math.max(16, Math.min(parent.width, parent.height) * 0.2)
colorize: theme.colors.negativeBackground colorize: theme.colors.negativeBackground

View File

@ -20,7 +20,6 @@ HImage {
} }
property string clientUserId
property string mxc property string mxc
property string sourceOverride: "" property string sourceOverride: ""
property bool thumbnail: true property bool thumbnail: true
@ -53,12 +52,16 @@ HImage {
let args = image.thumbnail ? let args = image.thumbnail ?
[image.mxc, w, h, cryptDict] : [image.mxc, cryptDict] [image.mxc, w, h, cryptDict] : [image.mxc, cryptDict]
py.callClientCoro( py.callCoro("media_cache." + method, args, path => {
clientUserId, "media_cache." + method, args, path => {
if (! image) return if (! image) return
if (image.cachedPath != path) image.cachedPath = path if (image.cachedPath != path) image.cachedPath = path
show = image.visible
} image.broken = false
image.show = image.visible
}, () => {
image.broken = true
},
) )
} }
} }

View File

@ -36,7 +36,6 @@ Rectangle {
HUserAvatar { HUserAvatar {
id: bannerAvatar id: bannerAvatar
clientUserId: chatPage.userId
anchors.centerIn: parent anchors.centerIn: parent
} }
} }

View File

@ -58,7 +58,6 @@ Rectangle {
HUserAvatar { HUserAvatar {
id: avatar id: avatar
clientUserId: chatPage.userId
userId: writingUserId userId: writingUserId
displayName: writingUserInfo.display_name displayName: writingUserInfo.display_name
mxc: writingUserInfo.avatar_url mxc: writingUserInfo.avatar_url

View File

@ -23,7 +23,6 @@ Rectangle {
HRoomAvatar { HRoomAvatar {
id: avatar id: avatar
clientUserId: chatPage.userId
displayName: chatPage.roomInfo.display_name displayName: chatPage.roomInfo.display_name
mxc: chatPage.roomInfo.avatar_url mxc: chatPage.roomInfo.avatar_url
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop

View File

@ -7,7 +7,6 @@ HTileDelegate {
backgroundColor: theme.chat.roomSidePane.member.background backgroundColor: theme.chat.roomSidePane.member.background
image: HUserAvatar { image: HUserAvatar {
clientUserId: chatPage.userId
userId: model.user_id userId: model.user_id
displayName: model.display_name displayName: model.display_name
mxc: model.avatar_url mxc: model.avatar_url

View File

@ -57,7 +57,6 @@ HRowLayout {
HUserAvatar { HUserAvatar {
id: avatar id: avatar
clientUserId: chatPage.userId
userId: model.sender_id userId: model.sender_id
displayName: model.sender_name displayName: model.sender_name
mxc: model.sender_avatar mxc: model.sender_avatar

View File

@ -10,7 +10,6 @@ HMxcImage {
horizontalAlignment: Image.AlignLeft horizontalAlignment: Image.AlignLeft
animated: loader.singleMediaInfo.media_mime === "image/gif" || animated: loader.singleMediaInfo.media_mime === "image/gif" ||
Utils.urlExtension(loader.mediaUrl) === "gif" Utils.urlExtension(loader.mediaUrl) === "gif"
clientUserId: chatPage.userId
thumbnail: ! animated && loader.thumbnailMxc thumbnail: ! animated && loader.thumbnailMxc
mxc: thumbnail ? mxc: thumbnail ?
(loader.thumbnailMxc || loader.mediaUrl) : (loader.thumbnailMxc || loader.mediaUrl) :

View File

@ -58,7 +58,6 @@ HGridLayout {
property bool changed: Boolean(sourceOverride) property bool changed: Boolean(sourceOverride)
id: avatar id: avatar
clientUserId: accountSettings.userId
userId: accountSettings.userId userId: accountSettings.userId
displayName: nameField.field.text displayName: nameField.field.text
mxc: accountInfo.avatar_url mxc: accountInfo.avatar_url

View File

@ -55,7 +55,6 @@ HBox {
HRoomAvatar { HRoomAvatar {
id: avatar id: avatar
clientUserId: userId
displayName: nameField.text displayName: nameField.text
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter

View File

@ -2,8 +2,7 @@ import QtQuick 2.12
import "../../Base" import "../../Base"
HUserAvatar { HUserAvatar {
clientUserId: addChatPage.userId userId: addChatPage.userId
userId: clientUserId
displayName: addChatPage.account ? addChatPage.account.display_name : "" displayName: addChatPage.account ? addChatPage.account.display_name : ""
mxc: addChatPage.account ? addChatPage.account.avatar_url : "" mxc: addChatPage.account ? addChatPage.account.avatar_url : ""
} }

View File

@ -46,7 +46,6 @@ HTileDelegate {
image: HUserAvatar { image: HUserAvatar {
clientUserId: model.data.user_id
userId: model.data.user_id userId: model.data.user_id
displayName: model.data.display_name displayName: model.data.display_name
mxc: model.data.avatar_url mxc: model.data.avatar_url

View File

@ -32,7 +32,6 @@ HTileDelegate {
image: HRoomAvatar { image: HRoomAvatar {
clientUserId: model.user_id
displayName: model.data.display_name displayName: model.data.display_name
mxc: model.data.avatar_url mxc: model.data.avatar_url
} }