Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: Implement shows sync using episodes #1578

Merged
merged 17 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions plextraktsync/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from plexapi.exceptions import PlexApiException
from requests import RequestException
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
Loading