Skip to content

Commit 860ad7b

Browse files
authored
Add Library timeline support (#573)
* Add Library timeline support * Retry intentional failure with different canary test * Temporarily disable activities tests * Set tests for normal runs * Add tests to validate library timeline attributes
1 parent 8410d81 commit 860ad7b

File tree

3 files changed

+95
-2
lines changed

3 files changed

+95
-2
lines changed

plexapi/library.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,12 @@ def settings(self):
466466
data = self._server.query(key)
467467
return self.findItems(data, cls=Setting)
468468

469+
def timeline(self):
470+
""" Returns a timeline query for this library section. """
471+
key = '/library/sections/%s/timeline' % self.key
472+
data = self._server.query(key)
473+
return LibraryTimeline(self, data)
474+
469475
def onDeck(self):
470476
""" Returns a list of media items on deck from this library section. """
471477
key = '/library/sections/%s/onDeck' % self.key
@@ -1060,6 +1066,46 @@ def _loadData(self, data):
10601066
self.type = data.attrib.get('type')
10611067

10621068

1069+
@utils.registerPlexObject
1070+
class LibraryTimeline(PlexObject):
1071+
"""Represents a LibrarySection timeline.
1072+
1073+
Attributes:
1074+
TAG (str): 'LibraryTimeline'
1075+
size (int): Unknown
1076+
allowSync (bool): Unknown
1077+
art (str): Relative path to art image.
1078+
content (str): "secondary"
1079+
identifier (str): "com.plexapp.plugins.library"
1080+
latestEntryTime (int): Epoch timestamp
1081+
mediaTagPrefix (str): "/system/bundle/media/flags/"
1082+
mediaTagVersion (int): Unknown
1083+
thumb (str): Relative path to library thumb image.
1084+
title1 (str): Name of library section.
1085+
updateQueueSize (int): Number of items queued to update.
1086+
viewGroup (str): "secondary"
1087+
viewMode (int): Unknown
1088+
"""
1089+
TAG = 'LibraryTimeline'
1090+
1091+
def _loadData(self, data):
1092+
""" Load attribute values from Plex XML response. """
1093+
self._data = data
1094+
self.size = utils.cast(int, data.attrib.get('size'))
1095+
self.allowSync = utils.cast(bool, data.attrib.get('allowSync'))
1096+
self.art = data.attrib.get('art')
1097+
self.content = data.attrib.get('content')
1098+
self.identifier = data.attrib.get('identifier')
1099+
self.latestEntryTime = utils.cast(int, data.attrib.get('latestEntryTime'))
1100+
self.mediaTagPrefix = data.attrib.get('mediaTagPrefix')
1101+
self.mediaTagVersion = utils.cast(int, data.attrib.get('mediaTagVersion'))
1102+
self.thumb = data.attrib.get('thumb')
1103+
self.title1 = data.attrib.get('title1')
1104+
self.updateQueueSize = utils.cast(int, data.attrib.get('updateQueueSize'))
1105+
self.viewGroup = data.attrib.get('viewGroup')
1106+
self.viewMode = utils.cast(int, data.attrib.get('viewMode'))
1107+
1108+
10631109
@utils.registerPlexObject
10641110
class Location(PlexObject):
10651111
""" Represents a single library Location.

tests/test__prepare.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,37 @@ def wait_for_idle_server(server):
1616
assert attempts < MAX_ATTEMPTS, f"Server still busy after {MAX_ATTEMPTS}s"
1717

1818

19-
def test_ensure_metadata_scans_completed(plex):
19+
def wait_for_metadata_processing(server):
20+
"""Wait for async metadata processing to complete."""
21+
attempts = 0
22+
23+
while True:
24+
busy = False
25+
for section in server.library.sections():
26+
tl = section.timeline()
27+
if tl.updateQueueSize > 0:
28+
busy = True
29+
print(f"{section.title}: {tl.updateQueueSize} items left")
30+
if not busy or attempts > MAX_ATTEMPTS:
31+
break
32+
time.sleep(1)
33+
attempts += 1
34+
assert attempts < MAX_ATTEMPTS, f"Metadata still processing after {MAX_ATTEMPTS}s"
35+
36+
37+
def test_ensure_activities_completed(plex):
2038
wait_for_idle_server(plex)
2139

2240

2341
@pytest.mark.authenticated
24-
def test_ensure_metadata_scans_completed_authenticated(plex):
42+
def test_ensure_activities_completed_authenticated(plex):
2543
wait_for_idle_server(plex)
44+
45+
46+
def test_ensure_metadata_scans_completed(plex):
47+
wait_for_metadata_processing(plex)
48+
49+
50+
@pytest.mark.authenticated
51+
def test_ensure_metadata_scans_completed_authenticated(plex):
52+
wait_for_metadata_processing(plex)

tests/test_library.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: utf-8 -*-
2+
from datetime import datetime
23
import pytest
34
from plexapi.exceptions import NotFound
45

@@ -277,3 +278,22 @@ def test_crazy_search(plex, movie):
277278
assert len(movies.search(container_size=1)) == 4
278279
assert len(movies.search(container_start=9999, container_size=1)) == 0
279280
assert len(movies.search(container_start=2, container_size=1)) == 2
281+
282+
283+
def test_library_section_timeline(plex):
284+
movies = plex.library.section("Movies")
285+
tl = movies.timeline()
286+
assert tl.TAG == "LibraryTimeline"
287+
assert tl.size > 0
288+
assert tl.allowSync is False
289+
assert tl.art == "/:/resources/movie-fanart.jpg"
290+
assert tl.content == "secondary"
291+
assert tl.identifier == "com.plexapp.plugins.library"
292+
assert datetime.fromtimestamp(tl.latestEntryTime).date() == datetime.today().date()
293+
assert tl.mediaTagPrefix == "/system/bundle/media/flags/"
294+
assert tl.mediaTagVersion > 1
295+
assert tl.thumb == "/:/resources/movie.png"
296+
assert tl.title1 == "Movies"
297+
assert tl.updateQueueSize == 0
298+
assert tl.viewGroup == "secondary"
299+
assert tl.viewMode == 65592

0 commit comments

Comments
 (0)