Allow shared Synology DSM Photo albums shown in media browser (#123613)

This commit is contained in:
Michael 2024-08-15 18:18:05 +02:00 committed by GitHub
parent 874ae15d6a
commit e39bfeac08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 65 additions and 34 deletions

View file

@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
"iot_class": "local_polling",
"loggers": ["synology_dsm"],
"requirements": ["py-synologydsm-api==2.4.5"],
"requirements": ["py-synologydsm-api==2.5.2"],
"ssdp": [
{
"manufacturer": "Synology",

View file

@ -46,18 +46,24 @@ class SynologyPhotosMediaSourceIdentifier:
self.cache_key = None
self.file_name = None
self.is_shared = False
self.passphrase = ""
if parts:
self.unique_id = parts[0]
if len(parts) > 1:
self.album_id = parts[1]
if len(parts) > 2:
self.cache_key = parts[2]
if len(parts) > 3:
self.file_name = parts[3]
if self.file_name.endswith(SHARED_SUFFIX):
self.is_shared = True
self.file_name = self.file_name.removesuffix(SHARED_SUFFIX)
self.unique_id = parts[0]
if len(parts) > 1:
album_parts = parts[1].split("_")
self.album_id = album_parts[0]
if len(album_parts) > 1:
self.passphrase = parts[1].replace(f"{self.album_id}_", "")
if len(parts) > 2:
self.cache_key = parts[2]
if len(parts) > 3:
self.file_name = parts[3]
if self.file_name.endswith(SHARED_SUFFIX):
self.is_shared = True
self.file_name = self.file_name.removesuffix(SHARED_SUFFIX)
class SynologyPhotosMediaSource(MediaSource):
@ -135,7 +141,7 @@ class SynologyPhotosMediaSource(MediaSource):
ret.extend(
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{item.identifier}/{album.album_id}",
identifier=f"{item.identifier}/{album.album_id}_{album.passphrase}",
media_class=MediaClass.DIRECTORY,
media_content_type=MediaClass.IMAGE,
title=album.name,
@ -149,7 +155,7 @@ class SynologyPhotosMediaSource(MediaSource):
# Request items of album
# Get Items
album = SynoPhotosAlbum(int(identifier.album_id), "", 0)
album = SynoPhotosAlbum(int(identifier.album_id), "", 0, identifier.passphrase)
try:
album_items = await diskstation.api.photos.get_items_from_album(
album, 0, 1000
@ -170,7 +176,12 @@ class SynologyPhotosMediaSource(MediaSource):
ret.append(
BrowseMediaSource(
domain=DOMAIN,
identifier=f"{identifier.unique_id}/{identifier.album_id}/{album_item.thumbnail_cache_key}/{album_item.file_name}{suffix}",
identifier=(
f"{identifier.unique_id}/"
f"{identifier.album_id}_{identifier.passphrase}/"
f"{album_item.thumbnail_cache_key}/"
f"{album_item.file_name}{suffix}"
),
media_class=MediaClass.IMAGE,
media_content_type=mime_type,
title=album_item.file_name,
@ -197,7 +208,12 @@ class SynologyPhotosMediaSource(MediaSource):
if identifier.is_shared:
suffix = SHARED_SUFFIX
return PlayMedia(
f"/synology_dsm/{identifier.unique_id}/{identifier.cache_key}/{identifier.file_name}{suffix}",
(
f"/synology_dsm/{identifier.unique_id}/"
f"{identifier.cache_key}/"
f"{identifier.file_name}{suffix}/"
f"{identifier.passphrase}"
),
mime_type,
)
@ -231,18 +247,24 @@ class SynologyDsmMediaView(http.HomeAssistantView):
if not self.hass.data.get(DOMAIN):
raise web.HTTPNotFound
# location: {cache_key}/{filename}
cache_key, file_name = location.split("/")
cache_key, file_name, passphrase = location.split("/")
image_id = int(cache_key.split("_")[0])
if shared := file_name.endswith(SHARED_SUFFIX):
file_name = file_name.removesuffix(SHARED_SUFFIX)
mime_type, _ = mimetypes.guess_type(file_name)
if not isinstance(mime_type, str):
raise web.HTTPNotFound
diskstation: SynologyDSMData = self.hass.data[DOMAIN][source_dir_id]
assert diskstation.api.photos is not None
item = SynoPhotosItem(image_id, "", "", "", cache_key, "", shared)
item = SynoPhotosItem(image_id, "", "", "", cache_key, "xl", shared, passphrase)
try:
image = await diskstation.api.photos.download_item(item)
if passphrase:
image = await diskstation.api.photos.download_item_thumbnail(item)
else:
image = await diskstation.api.photos.download_item(item)
except SynologyDSMException as exc:
raise web.HTTPNotFound from exc
return web.Response(body=image, content_type=mime_type)

View file

@ -1662,7 +1662,7 @@ py-schluter==0.1.7
py-sucks==0.9.10
# homeassistant.components.synology_dsm
py-synologydsm-api==2.4.5
py-synologydsm-api==2.5.2
# homeassistant.components.zabbix
py-zabbix==1.1.7

View file

@ -1354,7 +1354,7 @@ py-nightscout==1.2.2
py-sucks==0.9.10
# homeassistant.components.synology_dsm
py-synologydsm-api==2.4.5
py-synologydsm-api==2.5.2
# homeassistant.components.hdmi_cec
pyCEC==0.5.2

View file

@ -48,11 +48,15 @@ def dsm_with_photos() -> MagicMock:
dsm.surveillance_station.update = AsyncMock(return_value=True)
dsm.upgrade.update = AsyncMock(return_value=True)
dsm.photos.get_albums = AsyncMock(return_value=[SynoPhotosAlbum(1, "Album 1", 10)])
dsm.photos.get_albums = AsyncMock(
return_value=[SynoPhotosAlbum(1, "Album 1", 10, "")]
)
dsm.photos.get_items_from_album = AsyncMock(
return_value=[
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", False),
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", True),
SynoPhotosItem(
10, "", "filename.jpg", 12345, "10_1298753", "sm", False, ""
),
SynoPhotosItem(10, "", "filename.jpg", 12345, "10_1298753", "sm", True, ""),
]
)
dsm.photos.get_item_thumbnail_url = AsyncMock(
@ -96,17 +100,22 @@ async def test_resolve_media_bad_identifier(
[
(
"ABC012345/10/27643_876876/filename.jpg",
"/synology_dsm/ABC012345/27643_876876/filename.jpg",
"/synology_dsm/ABC012345/27643_876876/filename.jpg/",
"image/jpeg",
),
(
"ABC012345/12/12631_47189/filename.png",
"/synology_dsm/ABC012345/12631_47189/filename.png",
"/synology_dsm/ABC012345/12631_47189/filename.png/",
"image/png",
),
(
"ABC012345/12/12631_47189/filename.png_shared",
"/synology_dsm/ABC012345/12631_47189/filename.png_shared",
"/synology_dsm/ABC012345/12631_47189/filename.png_shared/",
"image/png",
),
(
"ABC012345/12_dmypass/12631_47189/filename.png",
"/synology_dsm/ABC012345/12631_47189/filename.png/dmypass",
"image/png",
),
],
@ -250,7 +259,7 @@ async def test_browse_media_get_albums(
assert result.children[0].identifier == "mocked_syno_dsm_entry/0"
assert result.children[0].title == "All images"
assert isinstance(result.children[1], BrowseMedia)
assert result.children[1].identifier == "mocked_syno_dsm_entry/1"
assert result.children[1].identifier == "mocked_syno_dsm_entry/1_"
assert result.children[1].title == "Album 1"
@ -382,7 +391,7 @@ async def test_browse_media_get_items(
assert len(result.children) == 2
item = result.children[0]
assert isinstance(item, BrowseMedia)
assert item.identifier == "mocked_syno_dsm_entry/1/10_1298753/filename.jpg"
assert item.identifier == "mocked_syno_dsm_entry/1_/10_1298753/filename.jpg"
assert item.title == "filename.jpg"
assert item.media_class == MediaClass.IMAGE
assert item.media_content_type == "image/jpeg"
@ -391,7 +400,7 @@ async def test_browse_media_get_items(
assert item.thumbnail == "http://my.thumbnail.url"
item = result.children[1]
assert isinstance(item, BrowseMedia)
assert item.identifier == "mocked_syno_dsm_entry/1/10_1298753/filename.jpg_shared"
assert item.identifier == "mocked_syno_dsm_entry/1_/10_1298753/filename.jpg_shared"
assert item.title == "filename.jpg"
assert item.media_class == MediaClass.IMAGE
assert item.media_content_type == "image/jpeg"
@ -435,24 +444,24 @@ async def test_media_view(
assert await hass.config_entries.async_setup(entry.entry_id)
with pytest.raises(web.HTTPNotFound):
await view.get(request, "", "10_1298753/filename")
await view.get(request, "", "10_1298753/filename/")
# exception in download_item()
dsm_with_photos.photos.download_item = AsyncMock(
side_effect=SynologyDSMException("", None)
)
with pytest.raises(web.HTTPNotFound):
await view.get(request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg")
await view.get(request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg/")
# success
dsm_with_photos.photos.download_item = AsyncMock(return_value=b"xxxx")
with patch.object(tempfile, "tempdir", tmp_path):
result = await view.get(
request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg"
request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg/"
)
assert isinstance(result, web.Response)
with patch.object(tempfile, "tempdir", tmp_path):
result = await view.get(
request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg_shared"
request, "mocked_syno_dsm_entry", "10_1298753/filename.jpg_shared/"
)
assert isinstance(result, web.Response)