added tag js, added playlist name sorting, moved cloud tasks
This commit is contained in:
parent
97ffc1f141
commit
29bd875ecd
36
deploy
36
deploy
@ -22,14 +22,34 @@ cp -r spotfm/spotfm $stage_dir/
|
||||
|
||||
cd $stage_dir
|
||||
|
||||
echo '>> building css'
|
||||
sass --style=compressed src/scss/style.scss build/style.css
|
||||
|
||||
echo '>> building javascript'
|
||||
npm run build
|
||||
|
||||
echo '>> deploying'
|
||||
gcloud config set project sarsooxyz
|
||||
gcloud app deploy
|
||||
|
||||
echo '>>> Target?'
|
||||
echo ''
|
||||
echo '(0) > api'
|
||||
echo '(1) > update_tag'
|
||||
read deploy_target
|
||||
|
||||
|
||||
case "$deploy_target" in
|
||||
0)
|
||||
echo '>> building css'
|
||||
sass --style=compressed src/scss/style.scss build/style.css
|
||||
|
||||
echo '>> building javascript'
|
||||
npm run build
|
||||
|
||||
echo '>> deploying'
|
||||
gcloud app deploy
|
||||
;;
|
||||
|
||||
1)
|
||||
echo '>> deploying update_tag'
|
||||
gcloud functions deploy update_tag
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
|
||||
|
||||
|
||||
|
138
music/api/api.py
138
music/api/api.py
@ -1,15 +1,14 @@
|
||||
from flask import Blueprint, session, request, jsonify
|
||||
from flask import Blueprint, request, jsonify
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
from google.cloud import firestore
|
||||
from google.cloud import tasks_v2
|
||||
from google.protobuf import timestamp_pb2
|
||||
|
||||
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, \
|
||||
create_play_user_playlist_task
|
||||
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
|
||||
|
||||
@ -18,9 +17,6 @@ import music.db.database as database
|
||||
blueprint = Blueprint('api', __name__)
|
||||
db = firestore.Client()
|
||||
|
||||
tasker = tasks_v2.CloudTasksClient()
|
||||
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -411,12 +407,12 @@ def run_playlist_task():
|
||||
def run_user(username=None):
|
||||
|
||||
db_user = database.get_user(username)
|
||||
if db_user.type == db_user.Type.admin:
|
||||
if db_user.user_type == db_user.Type.admin:
|
||||
user_name = request.args.get('username', username)
|
||||
else:
|
||||
user_name = username
|
||||
|
||||
execute_user(user_name)
|
||||
execute_user_playlists(user_name)
|
||||
|
||||
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
||||
|
||||
@ -427,7 +423,7 @@ def run_user_task():
|
||||
|
||||
payload = request.get_data(as_text=True)
|
||||
if payload:
|
||||
execute_user(payload)
|
||||
execute_user_playlists(payload)
|
||||
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
||||
|
||||
|
||||
@ -436,7 +432,7 @@ def run_user_task():
|
||||
@admin_required
|
||||
def run_users(username=None):
|
||||
|
||||
execute_all_users()
|
||||
execute_all_user_playlists()
|
||||
return jsonify({'message': 'executed all users', 'status': 'success'}), 200
|
||||
|
||||
|
||||
@ -444,123 +440,5 @@ def run_users(username=None):
|
||||
@gae_cron
|
||||
def run_users_cron():
|
||||
|
||||
execute_all_users()
|
||||
execute_all_user_playlists()
|
||||
return jsonify({'status': 'success'}), 200
|
||||
|
||||
|
||||
def execute_all_users():
|
||||
|
||||
seconds_delay = 0
|
||||
logger.info('running')
|
||||
|
||||
for iter_user in database.get_users():
|
||||
|
||||
if iter_user.spotify_linked and not iter_user.locked:
|
||||
|
||||
task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/playlist/run/user/task',
|
||||
'body': iter_user.username.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 += 30
|
||||
|
||||
|
||||
def execute_user(username):
|
||||
|
||||
playlists = database.get_user_playlists(username)
|
||||
|
||||
seconds_delay = 0
|
||||
logger.info(f'running {username}')
|
||||
|
||||
for iterate_playlist in playlists:
|
||||
if iterate_playlist.uri:
|
||||
|
||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||
create_run_user_playlist_task(username, iterate_playlist.name, seconds_delay)
|
||||
else:
|
||||
run_playlist(username, iterate_playlist.name)
|
||||
|
||||
seconds_delay += 6
|
||||
|
||||
|
||||
def create_run_user_playlist_task(username, playlist_name, delay=0):
|
||||
|
||||
task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/playlist/run/task',
|
||||
'body': json.dumps({
|
||||
'username': username,
|
||||
'name': playlist_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 create_play_user_playlist_task(username,
|
||||
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):
|
||||
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)
|
||||
|
@ -2,10 +2,10 @@ from flask import Blueprint, jsonify, request
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import datetime
|
||||
|
||||
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
|
||||
from music.cloud.tasks import execute_all_user_playlist_stats, execute_user_playlist_stats, create_refresh_playlist_task
|
||||
from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats, \
|
||||
refresh_lastfm_album_stats, \
|
||||
refresh_lastfm_artist_stats
|
||||
@ -13,17 +13,9 @@ from music.tasks.refresh_lastfm_stats import refresh_lastfm_track_stats, \
|
||||
from spotfm.maths.counter import Counter
|
||||
from spotframework.model.uri import Uri
|
||||
|
||||
from google.cloud import firestore
|
||||
from google.cloud import tasks_v2
|
||||
from google.protobuf import timestamp_pb2
|
||||
|
||||
blueprint = Blueprint('spotfm-api', __name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
db = firestore.Client()
|
||||
tasker = tasks_v2.CloudTasksClient()
|
||||
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
|
||||
|
||||
|
||||
@blueprint.route('/count', methods=['GET'])
|
||||
@login_or_basic_auth
|
||||
@ -144,14 +136,14 @@ def run_playlist_artist_task():
|
||||
@login_or_basic_auth
|
||||
@admin_required
|
||||
def run_users(username=None):
|
||||
execute_all_users()
|
||||
execute_all_user_playlist_stats()
|
||||
return jsonify({'message': 'executed all users', 'status': 'success'}), 200
|
||||
|
||||
|
||||
@blueprint.route('/playlist/refresh/users/cron', methods=['GET'])
|
||||
@gae_cron
|
||||
def run_users_task():
|
||||
execute_all_users()
|
||||
execute_all_user_playlist_stats()
|
||||
return jsonify({'status': 'success'}), 200
|
||||
|
||||
|
||||
@ -165,7 +157,7 @@ def run_user(username=None):
|
||||
else:
|
||||
user_name = username
|
||||
|
||||
execute_user(user_name)
|
||||
execute_user_playlist_stats(user_name)
|
||||
|
||||
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
||||
|
||||
@ -176,125 +168,5 @@ def run_user_task():
|
||||
|
||||
payload = request.get_data(as_text=True)
|
||||
if payload:
|
||||
execute_user(payload)
|
||||
execute_user_playlist_stats(payload)
|
||||
return jsonify({'message': 'executed user', 'status': 'success'}), 200
|
||||
|
||||
|
||||
def execute_all_users():
|
||||
|
||||
seconds_delay = 0
|
||||
logger.info('running')
|
||||
|
||||
for iter_user in database.get_users():
|
||||
|
||||
if iter_user.spotify_linked and iter_user.lastfm_username and \
|
||||
len(iter_user.lastfm_username) > 0 and not iter_user.locked:
|
||||
|
||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||
create_refresh_user_task(username=iter_user.username, delay=seconds_delay)
|
||||
else:
|
||||
execute_user(username=iter_user.username)
|
||||
|
||||
seconds_delay += 2400
|
||||
|
||||
else:
|
||||
logger.debug(f'skipping {iter_user.username}')
|
||||
|
||||
|
||||
def execute_user(username):
|
||||
|
||||
playlists = database.get_user_playlists(username)
|
||||
user = database.get_user(username)
|
||||
|
||||
seconds_delay = 0
|
||||
logger.info(f'running {username}')
|
||||
|
||||
if user.lastfm_username and len(user.lastfm_username) > 0:
|
||||
for playlist in playlists:
|
||||
if playlist.uri:
|
||||
|
||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||
create_refresh_playlist_task(username, playlist.name, seconds_delay)
|
||||
else:
|
||||
refresh_lastfm_track_stats(username, playlist.name)
|
||||
|
||||
seconds_delay += 1200
|
||||
else:
|
||||
logger.error('no last.fm username')
|
||||
|
||||
|
||||
def create_refresh_user_task(username, delay=0):
|
||||
|
||||
task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/spotfm/playlist/refresh/user/task',
|
||||
'body': username.encode()
|
||||
}
|
||||
}
|
||||
|
||||
if delay > 0:
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
||||
|
||||
timestamp = timestamp_pb2.Timestamp()
|
||||
timestamp.FromDatetime(d)
|
||||
|
||||
task['schedule_time'] = timestamp
|
||||
|
||||
tasker.create_task(task_path, task)
|
||||
|
||||
|
||||
def create_refresh_playlist_task(username, playlist_name, delay=0):
|
||||
|
||||
track_task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/spotfm/playlist/refresh/task/track',
|
||||
'body': json.dumps({
|
||||
'username': username,
|
||||
'name': playlist_name
|
||||
}).encode()
|
||||
}
|
||||
}
|
||||
if delay > 0:
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
||||
timestamp = timestamp_pb2.Timestamp()
|
||||
timestamp.FromDatetime(d)
|
||||
|
||||
track_task['schedule_time'] = timestamp
|
||||
|
||||
album_task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/spotfm/playlist/refresh/task/album',
|
||||
'body': json.dumps({
|
||||
'username': username,
|
||||
'name': playlist_name
|
||||
}).encode()
|
||||
}
|
||||
}
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay + 180)
|
||||
timestamp = timestamp_pb2.Timestamp()
|
||||
timestamp.FromDatetime(d)
|
||||
|
||||
album_task['schedule_time'] = timestamp
|
||||
|
||||
artist_task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/spotfm/playlist/refresh/task/artist',
|
||||
'body': json.dumps({
|
||||
'username': username,
|
||||
'name': playlist_name
|
||||
}).encode()
|
||||
}
|
||||
}
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay + 360)
|
||||
timestamp = timestamp_pb2.Timestamp()
|
||||
timestamp.FromDatetime(d)
|
||||
|
||||
artist_task['schedule_time'] = timestamp
|
||||
|
||||
tasker.create_task(task_path, track_task)
|
||||
tasker.create_task(task_path, album_task)
|
||||
tasker.create_task(task_path, artist_task)
|
||||
|
@ -2,16 +2,13 @@ from flask import Blueprint, jsonify, request
|
||||
|
||||
import logging
|
||||
|
||||
from google.cloud import pubsub_v1
|
||||
|
||||
import music.db.database as database
|
||||
from music.api.decorators import login_or_basic_auth
|
||||
from music.cloud.function import update_tag
|
||||
|
||||
blueprint = Blueprint('task', __name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
publisher = pubsub_v1.PublisherClient()
|
||||
|
||||
|
||||
@blueprint.route('/tag', methods=['GET'])
|
||||
@login_or_basic_auth
|
||||
@ -26,7 +23,7 @@ def tags(username=None):
|
||||
@login_or_basic_auth
|
||||
def tag(tag_id, username=None):
|
||||
if request.method == 'GET':
|
||||
return put_tag(tag_id, username)
|
||||
return get_tag(tag_id, username)
|
||||
elif request.method == 'PUT':
|
||||
return put_tag(tag_id, username)
|
||||
elif request.method == 'POST':
|
||||
@ -63,7 +60,7 @@ def put_tag(tag_id, username):
|
||||
update_required = False
|
||||
|
||||
tracks = []
|
||||
if request_json.get('tracks'):
|
||||
if request_json.get('tracks') is not None:
|
||||
update_required = True
|
||||
for track in request_json['tracks']:
|
||||
if track.get('name') and track.get('artist'):
|
||||
@ -74,7 +71,7 @@ def put_tag(tag_id, username):
|
||||
db_tag.tracks = tracks
|
||||
|
||||
albums = []
|
||||
if request_json.get('albums'):
|
||||
if request_json.get('albums') is not None:
|
||||
update_required = True
|
||||
for album in request_json['albums']:
|
||||
if album.get('name') and album.get('artist'):
|
||||
@ -82,13 +79,13 @@ def put_tag(tag_id, username):
|
||||
'name': album['name'],
|
||||
'artist': album['artist']
|
||||
})
|
||||
db_tag.album = albums
|
||||
db_tag.albums = albums
|
||||
|
||||
artists = []
|
||||
if request_json.get('artists'):
|
||||
if request_json.get('artists') is not None:
|
||||
update_required = True
|
||||
for artist in request_json['tracks']:
|
||||
if artist.get('name') and artist.get('artist'):
|
||||
for artist in request_json['artists']:
|
||||
if artist.get('name'):
|
||||
artists.append({
|
||||
'name': artist['name']
|
||||
})
|
||||
@ -103,11 +100,10 @@ def put_tag(tag_id, username):
|
||||
def post_tag(tag_id, username):
|
||||
logger.info(f'creating {tag_id} for {username}')
|
||||
|
||||
new_tag = database.create_tag(username=username, tag_id=tag_id)
|
||||
if new_tag is not None:
|
||||
tag_id = tag_id.replace(' ', '_')
|
||||
|
||||
database.create_tag(username=username, tag_id=tag_id)
|
||||
return jsonify({"message": 'tag added', "status": "success"}), 201
|
||||
else:
|
||||
return jsonify({"error": 'tag not created'}), 400
|
||||
|
||||
|
||||
def delete_tag(tag_id, username):
|
||||
@ -121,7 +117,9 @@ def delete_tag(tag_id, username):
|
||||
return jsonify({"error": 'tag not deleted'}), 400
|
||||
|
||||
|
||||
def update_tag(username, tag_id):
|
||||
logger.info(f'queuing {tag_id} update for {username}')
|
||||
|
||||
publisher.publish('projects/sarsooxyz/topics/update_tag', b'', tag_id=tag_id, username=username)
|
||||
@blueprint.route('/tag/<tag_id>/update', methods=['GET'])
|
||||
@login_or_basic_auth
|
||||
def tag_refresh(tag_id, username=None):
|
||||
logger.info(f'updating {tag_id} tag for {username}')
|
||||
update_tag(username=username, tag_id=tag_id)
|
||||
return jsonify({"message": 'tag updated', "status": "success"}), 200
|
||||
|
0
music/cloud/__init__.py
Normal file
0
music/cloud/__init__.py
Normal file
10
music/cloud/function.py
Normal file
10
music/cloud/function.py
Normal file
@ -0,0 +1,10 @@
|
||||
import logging
|
||||
from google.cloud import pubsub_v1
|
||||
|
||||
publisher = pubsub_v1.PublisherClient()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def update_tag(username, tag_id):
|
||||
logger.info(f'queuing {tag_id} update for {username}')
|
||||
publisher.publish('projects/sarsooxyz/topics/update_tag', b'', tag_id=tag_id, username=username)
|
254
music/cloud/tasks.py
Normal file
254
music/cloud/tasks.py
Normal file
@ -0,0 +1,254 @@
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
|
||||
from google.cloud import tasks_v2
|
||||
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.refresh_lastfm_stats import refresh_lastfm_track_stats
|
||||
|
||||
tasker = tasks_v2.CloudTasksClient()
|
||||
task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def execute_all_user_playlists():
|
||||
|
||||
seconds_delay = 0
|
||||
logger.info('running')
|
||||
|
||||
for iter_user in database.get_users():
|
||||
|
||||
if iter_user.spotify_linked and not iter_user.locked:
|
||||
|
||||
task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/playlist/run/user/task',
|
||||
'body': iter_user.username.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 += 30
|
||||
|
||||
|
||||
def execute_user_playlists(username):
|
||||
|
||||
playlists = database.get_user_playlists(username)
|
||||
|
||||
seconds_delay = 0
|
||||
logger.info(f'running {username}')
|
||||
|
||||
for iterate_playlist in playlists:
|
||||
if iterate_playlist.uri:
|
||||
|
||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||
create_run_user_playlist_task(username, iterate_playlist.name, seconds_delay)
|
||||
else:
|
||||
run_user_playlist(username, iterate_playlist.name)
|
||||
|
||||
seconds_delay += 6
|
||||
|
||||
|
||||
def create_run_user_playlist_task(username, playlist_name, delay=0):
|
||||
|
||||
task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/playlist/run/task',
|
||||
'body': json.dumps({
|
||||
'username': username,
|
||||
'name': playlist_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 create_play_user_playlist_task(username,
|
||||
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):
|
||||
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():
|
||||
|
||||
seconds_delay = 0
|
||||
logger.info('running')
|
||||
|
||||
for iter_user in database.get_users():
|
||||
|
||||
if iter_user.spotify_linked and iter_user.lastfm_username and \
|
||||
len(iter_user.lastfm_username) > 0 and not iter_user.locked:
|
||||
|
||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||
create_refresh_user_task(username=iter_user.username, delay=seconds_delay)
|
||||
else:
|
||||
execute_user_playlist_stats(username=iter_user.username)
|
||||
|
||||
seconds_delay += 2400
|
||||
|
||||
else:
|
||||
logger.debug(f'skipping {iter_user.username}')
|
||||
|
||||
|
||||
def execute_user_playlist_stats(username):
|
||||
|
||||
playlists = database.get_user_playlists(username)
|
||||
user = database.get_user(username)
|
||||
|
||||
seconds_delay = 0
|
||||
logger.info(f'running {username}')
|
||||
|
||||
if user.lastfm_username and len(user.lastfm_username) > 0:
|
||||
for playlist in playlists:
|
||||
if playlist.uri:
|
||||
|
||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||
create_refresh_playlist_task(username, playlist.name, seconds_delay)
|
||||
else:
|
||||
refresh_lastfm_track_stats(username, playlist.name)
|
||||
|
||||
seconds_delay += 1200
|
||||
else:
|
||||
logger.error('no last.fm username')
|
||||
|
||||
|
||||
def create_refresh_user_task(username, delay=0):
|
||||
|
||||
task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/spotfm/playlist/refresh/user/task',
|
||||
'body': username.encode()
|
||||
}
|
||||
}
|
||||
|
||||
if delay > 0:
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
||||
|
||||
timestamp = timestamp_pb2.Timestamp()
|
||||
timestamp.FromDatetime(d)
|
||||
|
||||
task['schedule_time'] = timestamp
|
||||
|
||||
tasker.create_task(task_path, task)
|
||||
|
||||
|
||||
def create_refresh_playlist_task(username, playlist_name, delay=0):
|
||||
|
||||
track_task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/spotfm/playlist/refresh/task/track',
|
||||
'body': json.dumps({
|
||||
'username': username,
|
||||
'name': playlist_name
|
||||
}).encode()
|
||||
}
|
||||
}
|
||||
if delay > 0:
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
|
||||
timestamp = timestamp_pb2.Timestamp()
|
||||
timestamp.FromDatetime(d)
|
||||
|
||||
track_task['schedule_time'] = timestamp
|
||||
|
||||
album_task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/spotfm/playlist/refresh/task/album',
|
||||
'body': json.dumps({
|
||||
'username': username,
|
||||
'name': playlist_name
|
||||
}).encode()
|
||||
}
|
||||
}
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay + 180)
|
||||
timestamp = timestamp_pb2.Timestamp()
|
||||
timestamp.FromDatetime(d)
|
||||
|
||||
album_task['schedule_time'] = timestamp
|
||||
|
||||
artist_task = {
|
||||
'app_engine_http_request': { # Specify the type of request.
|
||||
'http_method': 'POST',
|
||||
'relative_uri': '/api/spotfm/playlist/refresh/task/artist',
|
||||
'body': json.dumps({
|
||||
'username': username,
|
||||
'name': playlist_name
|
||||
}).encode()
|
||||
}
|
||||
}
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay + 360)
|
||||
timestamp = timestamp_pb2.Timestamp()
|
||||
timestamp.FromDatetime(d)
|
||||
|
||||
artist_task['schedule_time'] = timestamp
|
||||
|
||||
tasker.create_task(task_path, track_task)
|
||||
tasker.create_task(task_path, album_task)
|
||||
tasker.create_task(task_path, artist_task)
|
@ -435,7 +435,7 @@ def create_tag(username: str, tag_id: str):
|
||||
logger.error(f'{tag_id} already exists for {username}')
|
||||
return None
|
||||
|
||||
return parse_tag_reference(user.db_ref.collection(u'tags').add({
|
||||
user.db_ref.collection(u'tags').add({
|
||||
'tag_id': tag_id,
|
||||
'name': tag_id,
|
||||
|
||||
@ -447,4 +447,4 @@ def create_tag(username: str, tag_id: str):
|
||||
'proportion': 0.0,
|
||||
'total_user_scrobbles': 0,
|
||||
'last_updated': None
|
||||
})[1])
|
||||
})
|
||||
|
@ -48,6 +48,7 @@ class Tag:
|
||||
|
||||
'count': self.count,
|
||||
'proportion': self.proportion,
|
||||
'total_user_scrobbles': self.total_user_scrobbles,
|
||||
|
||||
'last_updated': self.last_updated
|
||||
}
|
||||
|
@ -29,13 +29,21 @@ class BarChart extends Component {
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
yAxes: [{
|
||||
ticks: {
|
||||
min: 0
|
||||
fontColor: "#d8d8d8",
|
||||
fontSize: 16,
|
||||
stepSize: 1,
|
||||
beginAtZero: true
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
fontColor: "#d8d8d8",
|
||||
fontSize: 16,
|
||||
stepSize: 1
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import { Build, PieChart, QueueMusic, ExitToApp, AccountCircle, KeyboardBackspace } from '@material-ui/icons'
|
||||
import { Build, PieChart, QueueMusic, ExitToApp, AccountCircle, KeyboardBackspace, GroupWork } from '@material-ui/icons'
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
@ -32,6 +32,8 @@ const LazyPlaylists = React.lazy(() => import("./Playlist/AllPlaylistsRouter"))
|
||||
const LazyPlaylistView = React.lazy(() => import("./Playlist/View/PlaylistRouter"))
|
||||
const LazySettings = React.lazy(() => import("./Settings/SettingsRouter"))
|
||||
const LazyAdmin = React.lazy(() => import("./Admin/AdminRouter"))
|
||||
const LazyTags = React.lazy(() => import("./Tag/TagRouter"))
|
||||
const LazyTag = React.lazy(() => import("./Tag/View"))
|
||||
|
||||
class MusicTools extends Component {
|
||||
|
||||
@ -117,6 +119,10 @@ class MusicTools extends Component {
|
||||
<ListItemIcon><QueueMusic /></ListItemIcon>
|
||||
<ListItemText primary="Playlists" />
|
||||
</ListItem>
|
||||
<ListItem button key="tags" component={Link} to='/app/tags'>
|
||||
<ListItemIcon><GroupWork /></ListItemIcon>
|
||||
<ListItemText primary="Tags" />
|
||||
</ListItem>
|
||||
<ListItem button key="maths" component={Link} to='/app/maths/count'>
|
||||
<ListItemIcon><PieChart /></ListItemIcon>
|
||||
<ListItemText primary="Maths" />
|
||||
@ -147,6 +153,8 @@ class MusicTools extends Component {
|
||||
<React.Suspense fallback={<LoadingMessage/>}>
|
||||
<Route path="/app" exact component={LazyIndex} />
|
||||
<Route path="/app/playlists" component={LazyPlaylists} />
|
||||
<Route path="/app/tags" component={LazyTags} />
|
||||
<Route path="/app/tag/:tag_id" component={LazyTag} />
|
||||
<Route path="/app/maths" component={LazyMaths} />
|
||||
<Route path="/app/settings" component={LazySettings} />
|
||||
{ this.state.type == 'admin' && <Route path="/app/admin" component={LazyAdmin} /> }
|
||||
|
@ -97,18 +97,24 @@ export class Edit extends Component{
|
||||
axios.all([this.getPlaylistInfo(), this.getPlaylists()])
|
||||
.then(axios.spread((info, playlists) => {
|
||||
|
||||
info.data.parts.sort(function(a, b){
|
||||
info.data.parts.sort((a, b) => {
|
||||
if(a.toLowerCase() < b.toLowerCase()) { return -1; }
|
||||
if(a.toLowerCase() > b.toLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
info.data.playlist_references.sort(function(a, b){
|
||||
info.data.playlist_references.sort((a, b) => {
|
||||
if(a.toLowerCase() < b.toLowerCase()) { return -1; }
|
||||
if(a.toLowerCase() > b.toLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
playlists.data.playlists.sort( (a, b) => {
|
||||
if(a.name.toLowerCase() < b.name.toLowerCase()) { return -1; }
|
||||
if(a.name.toLowerCase() > b.name.toLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
var filteredPlaylists = playlists.data.playlists.filter((entry) => entry.name != this.state.name);
|
||||
|
||||
this.setState(info.data);
|
||||
@ -421,7 +427,7 @@ export class Edit extends Component{
|
||||
<Grid item xs={8} sm={8} md={3}>
|
||||
<TextField
|
||||
name="newPlaylistName"
|
||||
variant="outlined"
|
||||
variant="filled"
|
||||
label="Spotify Playlist"
|
||||
value={this.state.newPlaylistName}
|
||||
onChange={this.handleInputChange}
|
||||
@ -476,6 +482,7 @@ export class Edit extends Component{
|
||||
<TextField type="number"
|
||||
name="recommendation_sample"
|
||||
label="Recommendation Size"
|
||||
variant="filled"
|
||||
value={this.state.recommendation_sample}
|
||||
onChange={this.handleInputChange}></TextField>
|
||||
</Grid>
|
||||
@ -485,6 +492,7 @@ export class Edit extends Component{
|
||||
<TextField type="number"
|
||||
name="chart_limit"
|
||||
label="Chart Size"
|
||||
variant="filled"
|
||||
value={this.state.chart_limit}
|
||||
onChange={this.handleInputChange}></TextField>
|
||||
</Grid>
|
||||
|
85
src/js/Tag/New.js
Normal file
85
src/js/Tag/New.js
Normal file
@ -0,0 +1,85 @@
|
||||
import React, { Component } from "react";
|
||||
const axios = require('axios');
|
||||
|
||||
import { Card, Button, TextField, CardActions, CardContent, Typography, Grid } from '@material-ui/core';
|
||||
|
||||
import showMessage from "../Toast.js"
|
||||
|
||||
class NewTag extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tag_id: ''
|
||||
}
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
handleInputChange(event){
|
||||
this.setState({
|
||||
tag_id: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit(event){
|
||||
var tag_id = this.state.tag_id;
|
||||
this.setState({
|
||||
tag_id: ''
|
||||
});
|
||||
|
||||
if(tag_id.length != 0){
|
||||
axios.get('/api/tag')
|
||||
.then((response) => {
|
||||
var tag_ids = response.data.tags.map(entry => entry.tag_id)
|
||||
|
||||
var sameTag_id = tag_ids.includes(this.state.tag_id);
|
||||
if(sameTag_id == false){
|
||||
axios.post(`/api/tag/${tag_id}`).then((response) => {
|
||||
showMessage(`${tag_id} Created`);
|
||||
}).catch((error) => {
|
||||
showMessage(`Error Creating Tag (${error.response.status})`);
|
||||
});
|
||||
}else{
|
||||
showMessage('Named Tag Already Exists');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
showMessage(`Error Getting Tags (${error.response.status})`);
|
||||
});
|
||||
}else{
|
||||
showMessage('Enter Name');
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div style={{maxWidth: '500px', margin: 'auto', marginTop: '20px'}}>
|
||||
<Card align="center">
|
||||
<CardContent>
|
||||
<Grid container spacing={5}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h3">New Tag</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
label="Name"
|
||||
variant="outlined"
|
||||
onChange={this.handleInputChange}
|
||||
name="tag_id"
|
||||
value={this.state.tag_id}
|
||||
className="full-width" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button variant="contained" color="primary" className="full-width" onClick={this.handleSubmit}>Create</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default NewTag;
|
@ -15,14 +15,12 @@ class TagList extends Component {
|
||||
isLoading: true
|
||||
}
|
||||
this.getTags();
|
||||
this.handleRunTag = this.handleRunTag.bind(this);
|
||||
this.handleDeleteTag = this.handleDeleteTag.bind(this);
|
||||
this.handleRunAll = this.handleRunAll.bind(this);
|
||||
}
|
||||
|
||||
getTags(){
|
||||
var self = this;
|
||||
axios.get('/api/tags')
|
||||
axios.get('/api/tag')
|
||||
.then((response) => {
|
||||
|
||||
var tags = response.data.tags.slice();
|
||||
@ -34,69 +32,29 @@ class TagList extends Component {
|
||||
});
|
||||
|
||||
self.setState({
|
||||
playlists: tags,
|
||||
tags: tags,
|
||||
isLoading: false
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
showMessage(`Error Getting Playlists (${error.response.status})`);
|
||||
showMessage(`Error Getting Tags (${error.response.status})`);
|
||||
});
|
||||
}
|
||||
|
||||
handleRunTag(name, event){
|
||||
axios.get('/api/user')
|
||||
handleDeleteTag(tag_id, event){
|
||||
axios.delete(`/api/tag/${tag_id}`)
|
||||
.then((response) => {
|
||||
if(response.data.spotify_linked == true){
|
||||
axios.get('/api/tag/run', {params: {name: name}})
|
||||
.then((response) => {
|
||||
showMessage(`${name} ran`);
|
||||
})
|
||||
.catch((error) => {
|
||||
showMessage(`Error Running ${name} (${error.response.status})`);
|
||||
});
|
||||
}else{
|
||||
showMessage(`Link Spotify Before Running`);
|
||||
}
|
||||
}).catch((error) => {
|
||||
showMessage(`Error Running ${this.state.name} (${error.response.status})`);
|
||||
});
|
||||
}
|
||||
|
||||
handleDeleteTag(name, event){
|
||||
axios.delete('/api/playlist', { params: { name: name } })
|
||||
.then((response) => {
|
||||
showMessage(`${name} Deleted`);
|
||||
showMessage(`${tag_id} Deleted`);
|
||||
this.getTags();
|
||||
}).catch((error) => {
|
||||
showMessage(`Error Deleting ${name} (${error.response.status})`);
|
||||
});
|
||||
}
|
||||
|
||||
handleRunAll(event){
|
||||
axios.get('/api/user')
|
||||
.then((response) => {
|
||||
if(response.data.spotify_linked == true){
|
||||
axios.get('/api/tag/run/user')
|
||||
.then((response) => {
|
||||
showMessage("All Tags Ran");
|
||||
})
|
||||
.catch((error) => {
|
||||
showMessage(`Error Running All (${error.response.status})`);
|
||||
});
|
||||
}else{
|
||||
showMessage(`Link Spotify Before Running`);
|
||||
}
|
||||
}).catch((error) => {
|
||||
showMessage(`Error Running ${this.state.name} (${error.response.status})`);
|
||||
showMessage(`Error Deleting ${tag_id} (${error.response.status})`);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const grid = <TagGrid tags={this.state.tags}
|
||||
handleRunTag={this.handleRunTag}
|
||||
handleDeleteTag={this.handleDeleteTag}
|
||||
handleRunAll={this.handleRunAll}/>;
|
||||
handleDeleteTag={this.handleDeleteTag}/>;
|
||||
|
||||
return this.state.isLoading ? <CircularProgress /> : grid;
|
||||
}
|
||||
@ -116,7 +74,6 @@ function TagGrid(props){
|
||||
orientation="vertical"
|
||||
className="full-width">
|
||||
<Button component={Link} to='tags/new' >New</Button>
|
||||
<Button onClick={props.handleRunAll}>Run All</Button>
|
||||
</ButtonGroup>
|
||||
</Grid>
|
||||
{ props.tags.length == 0 ? (
|
||||
@ -146,9 +103,8 @@ function TagCard(props){
|
||||
<ButtonGroup
|
||||
color="primary"
|
||||
variant="contained">
|
||||
<Button component={Link} to={getTagLink(props.tag.name)}>View</Button>
|
||||
<Button onClick={(e) => props.handleRunTag(props.tag.name, e)}>Update</Button>
|
||||
<Button onClick={(e) => props.handleDeleteTag(props.tag.name, e)}>Delete</Button>
|
||||
<Button component={Link} to={getTagLink(props.tag.tag_id)}>View</Button>
|
||||
<Button onClick={(e) => props.handleDeleteTag(props.tag.tag_id, e)}>Delete</Button>
|
||||
</ButtonGroup>
|
||||
</CardActions>
|
||||
</Card>
|
||||
@ -157,7 +113,7 @@ function TagCard(props){
|
||||
}
|
||||
|
||||
function getTagLink(tagName){
|
||||
return `/app/tag/${tagName}/edit`;
|
||||
return `/app/tag/${tagName}`;
|
||||
}
|
||||
|
||||
export default TagList;
|
20
src/js/Tag/TagRouter.js
Normal file
20
src/js/Tag/TagRouter.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { Component } from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
|
||||
import TagList from "./TagList.js"
|
||||
import New from "./New.js"
|
||||
|
||||
class TagRouter extends Component {
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<Switch>
|
||||
<Route exact path={`${this.props.match.url}/`} component={TagList} />
|
||||
<Route path={`${this.props.match.url}/new`} component={New} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TagRouter;
|
389
src/js/Tag/View.js
Normal file
389
src/js/Tag/View.js
Normal file
@ -0,0 +1,389 @@
|
||||
import React, { Component } from "react";
|
||||
const axios = require('axios');
|
||||
|
||||
import { Card, Button, CircularProgress, CardActions, CardContent, FormControl, InputLabel, Select, Typography, Grid, TextField } from '@material-ui/core';
|
||||
import { Delete } from '@material-ui/icons';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
import showMessage from "../Toast.js";
|
||||
import BarChart from "../Maths/BarChart.js";
|
||||
import PieChart from "../Maths/PieChart.js";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
background: '#9e9e9e',
|
||||
color: '#212121',
|
||||
align: "center"
|
||||
},
|
||||
});
|
||||
|
||||
class View extends Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
tag_id: props.match.params.tag_id,
|
||||
tag: {
|
||||
name: "",
|
||||
tracks: [],
|
||||
albums: [],
|
||||
artists: []
|
||||
},
|
||||
|
||||
addType: 'artists',
|
||||
|
||||
name: '',
|
||||
artist: '',
|
||||
|
||||
isLoading: true
|
||||
}
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleRun = this.handleRun.bind(this);
|
||||
this.handleRemoveObj = this.handleRemoveObj.bind(this);
|
||||
|
||||
this.handleAdd = this.handleAdd.bind(this);
|
||||
this.handleChangeAddType = this.handleChangeAddType.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.getTag();
|
||||
var intervalId = setInterval(() => {this.getTag(false)}, 5000);
|
||||
var timeoutId = setTimeout(() => {clearInterval(this.state.intervalId)}, 300000);
|
||||
|
||||
this.setState({
|
||||
intervalId: intervalId,
|
||||
timeoutId: timeoutId
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
clearInterval(this.state.intervalId);
|
||||
clearTimeout(this.state.timeoutId);
|
||||
}
|
||||
|
||||
getTag(error_toast = true){
|
||||
axios.get(`/api/tag/${ this.state.tag_id }`)
|
||||
.then( (response) => {
|
||||
|
||||
var tag = response.data.tag;
|
||||
|
||||
tag.artists = tag.artists.sort((a, b) => {
|
||||
if(a.name.toLowerCase() < b.name.toLowerCase()) { return -1; }
|
||||
if(a.name.toLowerCase() > b.name.toLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
tag.albums = tag.albums.sort((a, b) => {
|
||||
if(a.artist.toLowerCase() < b.artist.toLowerCase()) { return -1; }
|
||||
if(a.artist.toLowerCase() > b.artist.toLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
tag.tracks = tag.tracks.sort((a, b) => {
|
||||
if(a.artist.toLowerCase() < b.artist.toLowerCase()) { return -1; }
|
||||
if(a.artist.toLowerCase() > b.artist.toLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
tag: response.data.tag,
|
||||
isLoading: false
|
||||
});
|
||||
})
|
||||
.catch( (error) => {
|
||||
if(error_toast) {
|
||||
showMessage(`Error Getting Tag Info (${error.response.status})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleInputChange(event){
|
||||
|
||||
this.setState({
|
||||
[event.target.name]: event.target.value
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
handleRun(event){
|
||||
axios.get('/api/user')
|
||||
.then((response) => {
|
||||
if(response.data.lastfm_username != null){
|
||||
axios.get(`/api/tag/${this.state.tag_id}/update`)
|
||||
.then((reponse) => {
|
||||
showMessage(`${this.state.name} Updating`);
|
||||
})
|
||||
.catch((error) => {
|
||||
showMessage(`Error Updating ${this.state.tag_id} (${error.response.status})`);
|
||||
});
|
||||
}else{
|
||||
showMessage(`Add a Last.fm Username In Settings`);
|
||||
}
|
||||
}).catch((error) => {
|
||||
showMessage(`Error Updating ${this.state.tag_id} (${error.response.status})`);
|
||||
});
|
||||
}
|
||||
|
||||
handleRemoveObj(music_obj, addType, event){
|
||||
var startingItems = this.state.tag[addType].slice();
|
||||
|
||||
var items = this.state.tag[addType].slice();
|
||||
items = items.filter((item) => {
|
||||
if(addType == 'albums' || addType == 'tracks') {
|
||||
return item.name.toLowerCase() != music_obj.name.toLowerCase() || item.artist.toLowerCase() != music_obj.artist.toLowerCase();
|
||||
}else{
|
||||
return item.name.toLowerCase() != music_obj.name.toLowerCase();
|
||||
}
|
||||
});
|
||||
|
||||
var tag = this.state.tag;
|
||||
tag[addType] = items;
|
||||
|
||||
this.setState({
|
||||
tag: tag
|
||||
});
|
||||
|
||||
axios.put(`/api/tag/${this.state.tag_id}`, {
|
||||
[addType]: items
|
||||
})
|
||||
.catch( (error) => {
|
||||
showMessage(`Error Removing ${music_obj.name} (${error.response.status})`);
|
||||
|
||||
redo_tag[addType] = startingItems;
|
||||
this.setState({
|
||||
tag: redo_tag
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleChangeAddType(type){
|
||||
this.setState({
|
||||
addType: type
|
||||
})
|
||||
}
|
||||
|
||||
handleAdd(){
|
||||
|
||||
var addType = this.state.addType;
|
||||
var music_obj = {
|
||||
name: this.state.name,
|
||||
artist: this.state.artist
|
||||
}
|
||||
|
||||
if(music_obj.name == ''){
|
||||
showMessage(`Enter Name`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(music_obj.artist == '' && addType != 'artists'){
|
||||
showMessage(`Enter Artist`);
|
||||
return;
|
||||
}
|
||||
|
||||
var list = this.state.tag[addType].slice().filter((item) => {
|
||||
if(addType != 'artists') {
|
||||
return item.name.toLowerCase() == music_obj.name.toLowerCase() && item.artist.toLowerCase() == music_obj.artist.toLowerCase();
|
||||
}else{
|
||||
return item.name.toLowerCase() == music_obj.name.toLowerCase();
|
||||
}
|
||||
});
|
||||
|
||||
if(list.length != 0) {
|
||||
showMessage(`${music_obj.name} already present`);
|
||||
return;
|
||||
}
|
||||
|
||||
list = this.state.tag[addType].slice();
|
||||
if(addType == 'artist'){
|
||||
list.push({
|
||||
name: music_obj.name
|
||||
});
|
||||
}else{
|
||||
list.push(music_obj);
|
||||
}
|
||||
|
||||
if(addType == "artists"){
|
||||
list = list.sort((a, b) => {
|
||||
if(a.name.toLowerCase() < b.name.toLowerCase()) { return -1; }
|
||||
if(a.name.toLowerCase() > b.name.toLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
}else{
|
||||
list = list.sort((a, b) => {
|
||||
if(a.artist.toLowerCase() < b.artist.toLowerCase()) { return -1; }
|
||||
if(a.artist.toLowerCase() > b.artist.toLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
var tag = this.state.tag;
|
||||
tag[addType] = list;
|
||||
|
||||
this.setState({
|
||||
tag: tag,
|
||||
name: '',
|
||||
artist: ''
|
||||
});
|
||||
|
||||
axios.put(`/api/tag/${this.state.tag_id}`, {
|
||||
[addType]: list
|
||||
})
|
||||
.catch( (error) => {
|
||||
showMessage(`Error Adding ${music_obj.name} (${error.response.status})`);
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
var all = [...this.state.tag.artists, ...this.state.tag.albums, ...this.state.tag.tracks];
|
||||
|
||||
var data = all.map((entry) => {
|
||||
return {
|
||||
"label": entry.name,
|
||||
"value": entry.count
|
||||
};
|
||||
}).sort((a, b) => {
|
||||
if(a.value < b.value) { return 1; }
|
||||
if(a.value > b.value) { return -1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
const table = (
|
||||
<div style={{maxWidth: '1000px', margin: 'auto', marginTop: '20px'}}>
|
||||
<Card align="center">
|
||||
<CardContent>
|
||||
<Typography variant="h2" color="textPrimary">{this.state.tag.name}</Typography>
|
||||
<Grid container spacing={5}>
|
||||
|
||||
{ this.state.tag.artists.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Artists</Typography></Grid> }
|
||||
{ this.state.tag.artists.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.artists} addType="artists"/> }
|
||||
|
||||
{ this.state.tag.albums.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Albums</Typography></Grid> }
|
||||
{ this.state.tag.albums.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.albums} addType="albums"/> }
|
||||
|
||||
{ this.state.tag.tracks.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Tracks</Typography></Grid> }
|
||||
{ this.state.tag.tracks.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.tracks} addType="tracks"/> }
|
||||
<Grid item xs={12} sm={this.state.addType != 'artists' ? 3 : 4} md={this.state.addType != 'artists' ? 3 : 4}>
|
||||
<TextField
|
||||
name="name"
|
||||
label="Name"
|
||||
variant="filled"
|
||||
value={this.state.name}
|
||||
onChange={this.handleInputChange}></TextField>
|
||||
</Grid>
|
||||
{ this.state.addType != 'artists' &&
|
||||
<Grid item xs={12} sm={3} md={4}>
|
||||
<TextField
|
||||
name="artist"
|
||||
label="Artist"
|
||||
variant="filled"
|
||||
value={this.state.artist}
|
||||
onChange={this.handleInputChange}></TextField>
|
||||
</Grid>
|
||||
}
|
||||
<Grid item xs={12} sm={this.state.addType != 'artists' ? 2 : 4} md={this.state.addType != 'artists' ? 2 : 4}>
|
||||
<FormControl>
|
||||
<InputLabel htmlFor="addType">Type</InputLabel>
|
||||
<Select
|
||||
native
|
||||
value={this.state.addType}
|
||||
onChange={this.handleInputChange}
|
||||
inputProps={{
|
||||
name: "addType",
|
||||
id: "addType",
|
||||
}}
|
||||
>
|
||||
<option value="artists">Artist</option>
|
||||
<option value="albums">Album</option>
|
||||
<option value="tracks">Track</option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={this.state.addType != 'artists' ? 3 : 4} md={this.state.addType != 'artists' ? 3 : 4}>
|
||||
<Button variant="contained" onClick={this.handleAdd} className="full-width">Add</Button>
|
||||
</Grid>
|
||||
<StatsCard count={this.state.tag.count} proportion={this.state.tag.proportion}></StatsCard>
|
||||
<Grid item xs={12}>
|
||||
<PieChart data={data}/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<BarChart data={data} title='scrobbles'/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button onClick={this.handleRun} variant="contained" color="primary" className="full-width" >Update</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
return this.state.isLoading ? <CircularProgress /> : table;
|
||||
}
|
||||
}
|
||||
|
||||
export default View;
|
||||
|
||||
function ListBlock(props) {
|
||||
return <Grid container
|
||||
spacing={3}
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
alignItems="flex-start"
|
||||
style={{padding: '24px'}}>
|
||||
{props.list.map((music_obj) => <BlockGridItem music_obj={ music_obj } key={ music_obj.name }
|
||||
handler={ props.handler } addType={ props.addType }/>)}
|
||||
</Grid>
|
||||
}
|
||||
|
||||
function BlockGridItem (props) {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Grid item xs={12} sm={4} md={3}>
|
||||
<Card variant="outlined" className={classes.root}>
|
||||
<CardContent>
|
||||
<Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4" color="textSecondary" className={classes.root}>{ props.music_obj.name }</Typography>
|
||||
</Grid>
|
||||
{ 'artist' in props.music_obj &&
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="body1" color="textSecondary" className={classes.root}>{ props.music_obj.artist }</Typography>
|
||||
</Grid>
|
||||
}
|
||||
{ 'count' in props.music_obj &&
|
||||
<Grid item xs={8}>
|
||||
<Typography variant="h4" color="textPrimary" className={classes.root}>{ props.music_obj.count }</Typography>
|
||||
</Grid>
|
||||
}
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button className="full-width" color="secondary" variant="contained" aria-label="delete" onClick={(e) => props.handler(props.music_obj, props.addType, e)} startIcon={<Delete />}>
|
||||
Delete
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
function StatsCard (props) {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Card variant="outlined" className={classes.root}>
|
||||
<CardContent>
|
||||
<Grid container spacing={10}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h1" color="textPrimary" className={classes.root}>= { props.count }</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4" color="textSecondary" className={classes.root}>{ Math.round(props.proportion) }%</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user