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
|
- 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ Rectangle {
|
||||||
|
|
||||||
HUserAvatar {
|
HUserAvatar {
|
||||||
id: bannerAvatar
|
id: bannerAvatar
|
||||||
clientUserId: chatPage.userId
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) :
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 : ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user