Skip to content

Commit

Permalink
Merge pull request #1578 from glensc/episodes
Browse files Browse the repository at this point in the history
Refactor: Implement shows sync using episodes
  • Loading branch information
glensc authored Jan 13, 2024
2 parents 656dd5e + dd7b491 commit f663ac6
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 14 deletions.
10 changes: 7 additions & 3 deletions plextraktsync/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from requests import RequestException
from rich.markup import escape
from trakt.errors import TraktException
from trakt.tv import TVShow

from plextraktsync.factory import logger
from plextraktsync.trakt.TraktLookup import TraktLookup
Expand Down Expand Up @@ -89,9 +90,12 @@ def trakt_url(self):
@property
def show(self) -> Media | None:
if self._show is None and self.mf and not self.plex.is_discover:
# TODO: fetch show for discover items
ps = self.plex_api.fetch_item(self.plex.item.grandparentRatingKey)
ms = self.mf.resolve_any(ps)
ps = self.plex.show
if isinstance(self.trakt.show, TVShow):
ts = self.trakt.show
ms = self.mf.make_media(trakt=ts, plex=ps)
else:
ms = self.mf.resolve_any(ps)
self._show = ms

return self._show
Expand Down
37 changes: 33 additions & 4 deletions plextraktsync/plan/Walker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import TYPE_CHECKING

from plextraktsync.decorators.measure_time import measure_time
from plextraktsync.factory import logging
from plextraktsync.mixin.SetWindowTitle import SetWindowTitle
from plextraktsync.plex.PlexGuid import PlexGuid
from plextraktsync.plex.PlexLibraryItem import PlexLibraryItem
Expand Down Expand Up @@ -39,6 +40,7 @@ def __init__(
self.trakt = trakt
self.mf = mf
self.config = config
self.logger = logging.getLogger("PlexTraktSync.Walker")

@cached_property
def plan(self):
Expand Down Expand Up @@ -100,11 +102,26 @@ def find_episodes(self):
if self.plan.episodes:
yield from self.get_plex_episodes(self.plan.episodes)

for ps in self.get_plex_shows():
show = self.mf.resolve_any(ps)
if not show:
# Preload plex shows
plex_shows = {}

self.logger.info("Preload shows data")
for show in self.get_plex_shows():
plex_shows[show.key] = show
self.logger.info(f"Preloaded shows data ({len(plex_shows)} shows)")

show_cache = {}
for ep in self.episodes_from_sections(self.plan.show_sections):
show_id = ep.show_id
ep.show = plex_shows[show_id]
show = show_cache[show_id] if show_id in show_cache else None
m = self.mf.resolve_any(ep, show)
if not m:
continue
yield from self.episode_from_show(show)
if show:
m.show = show
show_cache[show_id] = m.show
yield m

def walk_shows(self, shows: set[Media], title="Processing Shows"):
if not shows:
Expand Down Expand Up @@ -137,6 +154,18 @@ def media_from_sections(self, sections: list[PlexLibrarySection]) -> Generator[P
)
yield from it

def episodes_from_sections(self, sections: list[PlexLibrarySection]) -> Generator[PlexLibraryItem, Any, None]:
for section in sections:
with measure_time(f"{section.title_link} processed", extra={"markup": True}):
self.set_window_title(f"Processing {section.title}")
items = section.search_episodes()
it = self.progressbar(
items,
total=len(items),
desc=f"Processing {section.title_link}",
)
yield from it

def media_from_items(self, libtype: str, items: list) -> Generator[PlexLibraryItem, Any, None]:
it = self.progressbar(items, desc=f"Processing {libtype}s")
for m in it:
Expand Down
25 changes: 25 additions & 0 deletions plextraktsync/plex/PlexLibraryItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class PlexLibraryItem:
def __init__(self, item: PlexMedia, plex: PlexApi = None):
self.item = item
self.plex = plex
self._show = None

@property
def key(self):
return self.item.ratingKey

@property
def is_legacy_agent(self):
Expand Down Expand Up @@ -312,6 +317,26 @@ def season_number(self):
def episode_number(self):
return self.item.index

@property
def show_id(self):
if self.type != "episode":
raise RuntimeError("show_id is valid for episodes only")
return self.item.grandparentRatingKey

@property
def show(self):
if self._show is None:
self._show = self.plex.fetch_item(self.show_id)

return self._show

@show.setter
def show(self, show):
if self.type != "episode":
raise RuntimeError("show_id is valid for episodes only")

self._show = show

@staticmethod
def date_value(date):
if not date:
Expand Down
9 changes: 9 additions & 0 deletions plextraktsync/plex/PlexLibrarySection.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ def find_by_id(self, id: str | int) -> PlexMedia | None:
except NotFound:
return None

def search_episodes(self):
if self.section.type == "show":
from plextraktsync.plex.PlexShowSectionPager import \
PlexShowSectionPager

return PlexShowSectionPager(section=self.section, plex=self.plex)

return None

def all(self, max_items: int):
libtype = self.section.TYPE
key = self.section._buildSearchKey(libtype=libtype, returnKwargs=False)
Expand Down
44 changes: 44 additions & 0 deletions plextraktsync/plex/PlexShowSectionPager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from __future__ import annotations

from functools import cached_property
from typing import TYPE_CHECKING

from plextraktsync.plex.PlexLibraryItem import PlexLibraryItem

if TYPE_CHECKING:
from plexapi.library import ShowSection

from plextraktsync.plex.PlexApi import PlexApi


class PlexShowSectionPager:
def __init__(self, section: ShowSection, plex: PlexApi):
self.section = section
self.plex = plex

def __len__(self):
return self.total_size

@cached_property
def total_size(self):
return self.section.totalViewSize(libtype="episode")

def __iter__(self):
from plexapi import X_PLEX_CONTAINER_SIZE

max_items = self.total_size
start = 0
size = X_PLEX_CONTAINER_SIZE

while True:
items = self.section.searchEpisodes(container_start=start, container_size=size, maxresults=size)

if not len(items):
break

for ep in items:
yield PlexLibraryItem(ep, plex=self.plex)

start += size
if start > max_items:
break
23 changes: 16 additions & 7 deletions plextraktsync/trakt/TraktApi.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,15 +211,24 @@ def remove_from_watchlist(self, m):

self.queue.remove_from_watchlist((m.media_type, item))

def find_by_episode_guid(self, guid: PlexGuid):
ts: TVShow = self.search_by_id(guid.show_id, id_type=guid.provider, media_type="show")
if not ts:
return None

lookup = TraktLookup(ts)
te = self.find_episode_guid(guid, lookup)
if not te:
return None

# NOTE: overwrites property of type str
te.show = ts

return te

def find_by_guid(self, guid: PlexGuid):
if guid.type == "episode" and guid.is_episode:
ts: TVShow = self.search_by_id(
guid.show_id, id_type=guid.provider, media_type="show"
)
if ts:
lookup = TraktLookup(ts)

return self.find_episode_guid(guid, lookup)
return self.find_by_episode_guid(guid)
else:
tm = self.search_by_id(guid.id, id_type=guid.provider, media_type=guid.type)
if tm is None and guid.type == "movie":
Expand Down

0 comments on commit f663ac6

Please sign in to comment.