From d6e0bb9cf83eabab6135e6da51ea26f3e269dc2b Mon Sep 17 00:00:00 2001 From: aj Date: Mon, 24 Feb 2020 18:15:38 +0000 Subject: [PATCH] added playlist last_updated, made tasks more legible --- music/api/api.py | 4 +- music/db/database.py | 6 ++ music/model/playlist.py | 23 +++++ music/tasks/create_playlist.py | 6 +- music/tasks/play_user_playlist.py | 133 ++++++++++++++-------------- music/tasks/refresh_lastfm_stats.py | 24 +++++ music/tasks/run_user_playlist.py | 118 ++++++++++++------------ 7 files changed, 187 insertions(+), 127 deletions(-) diff --git a/music/api/api.py b/music/api/api.py index a21132c..6fd487a 100644 --- a/music/api/api.py +++ b/music/api/api.py @@ -3,6 +3,7 @@ from flask import Blueprint, request, jsonify import os import json import logging +from datetime import datetime from google.cloud import firestore @@ -117,7 +118,8 @@ def playlist(username=None): 'recommendation_sample': playlist_recommendation_sample if playlist_recommendation_sample is not None else 10, 'uri': None, 'shuffle': playlist_shuffle if playlist_shuffle is not None else False, - 'type': playlist_type if playlist_type is not None else 'default' + 'type': playlist_type if playlist_type is not None else 'default', + 'last_updated': datetime.utcnow() } if user_ref.get().to_dict()['spotify_linked']: diff --git a/music/db/database.py b/music/db/database.py index d93f116..85d46b6 100644 --- a/music/db/database.py +++ b/music/db/database.py @@ -217,6 +217,8 @@ def parse_playlist_reference(username, playlist_ref=None, playlist_snapshot=None description_overwrite=playlist_dict.get('description_overwrite'), description_suffix=playlist_dict.get('description_suffix'), + last_updated=playlist_dict.get('last_updated'), + lastfm_stat_count=playlist_dict.get('lastfm_stat_count', 0), lastfm_stat_album_count=playlist_dict.get('lastfm_stat_album_count', 0), lastfm_stat_artist_count=playlist_dict.get('lastfm_stat_artist_count', 0), @@ -247,6 +249,8 @@ def parse_playlist_reference(username, playlist_ref=None, playlist_snapshot=None description_overwrite=playlist_dict.get('description_overwrite'), description_suffix=playlist_dict.get('description_suffix'), + last_updated=playlist_dict.get('last_updated'), + lastfm_stat_count=playlist_dict.get('lastfm_stat_count', 0), lastfm_stat_album_count=playlist_dict.get('lastfm_stat_album_count', 0), lastfm_stat_artist_count=playlist_dict.get('lastfm_stat_artist_count', 0), @@ -281,6 +285,8 @@ def parse_playlist_reference(username, playlist_ref=None, playlist_snapshot=None description_overwrite=playlist_dict.get('description_overwrite'), description_suffix=playlist_dict.get('description_suffix'), + last_updated=playlist_dict.get('last_updated'), + lastfm_stat_count=playlist_dict.get('lastfm_stat_count', 0), lastfm_stat_album_count=playlist_dict.get('lastfm_stat_album_count', 0), lastfm_stat_artist_count=playlist_dict.get('lastfm_stat_artist_count', 0), diff --git a/music/model/playlist.py b/music/model/playlist.py index 26e689e..f9b9e03 100644 --- a/music/model/playlist.py +++ b/music/model/playlist.py @@ -35,6 +35,8 @@ class Playlist: description_overwrite: str = None, description_suffix: str = None, + last_updated: datetime = None, + lastfm_stat_count: int = None, lastfm_stat_album_count: int = None, lastfm_stat_artist_count: int = None, @@ -62,6 +64,8 @@ class Playlist: self._description_overwrite = description_overwrite self._description_suffix = description_suffix + self._last_updated = last_updated + self._lastfm_stat_count = lastfm_stat_count self._lastfm_stat_album_count = lastfm_stat_album_count self._lastfm_stat_artist_count = lastfm_stat_artist_count @@ -90,6 +94,8 @@ class Playlist: 'description_overwrite': self.description_overwrite, 'description_suffix': self.description_suffix, + 'last_updated': self.last_updated, + 'lastfm_stat_count': self.lastfm_stat_count, 'lastfm_stat_album_count': self.lastfm_stat_album_count, 'lastfm_stat_artist_count': self.lastfm_stat_artist_count, @@ -194,6 +200,15 @@ class Playlist: database.update_playlist(self.username, self.name, {'description_suffix': value}) self._description_suffix = value + @property + def last_updated(self): + return self._last_updated + + @last_updated.setter + def last_updated(self, value): + database.update_playlist(self.username, self.name, {'last_updated': value}) + self._last_updated = value + @property def lastfm_stat_count(self): return self._lastfm_stat_count @@ -279,6 +294,8 @@ class RecentsPlaylist(Playlist): description_overwrite: str = None, description_suffix: str = None, + last_updated: datetime = None, + lastfm_stat_count: int = None, lastfm_stat_album_count: int = None, lastfm_stat_artist_count: int = None, @@ -311,6 +328,8 @@ class RecentsPlaylist(Playlist): description_overwrite=description_overwrite, description_suffix=description_suffix, + last_updated=last_updated, + lastfm_stat_count=lastfm_stat_count, lastfm_stat_album_count=lastfm_stat_album_count, lastfm_stat_artist_count=lastfm_stat_artist_count, @@ -388,6 +407,8 @@ class LastFMChartPlaylist(Playlist): description_overwrite: str = None, description_suffix: str = None, + last_updated: datetime = None, + lastfm_stat_count: int = None, lastfm_stat_album_count: int = None, lastfm_stat_artist_count: int = None, @@ -416,6 +437,8 @@ class LastFMChartPlaylist(Playlist): description_overwrite=description_overwrite, description_suffix=description_suffix, + last_updated=last_updated, + lastfm_stat_count=lastfm_stat_count, lastfm_stat_album_count=lastfm_stat_album_count, lastfm_stat_artist_count=lastfm_stat_artist_count, diff --git a/music/tasks/create_playlist.py b/music/tasks/create_playlist.py index de5fade..26e9004 100644 --- a/music/tasks/create_playlist.py +++ b/music/tasks/create_playlist.py @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) def create_playlist(username, name): - logger.info(f'creating {username} / {name}') + logger.info(f'creating spotify playlist for {username} / {name}') user = database.get_user(username) if user is not None: @@ -22,8 +22,8 @@ def create_playlist(username, name): return playlist else: logger.error(f'no response received {username} / {name}') - return None + return else: logger.error(f'{username} not found') - return None + return diff --git a/music/tasks/play_user_playlist.py b/music/tasks/play_user_playlist.py index a152dfc..da99928 100644 --- a/music/tasks/play_user_playlist.py +++ b/music/tasks/play_user_playlist.py @@ -33,71 +33,70 @@ def play_user_playlist(username, logger.info(f'playing for {username}') - if user: - - if parts is None and playlists is None: - logger.critical(f'no playlists to use for creation ({username})') - return None - - if parts is None: - parts = [] - - if playlists is None: - playlists = [] - - if len(parts) == 0 and len(playlists) == 0: - logger.critical(f'no playlists to use for creation ({username})') - return None - - net = database.get_authed_spotify_network(username) - - device = None - if device_name: - devices = net.get_available_devices() - if devices and len(devices) > 0: - device = next((i for i in devices if i.name == device_name), None) - if device is None: - logger.error(f'error selecting device {device_name} to play on') - else: - logger.warning(f'no available devices to play') - - engine = PlaylistEngine(net) - - player = Player(net) - - processors = [DeduplicateByID()] - - if shuffle: - processors.append(Shuffle()) - else: - processors.append(SortReleaseDate(reverse=True)) - - submit_parts = parts - - part_generator = PartGenerator(user=user) - - for part in playlists: - submit_parts += part_generator.get_recursive_parts(part) - - submit_parts = [i for i in {j for j in submit_parts}] - - params = [ - PlaylistSource.Params(names=submit_parts, processors=processors) - ] - - if include_recommendations: - params.append(RecommendationSource.Params(recommendation_limit=int(recommendation_sample))) - - if playlist_type == 'recents': - boundary_date = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=int(day_boundary)) - tracks = engine.get_recent_playlist(params=params, - boundary_date=boundary_date, - add_this_month=add_this_month, - add_last_month=add_last_month) - else: - tracks = engine.make_playlist(params=params) - - player.play(tracks=tracks, device=device) - - else: + if user is None: logger.critical(f'{username} not found') + return + + if parts is None and playlists is None: + logger.critical(f'no playlists to use for creation ({username})') + return None + + if parts is None: + parts = [] + + if playlists is None: + playlists = [] + + if len(parts) == 0 and len(playlists) == 0: + logger.critical(f'no playlists to use for creation ({username})') + return None + + net = database.get_authed_spotify_network(username) + + device = None + if device_name: + devices = net.get_available_devices() + if devices and len(devices) > 0: + device = next((i for i in devices if i.name == device_name), None) + if device is None: + logger.error(f'error selecting device {device_name} to play on') + else: + logger.warning(f'no available devices to play') + + engine = PlaylistEngine(net) + + player = Player(net) + + processors = [DeduplicateByID()] + + if shuffle: + processors.append(Shuffle()) + else: + processors.append(SortReleaseDate(reverse=True)) + + submit_parts = parts + + part_generator = PartGenerator(user=user) + + for part in playlists: + submit_parts += part_generator.get_recursive_parts(part) + + submit_parts = [i for i in {j for j in submit_parts}] + + params = [ + PlaylistSource.Params(names=submit_parts, processors=processors) + ] + + if include_recommendations: + params.append(RecommendationSource.Params(recommendation_limit=int(recommendation_sample))) + + if playlist_type == 'recents': + boundary_date = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=int(day_boundary)) + tracks = engine.get_recent_playlist(params=params, + boundary_date=boundary_date, + add_this_month=add_this_month, + add_last_month=add_last_month) + else: + tracks = engine.make_playlist(params=params) + + player.play(tracks=tracks, device=device) diff --git a/music/tasks/refresh_lastfm_stats.py b/music/tasks/refresh_lastfm_stats.py index 6b0bf8b..39e2615 100644 --- a/music/tasks/refresh_lastfm_stats.py +++ b/music/tasks/refresh_lastfm_stats.py @@ -23,6 +23,14 @@ def refresh_lastfm_track_stats(username, playlist_name): playlist = database.get_playlist(username=username, name=playlist_name) + if playlist is None: + logger.critical(f'playlist {playlist_name} for {username} not found') + return + + if playlist.uri is None: + logger.critical(f'playlist {playlist_name} for {username} has no spotify uri') + return + spotify_playlist = spotnet.get_playlist(uri=Uri(playlist.uri)) track_count = counter.count_playlist(playlist=spotify_playlist) @@ -50,6 +58,14 @@ def refresh_lastfm_album_stats(username, playlist_name): playlist = database.get_playlist(username=username, name=playlist_name) + if playlist is None: + logger.critical(f'playlist {playlist_name} for {username} not found') + return + + if playlist.uri is None: + logger.critical(f'playlist {playlist_name} for {username} has no spotify uri') + return + spotify_playlist = spotnet.get_playlist(uri=Uri(playlist.uri)) album_count = counter.count_playlist(playlist=spotify_playlist, query_album=True) @@ -77,6 +93,14 @@ def refresh_lastfm_artist_stats(username, playlist_name): playlist = database.get_playlist(username=username, name=playlist_name) + if playlist is None: + logger.critical(f'playlist {playlist_name} for {username} not found') + return + + if playlist.uri is None: + logger.critical(f'playlist {playlist_name} for {username} has no spotify uri') + return + spotify_playlist = spotnet.get_playlist(uri=Uri(playlist.uri)) artist_count = counter.count_playlist(playlist=spotify_playlist, query_artist=True) diff --git a/music/tasks/run_user_playlist.py b/music/tasks/run_user_playlist.py index 52d51be..01a7d1a 100644 --- a/music/tasks/run_user_playlist.py +++ b/music/tasks/run_user_playlist.py @@ -22,76 +22,82 @@ logger = logging.getLogger(__name__) def run_user_playlist(username, playlist_name): + """Generate and upadate a user's playlist""" user = database.get_user(username) logger.info(f'running {username} / {playlist_name}') - if user: + # PRE-RUN CHECKS + if user is None: + logger.critical(f'{username} not found') + return - playlist = database.get_playlist(username=username, name=playlist_name) + playlist = database.get_playlist(username=username, name=playlist_name) - if playlist is not None: + if playlist is None: + logger.critical(f'playlist not found ({username}/{playlist_name})') + return - if playlist.uri is None: - logger.critical(f'no playlist id to populate ({username}/{playlist_name})') - return None + if playlist.uri is None: + logger.critical(f'no playlist id to populate ({username}/{playlist_name})') + return - net = database.get_authed_spotify_network(username) + # END CHECKS - engine = PlaylistEngine(net) + net = database.get_authed_spotify_network(username) + engine = PlaylistEngine(net) + part_generator = PartGenerator(user=user) - if isinstance(playlist, LastFMChartPlaylist) and user.lastfm_username is not None: - engine.sources.append(ChartSource(spotnet=net, fmnet=database.get_authed_lastfm_network(user.username))) + spotify_playlist_names = part_generator.get_recursive_parts(playlist.name) - processors = [DeduplicateByID()] + processors = [DeduplicateByID()] + params = [ + PlaylistSource.Params(names=spotify_playlist_names) + ] - if not isinstance(playlist, LastFMChartPlaylist): - if playlist.shuffle is True: - processors.append(Shuffle()) - else: - processors.append(SortReleaseDate(reverse=True)) + # OPTIONS + if playlist.include_recommendations: + params.append(RecommendationSource.Params(recommendation_limit=playlist.recommendation_sample)) - part_generator = PartGenerator(user=user) - submit_parts = part_generator.get_recursive_parts(playlist.name) - - params = [ - PlaylistSource.Params(names=submit_parts) - ] - - if playlist.include_recommendations: - params.append(RecommendationSource.Params(recommendation_limit=playlist.recommendation_sample)) - - if playlist.include_library_tracks: - params.append(LibraryTrackSource.Params()) - - if isinstance(playlist, LastFMChartPlaylist): - params.append(ChartSource.Params(chart_range=playlist.chart_range, limit=playlist.chart_limit)) - - if isinstance(playlist, RecentsPlaylist): - boundary_date = datetime.datetime.now(datetime.timezone.utc) - \ - datetime.timedelta(days=int(playlist.day_boundary)) - tracks = engine.get_recent_playlist(params=params, - processors=processors, - boundary_date=boundary_date, - add_this_month=playlist.add_this_month, - add_last_month=playlist.add_last_month) - else: - tracks = engine.make_playlist(params=params, - processors=processors) - - engine.execute_playlist(tracks, Uri(playlist.uri)) - - overwrite = playlist.description_overwrite - suffix = playlist.description_suffix - - engine.change_description(sorted(submit_parts), - uri=Uri(playlist.uri), - overwrite=overwrite, - suffix=suffix) + if playlist.include_library_tracks: + params.append(LibraryTrackSource.Params()) + # END OPTIONS + if isinstance(playlist, LastFMChartPlaylist): + if user.lastfm_username is None: + logger.error(f'{username} has no associated last.fm username, chart source skipped') else: - logger.critical(f'playlist not found ({username}/{playlist_name})') - return None + engine.sources.append(ChartSource(spotnet=net, fmnet=database.get_authed_lastfm_network(user.username))) + params.append(ChartSource.Params(chart_range=playlist.chart_range, limit=playlist.chart_limit)) else: - logger.critical(f'{username} not found') + # INCLUDE SORT METHOD (no sorting for last.fm chart playlist) + if playlist.shuffle is True: + processors.append(Shuffle()) + else: + processors.append(SortReleaseDate(reverse=True)) + + # GENERATE TRACKS + if isinstance(playlist, RecentsPlaylist): + boundary_date = datetime.datetime.now(datetime.timezone.utc) - \ + datetime.timedelta(days=int(playlist.day_boundary)) + tracks = engine.get_recent_playlist(params=params, + processors=processors, + boundary_date=boundary_date, + add_this_month=playlist.add_this_month, + add_last_month=playlist.add_last_month) + else: + tracks = engine.make_playlist(params=params, + processors=processors) + + # NET OPS + engine.execute_playlist(tracks, Uri(playlist.uri)) + + overwrite = playlist.description_overwrite + suffix = playlist.description_suffix + + engine.change_description(sorted(spotify_playlist_names), + uri=Uri(playlist.uri), + overwrite=overwrite, + suffix=suffix) + playlist.last_updated = datetime.datetime.utcnow()