tag update cron job, removed scratch playlist

closes #3
closes #11
This commit is contained in:
aj 2020-05-15 23:25:19 +01:00
parent e3fd4bb6d5
commit 3c248fd728
8 changed files with 98 additions and 686 deletions

View File

@ -8,10 +8,8 @@ import logging
from datetime import datetime from datetime import datetime
from music.api.decorators import login_required, login_or_basic_auth, admin_required, gae_cron, cloud_task from music.api.decorators import login_required, login_or_basic_auth, admin_required, gae_cron, cloud_task
from music.cloud.tasks import execute_all_user_playlists, execute_user_playlists, create_run_user_playlist_task, \ from music.cloud.tasks import update_all_user_playlists, update_playlists, run_user_playlist_task
create_play_user_playlist_task
from music.tasks.run_user_playlist import run_user_playlist as run_user_playlist from music.tasks.run_user_playlist import run_user_playlist as run_user_playlist
from music.tasks.play_user_playlist import play_user_playlist as play_user_playlist
from music.model.user import User from music.model.user import User
from music.model.playlist import Playlist from music.model.playlist import Playlist
@ -25,7 +23,7 @@ logger = logging.getLogger(__name__)
@blueprint.route('/playlists', methods=['GET']) @blueprint.route('/playlists', methods=['GET'])
@login_or_basic_auth @login_or_basic_auth
def get_playlists(user=None): def all_playlists_route(user=None):
assert user is not None assert user is not None
return jsonify({ return jsonify({
'playlists': [i.to_dict() for i in Playlist.collection.parent(user.key).fetch()] 'playlists': [i.to_dict() for i in Playlist.collection.parent(user.key).fetch()]
@ -188,8 +186,9 @@ def playlist_route(user=None):
updating_playlist.chart_limit = playlist_chart_limit updating_playlist.chart_limit = playlist_chart_limit
if playlist_type is not None: if playlist_type is not None:
# TODO check acceptable value playlist_type = playlist_type.strip().lower()
updating_playlist.type = playlist_type if playlist_type in ['default', 'recents', 'fmchart']:
updating_playlist.type = playlist_type
updating_playlist.update() updating_playlist.update()
logger.info(f'updated {user.username} / {playlist_name}') logger.info(f'updated {user.username} / {playlist_name}')
@ -241,7 +240,7 @@ def user_route(user=None):
@blueprint.route('/users', methods=['GET']) @blueprint.route('/users', methods=['GET'])
@login_or_basic_auth @login_or_basic_auth
@admin_required @admin_required
def users(user=None): def all_users_route(user=None):
return jsonify({ return jsonify({
'accounts': [i.to_dict() for i in User.collection.fetch()] 'accounts': [i.to_dict() for i in User.collection.fetch()]
}), 200 }), 200
@ -274,82 +273,6 @@ def change_password(user=None):
return jsonify({'error': 'malformed request, no old_password/new_password'}), 400 return jsonify({'error': 'malformed request, no old_password/new_password'}), 400
@blueprint.route('/playlist/play', methods=['POST'])
@login_or_basic_auth
def play_playlist(user=None):
request_json = request.get_json()
request_parts = request_json.get('parts', None)
request_playlist_type = request_json.get('playlist_type', 'default')
request_playlists = request_json.get('playlists', None)
request_shuffle = request_json.get('shuffle', False)
request_include_recommendations = request_json.get('include_recommendations', True)
request_recommendation_sample = request_json.get('recommendation_sample', 10)
request_day_boundary = request_json.get('day_boundary', 10)
request_add_this_month = request_json.get('add_this_month', False)
request_add_last_month = request_json.get('add_last_month', False)
request_device_name = request_json.get('device_name', None)
logger.info(f'playing {user.username}')
if (request_parts and len(request_parts) > 0) or (request_playlists and len(request_playlists) > 0):
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_play_user_playlist_task(user.username,
parts=request_parts,
playlist_type=request_playlist_type,
playlists=request_playlists,
shuffle=request_shuffle,
include_recommendations=request_include_recommendations,
recommendation_sample=request_recommendation_sample,
day_boundary=request_day_boundary,
add_this_month=request_add_this_month,
add_last_month=request_add_last_month,
device_name=request_device_name)
else:
play_user_playlist(user.username,
parts=request_parts,
playlist_type=request_playlist_type,
playlists=request_playlists,
shuffle=request_shuffle,
include_recommendations=request_include_recommendations,
recommendation_sample=request_recommendation_sample,
day_boundary=request_day_boundary,
add_this_month=request_add_this_month,
add_last_month=request_add_last_month,
device_name=request_device_name)
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
else:
logger.error(f'no playlists/parts {user.username}')
return jsonify({'error': 'insufficient playlist sources'}), 400
@blueprint.route('/playlist/play/task', methods=['POST'])
@cloud_task
def play_playlist_task():
payload = request.get_data(as_text=True)
if payload:
payload = json.loads(payload)
logger.info(f'playing {payload["username"]}')
play_user_playlist(payload['username'],
parts=payload['parts'],
playlist_type=payload['playlist_type'],
playlists=payload['playlists'],
shuffle=payload['shuffle'],
include_recommendations=payload['include_recommendations'],
recommendation_sample=payload['recommendation_sample'],
day_boundary=payload['day_boundary'],
add_this_month=payload['add_this_month'],
add_last_month=payload['add_last_month'],
device_name=payload['device_name'])
return jsonify({'message': 'executed playlist', 'status': 'success'}), 200
@blueprint.route('/playlist/run', methods=['GET']) @blueprint.route('/playlist/run', methods=['GET'])
@login_or_basic_auth @login_or_basic_auth
def run_playlist(user=None): def run_playlist(user=None):
@ -359,7 +282,7 @@ def run_playlist(user=None):
if playlist_name: if playlist_name:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_run_user_playlist_task(user.username, playlist_name) run_user_playlist_task(user.username, playlist_name)
else: else:
run_user_playlist(user.username, playlist_name) run_user_playlist(user.username, playlist_name)
@ -394,7 +317,7 @@ def run_user(user=None):
else: else:
user_name = user.username user_name = user.username
execute_user_playlists(user_name) update_playlists(user_name)
return jsonify({'message': 'executed user', 'status': 'success'}), 200 return jsonify({'message': 'executed user', 'status': 'success'}), 200
@ -405,7 +328,7 @@ def run_user_task():
payload = request.get_data(as_text=True) payload = request.get_data(as_text=True)
if payload: if payload:
execute_user_playlists(payload) update_playlists(payload)
return jsonify({'message': 'executed user', 'status': 'success'}), 200 return jsonify({'message': 'executed user', 'status': 'success'}), 200
@ -414,7 +337,7 @@ def run_user_task():
@admin_required @admin_required
def run_users(user=None): def run_users(user=None):
execute_all_user_playlists() update_all_user_playlists()
return jsonify({'message': 'executed all users', 'status': 'success'}), 200 return jsonify({'message': 'executed all users', 'status': 'success'}), 200
@ -422,7 +345,7 @@ def run_users(user=None):
@gae_cron @gae_cron
def run_users_cron(): def run_users_cron():
execute_all_user_playlists() update_all_user_playlists()
return jsonify({'status': 'success'}), 200 return jsonify({'status': 'success'}), 200

View File

@ -5,7 +5,7 @@ import os
from music.api.decorators import admin_required, login_or_basic_auth, lastfm_username_required, spotify_link_required, cloud_task, gae_cron from music.api.decorators import admin_required, login_or_basic_auth, lastfm_username_required, spotify_link_required, cloud_task, gae_cron
import music.db.database as database import music.db.database as database
from music.cloud.tasks import execute_all_user_playlist_stats, execute_user_playlist_stats, create_refresh_playlist_task from music.cloud.tasks import refresh_all_user_playlist_stats, refresh_user_playlist_stats, refresh_playlist_task
from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats, \ from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats, \
refresh_lastfm_album_stats, \ refresh_lastfm_album_stats, \
refresh_lastfm_artist_stats refresh_lastfm_artist_stats
@ -74,7 +74,7 @@ def playlist_refresh(user=None):
if playlist_name: if playlist_name:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_refresh_playlist_task(user.username, playlist_name) refresh_playlist_task(user.username, playlist_name)
else: else:
refresh_lastfm_track_stats(user.username, playlist_name) refresh_lastfm_track_stats(user.username, playlist_name)
refresh_lastfm_album_stats(user.username, playlist_name) refresh_lastfm_album_stats(user.username, playlist_name)
@ -136,14 +136,14 @@ def run_playlist_artist_task():
@login_or_basic_auth @login_or_basic_auth
@admin_required @admin_required
def run_users(user=None): def run_users(user=None):
execute_all_user_playlist_stats() refresh_all_user_playlist_stats()
return jsonify({'message': 'executed all users', 'status': 'success'}), 200 return jsonify({'message': 'executed all users', 'status': 'success'}), 200
@blueprint.route('/playlist/refresh/users/cron', methods=['GET']) @blueprint.route('/playlist/refresh/users/cron', methods=['GET'])
@gae_cron @gae_cron
def run_users_task(): def run_users_task():
execute_all_user_playlist_stats() refresh_all_user_playlist_stats()
return jsonify({'status': 'success'}), 200 return jsonify({'status': 'success'}), 200
@ -156,7 +156,7 @@ def run_user(user=None):
else: else:
user_name = user.username user_name = user.username
execute_user_playlist_stats(user_name) refresh_user_playlist_stats(user_name)
return jsonify({'message': 'executed user', 'status': 'success'}), 200 return jsonify({'message': 'executed user', 'status': 'success'}), 200
@ -167,5 +167,5 @@ def run_user_task():
payload = request.get_data(as_text=True) payload = request.get_data(as_text=True)
if payload: if payload:
execute_user_playlist_stats(payload) refresh_user_playlist_stats(payload)
return jsonify({'message': 'executed user', 'status': 'success'}), 200 return jsonify({'message': 'executed user', 'status': 'success'}), 200

View File

@ -2,9 +2,11 @@ from flask import Blueprint, jsonify, request
import logging import logging
import os import os
import json
from music.api.decorators import login_or_basic_auth from music.api.decorators import login_or_basic_auth, gae_cron, cloud_task
from music.cloud.function import update_tag as serverless_update_tag from music.cloud.function import update_tag as serverless_update_tag
from music.cloud.tasks import update_all_user_tags
from music.tasks.update_tag import update_tag from music.tasks.update_tag import update_tag
from music.model.tag import Tag from music.model.tag import Tag
@ -60,10 +62,7 @@ def put_tag(tag_id, user):
if request_json.get('name'): if request_json.get('name'):
db_tag.name = request_json['name'].strip() db_tag.name = request_json['name'].strip()
update_required = False
if request_json.get('tracks') is not None: if request_json.get('tracks') is not None:
update_required = True
db_tag.tracks = [ db_tag.tracks = [
{ {
'name': track['name'].strip(), 'name': track['name'].strip(),
@ -74,7 +73,6 @@ def put_tag(tag_id, user):
] ]
if request_json.get('albums') is not None: if request_json.get('albums') is not None:
update_required = True
db_tag.albums = [ db_tag.albums = [
{ {
'name': album['name'].strip(), 'name': album['name'].strip(),
@ -85,7 +83,6 @@ def put_tag(tag_id, user):
] ]
if request_json.get('artists') is not None: if request_json.get('artists') is not None:
update_required = True
db_tag.artists = [ db_tag.artists = [
{ {
'name': artist['name'].strip() 'name': artist['name'].strip()
@ -96,13 +93,6 @@ def put_tag(tag_id, user):
db_tag.update() db_tag.update()
if update_required:
# queue serverless refresh
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
serverless_update_tag(username=user.username, tag_id=tag_id)
else:
update_tag(username=user.username, tag_id=tag_id)
return jsonify({"message": 'tag updated', "status": "success"}), 200 return jsonify({"message": 'tag updated', "status": "success"}), 200
@ -113,7 +103,7 @@ def post_tag(tag_id, user):
existing_ids = [i.tag_id for i in Tag.collection.parent(user.key).fetch()] existing_ids = [i.tag_id for i in Tag.collection.parent(user.key).fetch()]
while tag_id in existing_ids: while tag_id in existing_ids:
tag_id += '1' tag_id += '_'
tag = Tag(parent=user.key) tag = Tag(parent=user.key)
tag.tag_id = tag_id tag.tag_id = tag_id
@ -144,3 +134,26 @@ def tag_refresh(tag_id, user=None):
update_tag(username=user.username, tag_id=tag_id) update_tag(username=user.username, tag_id=tag_id)
return jsonify({"message": 'tag updated', "status": "success"}), 200 return jsonify({"message": 'tag updated', "status": "success"}), 200
@blueprint.route('/tag/update/task', methods=['POST'])
@cloud_task
def run_tag_task():
payload = request.get_data(as_text=True)
if payload:
payload = json.loads(payload)
logger.info(f'running {payload["username"]} / {payload["tag_id"]}')
serverless_update_tag(username=payload['username'], tag_id=payload['tag_id'])
return jsonify({'message': 'executed playlist', 'status': 'success'}), 200
@blueprint.route('/tag/update/users/cron', methods=['GET'])
@gae_cron
def run_tags_cron():
update_all_user_tags()
return jsonify({'status': 'success'}), 200

View File

@ -6,12 +6,12 @@ import logging
from google.cloud import tasks_v2 from google.cloud import tasks_v2
from google.protobuf import timestamp_pb2 from google.protobuf import timestamp_pb2
from music.db import database as database
from music.tasks.run_user_playlist import run_user_playlist from music.tasks.run_user_playlist import run_user_playlist
from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats
from music.model.user import User from music.model.user import User
from music.model.playlist import Playlist from music.model.playlist import Playlist
from music.model.tag import Tag
tasker = tasks_v2.CloudTasksClient() tasker = tasks_v2.CloudTasksClient()
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions') task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
@ -19,7 +19,7 @@ task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def execute_all_user_playlists(): def update_all_user_playlists():
"""Create user playlist refresh task for all users""" """Create user playlist refresh task for all users"""
seconds_delay = 0 seconds_delay = 0
@ -48,7 +48,7 @@ def execute_all_user_playlists():
seconds_delay += 30 seconds_delay += 30
def execute_user_playlists(username): def update_playlists(username):
"""Refresh all playlists for given user, environment dependent""" """Refresh all playlists for given user, environment dependent"""
user = User.collection.filter('username', '==', username.strip().lower()).get() user = User.collection.filter('username', '==', username.strip().lower()).get()
@ -66,14 +66,14 @@ def execute_user_playlists(username):
if iterate_playlist.uri is not None: if iterate_playlist.uri is not None:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_run_user_playlist_task(username, iterate_playlist.name, seconds_delay) run_user_playlist_task(username, iterate_playlist.name, seconds_delay)
else: else:
run_user_playlist(username, iterate_playlist.name) run_user_playlist(username, iterate_playlist.name)
seconds_delay += 6 seconds_delay += 6
def create_run_user_playlist_task(username, playlist_name, delay=0): def run_user_playlist_task(username, playlist_name, delay=0):
"""Create tasks for a users given playlist""" """Create tasks for a users given playlist"""
task = { task = {
@ -101,54 +101,7 @@ def create_run_user_playlist_task(username, playlist_name, delay=0):
tasker.create_task(task_path, task) tasker.create_task(task_path, task)
def create_play_user_playlist_task(username, def refresh_all_user_playlist_stats():
parts=None,
playlist_type='default',
playlists=None,
shuffle=False,
include_recommendations=False,
recommendation_sample=10,
day_boundary=10,
add_this_month=False,
add_last_month=False,
delay=0,
device_name=None):
"""Create tasks for a users given scratch playlist"""
task = {
'app_engine_http_request': { # Specify the type of request.
'http_method': 'POST',
'relative_uri': '/api/playlist/play/task',
'body': json.dumps({
'username': username,
'playlist_type': playlist_type,
'parts': parts,
'playlists': playlists,
'shuffle': shuffle,
'include_recommendations': include_recommendations,
'recommendation_sample': recommendation_sample,
'day_boundary': day_boundary,
'add_this_month': add_this_month,
'add_last_month': add_last_month,
'device_name': device_name
}).encode()
}
}
if delay > 0:
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
# Create Timestamp protobuf.
timestamp = timestamp_pb2.Timestamp()
timestamp.FromDatetime(d)
# Add the timestamp to the tasks.
task['schedule_time'] = timestamp
tasker.create_task(task_path, task)
def execute_all_user_playlist_stats():
""""Create user playlist stats refresh task for all users""" """"Create user playlist stats refresh task for all users"""
seconds_delay = 0 seconds_delay = 0
@ -160,9 +113,9 @@ def execute_all_user_playlist_stats():
len(iter_user.lastfm_username) > 0 and not iter_user.locked: len(iter_user.lastfm_username) > 0 and not iter_user.locked:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_refresh_user_task(username=iter_user.username, delay=seconds_delay) refresh_user_stats_task(username=iter_user.username, delay=seconds_delay)
else: else:
execute_user_playlist_stats(username=iter_user.username) refresh_user_playlist_stats(username=iter_user.username)
seconds_delay += 2400 seconds_delay += 2400
@ -170,7 +123,7 @@ def execute_all_user_playlist_stats():
logger.debug(f'skipping {iter_user.username}') logger.debug(f'skipping {iter_user.username}')
def execute_user_playlist_stats(username): def refresh_user_playlist_stats(username):
"""Refresh all playlist stats for given user, environment dependent""" """Refresh all playlist stats for given user, environment dependent"""
user = User.collection.filter('username', '==', username.strip().lower()).get() user = User.collection.filter('username', '==', username.strip().lower()).get()
@ -188,7 +141,7 @@ def execute_user_playlist_stats(username):
if playlist.uri is not None: if playlist.uri is not None:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD': if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_refresh_playlist_task(username, playlist.name, seconds_delay) refresh_playlist_task(username, playlist.name, seconds_delay)
else: else:
refresh_lastfm_track_stats(username, playlist.name) refresh_lastfm_track_stats(username, playlist.name)
@ -197,7 +150,7 @@ def execute_user_playlist_stats(username):
logger.error('no last.fm username') logger.error('no last.fm username')
def create_refresh_user_task(username, delay=0): def refresh_user_stats_task(username, delay=0):
"""Create user playlist stats refresh task""" """Create user playlist stats refresh task"""
task = { task = {
@ -219,7 +172,7 @@ def create_refresh_user_task(username, delay=0):
tasker.create_task(task_path, task) tasker.create_task(task_path, task)
def create_refresh_playlist_task(username, playlist_name, delay=0): def refresh_playlist_task(username, playlist_name, delay=0):
"""Create user playlist stats refresh tasks""" """Create user playlist stats refresh tasks"""
track_task = { track_task = {
@ -274,3 +227,37 @@ def create_refresh_playlist_task(username, playlist_name, delay=0):
tasker.create_task(task_path, track_task) tasker.create_task(task_path, track_task)
tasker.create_task(task_path, album_task) tasker.create_task(task_path, album_task)
tasker.create_task(task_path, artist_task) tasker.create_task(task_path, artist_task)
def update_all_user_tags():
"""Create user tag refresh task sfor all users"""
seconds_delay = 0
logger.info('running')
for iter_user in User.collection.fetch():
if iter_user.lastfm_username and len(iter_user.lastfm_username) > 0 and not iter_user.locked:
for tag in Tag.collection.parent(iter_user.key).fetch():
task = {
'app_engine_http_request': { # Specify the type of request.
'http_method': 'POST',
'relative_uri': '/api/tag/update/task',
'body': json.dumps({
'username': iter_user.username,
'tag_id': tag.tag_id
}).encode()
}
}
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds_delay)
timestamp = timestamp_pb2.Timestamp()
timestamp.FromDatetime(d)
task['schedule_time'] = timestamp
tasker.create_task(task_path, task)
seconds_delay += 10

View File

@ -1,99 +0,0 @@
import datetime
import logging
from spotframework.engine.playlistengine import PlaylistEngine, PlaylistSource, RecommendationSource
from spotframework.engine.processor.shuffle import Shuffle
from spotframework.engine.processor.sort import SortReleaseDate
from spotframework.engine.processor.deduplicate import DeduplicateByID
from spotframework.player.player import Player
import music.db.database as database
from music.db.part_generator import PartGenerator
from music.model.user import User
logger = logging.getLogger(__name__)
def play_user_playlist(username,
playlist_type='default',
parts=None,
playlists=None,
shuffle=False,
include_recommendations=True,
recommendation_sample=10,
day_boundary=10,
add_this_month=False,
add_last_month=False,
device_name=None):
user = User.collection.filter('username', '==', username.strip().lower()).get()
if user is None:
logger.error(f'user {username} not found')
return
logger.info(f'playing for {username}')
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(user)
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)

View File

@ -1,20 +1,20 @@
astroid==2.4.0 astroid==2.4.1
cachetools==4.1.0 cachetools==4.1.0
certifi==2020.4.5.1 certifi==2020.4.5.1
chardet==3.0.4 chardet==3.0.4
click==7.1.2 click==7.1.2
fireo==1.2.4 fireo==1.2.8
Flask==1.1.2 Flask==1.1.2
google-api-core==1.17.0 google-api-core==1.17.0
google-auth==1.14.1 google-auth==1.14.3
google-cloud-core==1.3.0 google-cloud-core==1.3.0
google-cloud-firestore==1.6.2 google-cloud-firestore==1.6.2
google-cloud-logging==1.15.0 google-cloud-logging==1.15.0
google-cloud-pubsub==1.4.3 google-cloud-pubsub==1.5.0
google-cloud-tasks==1.5.0 google-cloud-tasks==1.5.0
googleapis-common-protos==1.51.0 googleapis-common-protos==1.51.0
grpc-google-iam-v1==0.12.3 grpc-google-iam-v1==0.12.3
grpcio==1.28.1 grpcio==1.29.0
idna==2.9 idna==2.9
isort==4.3.21 isort==4.3.21
itsdangerous==1.1.0 itsdangerous==1.1.0
@ -22,18 +22,18 @@ Jinja2==2.11.2
lazy-object-proxy==1.4.3 lazy-object-proxy==1.4.3
MarkupSafe==1.1.1 MarkupSafe==1.1.1
mccabe==0.6.1 mccabe==0.6.1
numpy==1.18.3 numpy==1.18.4
opencv-python==4.2.0.34 opencv-python==4.2.0.34
protobuf==3.11.3 protobuf==3.11.3
pyasn1==0.4.8 pyasn1==0.4.8
pyasn1-modules==0.2.8 pyasn1-modules==0.2.8
pylint==2.5.0 pylint==2.5.2
pytz==2020.1 pytz==2020.1
requests==2.23.0 requests==2.23.0
rsa==4.0 rsa==4.0
six==1.14.0 six==1.14.0
tabulate==0.8.7 tabulate==0.8.7
toml==0.10.0 toml==0.10.1
urllib3==1.25.9 urllib3==1.25.9
Werkzeug==1.0.1 Werkzeug==1.0.1
wrapt==1.12.1 wrapt==1.12.1

View File

@ -3,7 +3,6 @@ import { Route, Switch } from "react-router-dom";
import PlaylistsView from "./PlaylistsList.js" import PlaylistsView from "./PlaylistsList.js"
import NewPlaylist from "./New.js"; import NewPlaylist from "./New.js";
import ScratchView from "./ScratchView.js";
class Playlists extends Component { class Playlists extends Component {
render(){ render(){
@ -12,7 +11,6 @@ class Playlists extends Component {
<Switch> <Switch>
<Route exact path={`${this.props.match.url}/`} component={PlaylistsView} /> <Route exact path={`${this.props.match.url}/`} component={PlaylistsView} />
<Route path={`${this.props.match.url}/new`} component={NewPlaylist} /> <Route path={`${this.props.match.url}/new`} component={NewPlaylist} />
<Route path={`${this.props.match.url}/play`} component={ScratchView} />
</Switch> </Switch>
</div> </div>
); );

View File

@ -1,410 +0,0 @@
import React, { Component } from "react";
const axios = require('axios');
import showMessage from "../Toast.js"
var thisMonth = [
'january',
'february',
'march',
'april',
'may',
'june',
'july',
'august',
'septempber',
'october',
'november',
'december'
];
var lastMonth = [
'december',
'january',
'february',
'march',
'april',
'may',
'june',
'july',
'august',
'septempber',
'october',
'november'
];
class ScratchView extends Component{
constructor(props){
super(props);
this.state = {
name: 'play',
parts: [],
playlists: [],
filteredPlaylists: [],
playlist_references: [],
type: 'default',
day_boundary: 5,
recommendation_sample: 5,
newPlaylistName: '',
newReferenceName: '',
shuffle: false,
include_recommendations: false,
add_this_month: false,
add_last_month: false
}
this.handleAddPart = this.handleAddPart.bind(this);
this.handleAddReference = this.handleAddReference.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleRemovePart = this.handleRemovePart.bind(this);
this.handleRemoveReference = this.handleRemoveReference.bind(this);
this.handleRun = this.handleRun.bind(this);
this.handleShuffleChange = this.handleShuffleChange.bind(this);
this.handleIncludeRecommendationsChange = this.handleIncludeRecommendationsChange.bind(this);
this.handleThisMonthChange = this.handleThisMonthChange.bind(this);
this.handleLastMonthChange = this.handleLastMonthChange.bind(this);
}
componentDidMount(){
this.getPlaylists();
}
getPlaylists(){
return axios.get(`/api/playlists`)
.then((response) => {
var filteredPlaylists = response.data.playlists.filter((entry) => entry.name != this.state.name);
this.setState({
playlists: response.data.playlists,
newReferenceName: filteredPlaylists.length > 0 ? filteredPlaylists[0].name : ''
});
})
.catch((error) => {
showMessage(`Error Getting Playlists (${error.response.status})`);
});
}
handleInputChange(event){
this.setState({
[event.target.name]: event.target.value
});
}
handleShuffleChange(event) {
this.setState({
shuffle: event.target.checked
});
}
handleThisMonthChange(event) {
this.setState({
add_this_month: event.target.checked
});
}
handleLastMonthChange(event) {
this.setState({
add_last_month: event.target.checked
});
}
handleIncludeRecommendationsChange(event) {
this.setState({
include_recommendations: event.target.checked
});
}
handleAddPart(event){
if(this.state.newPlaylistName.length != 0){
var check = this.state.parts.includes(this.state.newPlaylistName);
if(check == false) {
var parts = this.state.parts.slice();
parts.push(this.state.newPlaylistName);
parts.sort(function(a, b){
if(a < b) { return -1; }
if(a > b) { return 1; }
return 0;
});
this.setState({
parts: parts,
newPlaylistName: ''
});
}else{
showMessage('Playlist Already Added');
}
}else{
showMessage('Enter Playlist Name');
}
}
handleAddReference(event){
if(this.state.newReferenceName.length != 0){
var check = this.state.playlist_references.includes(this.state.newReferenceName);
if(check == false) {
var playlist_references = this.state.playlist_references.slice();
playlist_references.push(this.state.newReferenceName);
playlist_references.sort(function(a, b){
if(a < b) { return -1; }
if(a > b) { return 1; }
return 0;
});
var filteredPlaylists = this.state.playlists.filter((entry) => entry.name != this.state.name);
this.setState({
playlist_references: playlist_references,
newReferenceName: filteredPlaylists.length > 0 ? filteredPlaylists[0].name : ''
});
}else{
showMessage('Playlist Already Added');
}
}else{
showMessage('No Other Playlists To Add');
}
}
handleRemovePart(id, event){
var parts = this.state.parts;
parts = parts.filter(e => e !== id);
this.setState({
parts: parts
});
if(parts.length == 0) {
parts = -1;
}
}
handleRemoveReference(id, event){
var playlist_references = this.state.playlist_references;
playlist_references = playlist_references.filter(e => e !== id);
this.setState({
playlist_references: playlist_references
});
}
handleRun(event){
if(this.state.playlist_references.length > 0 || this.state.parts.length > 0){
axios.get('/api/user')
.then((response) => {
if(response.data.spotify_linked == true){
axios.post('/api/playlist/play', {
parts: this.state.parts,
playlists: this.state.playlist_references,
shuffle: this.state.shuffle,
include_recommendations: this.state.include_recommendations,
recommendation_sample: this.state.recommendation_sample,
day_boundary: this.state.day_boundary,
playlist_type: this.state.type,
add_this_month: this.state.add_this_month,
add_last_month: this.state.add_last_month
})
.then((reponse) => {
showMessage(`Played`);
})
.catch((error) => {
showMessage(`Error Playing (${error.response.status})`);
});
}else{
showMessage(`Link Spotify Before Running`);
}
}).catch((error) => {
showMessage(`Error Playing (${error.response.status})`);
});
}else{
showMessage(`Add Either Playlists Or Parts`);
}
}
render(){
var date = new Date();
const table = (
<table className="app-table max-width">
{/* <thead>
<tr>
<th colSpan="2"><h1 className="text-no-select">{ this.state.name }</h1></th>
</tr>
</thead> */}
{ this.state.playlist_references.length > 0 && <ListBlock name="managed" handler={this.handleRemoveReference} list={this.state.playlist_references}/> }
{ this.state.parts.length > 0 && <ListBlock name="spotify" handler={this.handleRemovePart} list={this.state.parts}/> }
<tbody>
<tr>
<td colSpan="2" className="center-text ui-text text-no-select" style={{fontStyle: "italic"}}>
<br></br>Spotify playlist can be the name of either your own created playlist or one you follow, names are case sensitive
</td>
</tr>
<tr>
<td>
<input type="text"
name="newPlaylistName"
className="full-width"
value={this.state.newPlaylistName}
onChange={this.handleInputChange}
placeholder="Spotify Playlist Name"></input>
</td>
<td>
<button className="button full-width" onClick={this.handleAddPart}>Add</button>
</td>
</tr>
<tr>
<td>
<select name="newReferenceName"
className="full-width"
value={this.state.newReferenceName}
onChange={this.handleInputChange}>
{ this.state.playlists
.filter((entry) => entry.name != this.state.name)
.map((entry) => <ReferenceEntry name={entry.name} key={entry.name} />) }
</select>
</td>
<td>
<button className="button full-width" onClick={this.handleAddReference}>Add</button>
</td>
</tr>
<tr>
<td className="center-text ui-text text-no-select">
Shuffle Output
</td>
<td>
<input type="checkbox"
name="shuffle"
checked={this.state.shuffle}
onChange={this.handleShuffleChange}></input>
</td>
</tr>
<tr>
<td className="center-text ui-text text-no-select">
Include Recommendations
</td>
<td>
<input type="checkbox"
name="include_recommendations"
checked={this.state.include_recommendations}
onChange={this.handleIncludeRecommendationsChange}></input>
</td>
</tr>
<tr>
<td className="center-text ui-text text-no-select">
Recommendation Size
</td>
<td>
<input type="number"
name="recommendation_sample"
className="full-width"
value={this.state.recommendation_sample}
onChange={this.handleInputChange}></input>
</td>
</tr>
{ this.state.type == 'recents' &&
<tr>
<td className="center-text ui-text text-no-select">
Added Since (Days)
</td>
<td>
<input type="number"
name="day_boundary"
className="full-width"
value={this.state.day_boundary}
onChange={this.handleInputChange}></input>
</td>
</tr>
}
{ this.state.type == 'recents' &&
<tr>
<td className="center-text ui-text text-no-select">
Include {thisMonth[date.getMonth()]} Playlist
</td>
<td>
<input type="checkbox"
checked={this.state.add_this_month}
onChange={this.handleThisMonthChange}></input>
</td>
</tr>
}
{ this.state.type == 'recents' &&
<tr>
<td className="center-text ui-text text-no-select">
Include {lastMonth[date.getMonth()]} Playlist
</td>
<td>
<input type="checkbox"
checked={this.state.add_last_month}
onChange={this.handleLastMonthChange}></input>
</td>
</tr>
}
<tr>
<td className="center-text ui-text text-no-select">
Type
</td>
<td>
<select className="full-width"
name="type"
onChange={this.handleInputChange}
value={this.state.type}>
<option value="default">Default</option>
<option value="recents">Recents</option>
</select>
</td>
</tr>
<tr>
<td colSpan="2">
<button className="button full-width" onClick={this.handleRun}>play</button>
</td>
</tr>
</tbody>
</table>
);
const error = <p style={{textAlign: "center"}}>{ this.state.error_text }</p>;
return this.state.error ? error : table;
}
}
function ReferenceEntry(props) {
return (
<option value={props.name}>{props.name}</option>
);
}
function ListBlock(props) {
return (
<tbody>
<tr><td colSpan="2" className="ui-text center-text text-no-select" style={{fontStyle: 'italic'}}>{props.name}</td></tr>
{ props.list.map((part) => <Row part={ part } key={ part } handler={props.handler}/>) }
</tbody>
);
}
function Row (props) {
return (
<tr>
<td className="ui-text center-text text-no-select">{ props.part }</td>
<td><button className="ui-text center-text button full-width" onClick={(e) => props.handler(props.part, e)}>Remove</button></td>
</tr>
);
}
export default ScratchView;