Skip to content

Commit 0689111

Browse files
authored
Merge pull request #92 from Makhuta/master
2 parents 4f89180 + 2640edc commit 0689111

File tree

5 files changed

+42
-57
lines changed

5 files changed

+42
-57
lines changed

custom_components/plex_recently_added/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Final
22

33
DOMAIN: Final = "plex_recently_added"
4+
TIMEOUT_MINUTES: Final = 10
45

56

67
DEFAULT_NAME: Final = 'Plex Recently Added'

custom_components/plex_recently_added/coordinator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from homeassistant.core import HomeAssistant
77
from homeassistant.exceptions import ConfigEntryError
88

9-
from .const import DOMAIN
9+
from .const import DOMAIN, TIMEOUT_MINUTES
1010
from .plex_api import (
1111
PlexApi,
1212
FailedToLogin,
@@ -23,7 +23,7 @@ def __init__(self, hass: HomeAssistant, client: PlexApi):
2323
_LOGGER,
2424
name=DOMAIN,
2525
update_method=self._async_update_data,
26-
update_interval=timedelta(minutes=10),
26+
update_interval=timedelta(minutes=TIMEOUT_MINUTES),
2727
)
2828

2929
async def _async_update_data(self) -> Dict[str, Any]:

custom_components/plex_recently_added/parser.py

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
import aiofiles
77
import re
88
import math
9-
from datetime import datetime
9+
from datetime import datetime, timedelta
10+
from homeassistant.core import HomeAssistant
11+
from homeassistant.components.http.auth import async_sign_path
1012

11-
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
12-
CACHE_FOLDER = os.path.join(SCRIPT_DIR, "images_cache")
13-
os.makedirs(CACHE_FOLDER, exist_ok=True)
13+
from .const import TIMEOUT_MINUTES
1414

1515
import logging
1616
_LOGGER = logging.getLogger(__name__)
@@ -30,34 +30,17 @@ def parse_library(root):
3030

3131
return output
3232

33-
def get_image_filename(url):
34-
"""Generate a unique filename from the URL while keeping the original extension."""
35-
parsed_url = urlparse(url)
36-
ext = os.path.splitext(parsed_url.path)[-1]
37-
if not ext:
38-
ext = ".jpg"
39-
return hashlib.md5(url.encode()).hexdigest() + ext
40-
41-
async def download_image(url):
42-
"""Download an image asynchronously and save it to the cache folder without blocking the event loop."""
43-
filename = get_image_filename(url)
44-
file_path = os.path.join(CACHE_FOLDER, filename)
45-
46-
async with aiohttp.ClientSession() as session:
47-
async with session.get(url) as response:
48-
if response.status == 200:
49-
async with aiofiles.open(file_path, "wb") as file: # ✅ Non-blocking file write
50-
await file.write(await response.read()) # ✅ Async file writing
51-
return filename
52-
return None
53-
54-
def cleanup_old_images(valid_filenames):
55-
"""Delete images that are not in the updated list."""
56-
for filename in os.listdir(CACHE_FOLDER):
57-
if filename not in valid_filenames:
58-
os.remove(os.path.join(CACHE_FOLDER, filename))
59-
60-
async def parse_data(data, max, base_url, token, identifier, section_key, images_base_url, is_all = False):
33+
def extract_metadata_and_type(path):
34+
pattern = re.compile(r"/library/metadata/(\d+)/(thumb|art)/(\d+)")
35+
match = pattern.search(path)
36+
37+
if match:
38+
metadata_id = match.group(1)
39+
art_type = match.group(2)
40+
art_id = match.group(3)
41+
return metadata_id, art_type, art_id
42+
43+
def parse_data(hass: HomeAssistant, data, max, base_url, token, identifier, section_key, images_base_url, is_all = False):
6144
if is_all:
6245
sorted_data = []
6346
for k in data.keys():
@@ -109,16 +92,12 @@ async def parse_data(data, max, base_url, token, identifier, section_key, images
10992
data_output['trailer'] = item.get('trailer')
11093

11194

112-
thumb_filename = await download_image(f'{base_url}{thumb}?X-Plex-Token={token}')
113-
if thumb_filename:
114-
valid_images.add(thumb_filename)
115-
data_output["poster"] = (f'{images_base_url}?filename={thumb_filename}') if thumb_filename else ""
95+
thumb_IDs = extract_metadata_and_type(thumb)
96+
data_output["poster"] = async_sign_path(hass, f'{images_base_url}?metadata={thumb_IDs[0]}&thumb={thumb_IDs[2]}', timedelta(minutes=TIMEOUT_MINUTES)) if thumb_IDs else ""
11697

11798

118-
art_filename = await download_image(f'{base_url}{art}?X-Plex-Token={token}')
119-
if art_filename:
120-
valid_images.add(art_filename)
121-
data_output["fanart"] = (f'{images_base_url}?filename={art_filename}') if art_filename else ""
99+
art_IDs = extract_metadata_and_type(art)
100+
data_output["fanart"] = async_sign_path(hass, f'{images_base_url}?metadata={art_IDs[0]}&art={art_IDs[2]}', timedelta(minutes=TIMEOUT_MINUTES)) if art_IDs else ""
122101

123102

124103
data_output["deep_link"] = deep_link if identifier else None

custom_components/plex_recently_added/plex_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ async def update(self):
129129

130130
data_out = {}
131131
for k in data.keys():
132-
parsed_data = await parse_data(data[k], self._max, info_url, self._token, identifier, k, self._images_base_url, k == "all")
132+
parsed_data = parse_data(self._hass, data[k], self._max, info_url, self._token, identifier, k, self._images_base_url, k == "all")
133133

134134
# Ensure trailer URLs are correctly set for the "all" sensor
135135
if k == "all":

custom_components/plex_recently_added/redirect.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
import requests
55
import os
66

7-
from .parser import CACHE_FOLDER
8-
os.makedirs(CACHE_FOLDER, exist_ok=True)
9-
107
from homeassistant.const import (
118
CONF_API_KEY,
129
CONF_NAME,
@@ -18,23 +15,31 @@
1815
from .const import DOMAIN
1916

2017
class ImagesRedirect(HomeAssistantView):
21-
requires_auth = False
22-
2318
def __init__(self, config_entry: ConfigEntry):
2419
super().__init__()
2520
self._token = config_entry.data[CONF_API_KEY]
21+
self._base_url = f'http{'s' if config_entry.data[CONF_SSL] else ''}://{config_entry.data[CONF_HOST]}:{config_entry.data[CONF_PORT]}'
2622
self.name = f'{self._token}_Plex_Recently_Added'
2723
self.url = f'/{config_entry.data[CONF_NAME].lower() + "_" if len(config_entry.data[CONF_NAME]) > 0 else ""}plex_recently_added'
2824

2925
async def get(self, request):
30-
filename = request.query.get("filename")
31-
if not filename:
32-
return web.Response(status=404)
33-
file_path = os.path.join(CACHE_FOLDER, filename)
34-
35-
if not os.path.isfile(file_path):
36-
return web.Response(status=404)
37-
38-
return web.FileResponse(file_path)
26+
metadataId = int(request.query.get("metadata", 0))
27+
thumbId = int(request.query.get("thumb", 0))
28+
artId = int(request.query.get("art", 0))
29+
30+
if metadataId == 0:
31+
return web.HTTPNotFound()
32+
image_type = "thumb" if thumbId != 0 else "art"
33+
image_id = thumbId if thumbId != 0 else artId if artId != 0 else ""
34+
35+
url = f'{self._base_url}/library/metadata/{metadataId}/{image_type}/{image_id}?X-Plex-Token={self._token}'
36+
37+
async with ClientSession() as session:
38+
async with session.get(url) as res:
39+
if res.ok:
40+
content = await res.read()
41+
return web.Response(body=content, content_type=res.content_type)
42+
43+
return web.HTTPNotFound()
3944

4045

0 commit comments

Comments
 (0)