From e9281e652c89956c2add3a7c819d779ade57a5f7 Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Sat, 3 Aug 2024 08:50:40 +0000 Subject: [PATCH] playlist_manager: Cache playlist entry IDs along with video IDs This is required in order to be able to move entries later. Note our view of entry IDs may always be out of date, so any time you use one you have to handle it no longer existing. --- playlist_manager/playlist_manager/main.py | 31 ++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/playlist_manager/playlist_manager/main.py b/playlist_manager/playlist_manager/main.py index ce7c90c..62a126d 100644 --- a/playlist_manager/playlist_manager/main.py +++ b/playlist_manager/playlist_manager/main.py @@ -14,6 +14,9 @@ from common.database import DBManager, query from common.googleapis import GoogleAPIClient +PlaylistEntry = namedtuple("PlaylistEntry", ["entry_id", "video_id"]) + + class PlaylistManager(object): def __init__(self, dbmanager, api_client, upload_locations, playlist_tags): @@ -30,7 +33,7 @@ class PlaylistManager(object): if playlist_id is None: # playlist_state represents our mirrored view of the list of items in each playlist. # If a playlist is not present, it means we need to refresh our view of it. - # {playlist_id: [video_id]} + # {playlist_id: [PlaylistEntry]} self.playlist_state = {} else: self.playlist_state.pop(playlist_id, None) @@ -112,7 +115,7 @@ class PlaylistManager(object): # If we have nothing to add, short circuit without doing any API calls to save quota. matching_video_ids = {video.video_id for video in matching} - playlist_video_ids = set(self.get_playlist(playlist_id)) + playlist_video_ids = {entry.video_id for entry in self.get_playlist(playlist_id)} if not (matching_video_ids - playlist_video_ids): logging.debug("All videos already in playlist, nothing to do") return @@ -120,7 +123,7 @@ class PlaylistManager(object): self.refresh_playlist(playlist_id) # Get an updated list of new videos matching_video_ids = {video.video_id for video in matching} - playlist_video_ids = set(self.get_playlist(playlist_id)) + playlist_video_ids = {entry.video_id for entry in self.get_playlist(playlist_id)} # It shouldn't matter, but just for clarity let's sort them by event order new_videos = sorted(matching_video_ids - playlist_video_ids, key=lambda v: v.start_time) @@ -154,8 +157,11 @@ class PlaylistManager(object): query.fetch_all() # Update saved copy with video ids self.playlist_state[playlist_id] = [ - item['snippet']['resourceId'].get('videoId') # api implies it's possible that non-videos are added - for item in query.items + PlaylistEntry( + item['id'], + # api implies it's possible that non-videos are added, so videoId might not exist + item['snippet']['resourceId'].get('videoId'), + ) for item in query.items ] def find_insert_index(self, videos, playlist, new_video): @@ -168,7 +174,7 @@ class PlaylistManager(object): # item that should be after us in sort order. # Note that we treat unknown items (videos we don't know) as being before us # in sort order, so that we always insert after them. - for n, video_id in enumerate(playlist): + for n, (_, video_id) in enumerate(playlist): if video_id not in videos: # ignore unknowns continue @@ -185,9 +191,9 @@ class PlaylistManager(object): Makes the API call then also updates our mirrored copy. """ logging.info(f"Inserting {video_id} at index {index} of {playlist_id}") - self.api.insert_into_playlist(playlist_id, video_id, index) + entry_id = self.api.insert_into_playlist(playlist_id, video_id, index) # Update our copy - self.playlist_state.setdefault(playlist_id, []).insert(index, video_id) + self.playlist_state.setdefault(playlist_id, []).insert(index, PlaylistEntry(entry_id, video_id) class YoutubeAPI(object): @@ -214,10 +220,11 @@ class YoutubeAPI(object): json=json, metric_name="playlist_insert", ) - if not resp.ok: - raise Exception("Failed to insert {video_id} at index {index} of {playlist} with {resp.status_code}: {resp.content}".format( - playlist=playlist_id, video_id=video_id, index=index, resp=resp, - )) + if not resp.ok: + raise Exception("Failed to insert {video_id} at index {index} of {playlist} with {resp.status_code}: {resp.content}".format( + playlist=playlist_id, video_id=video_id, index=index, resp=resp, + )) + # TODO return entry_id from resp def list_playlist(self, playlist_id): """Fetches the first page of playlist contents and returns a ListQuery object.