diff --git a/music/api/tag.py b/music/api/tag.py index c7aa9cf..08d7b16 100644 --- a/music/api/tag.py +++ b/music/api/tag.py @@ -38,7 +38,7 @@ def tag_route(tag_id, user=None): def get_tag(tag_id, user): - logger.info(f'retriving {tag_id} for {user.username}') + logger.info(f'retrieving {tag_id} for {user.username}') db_tag = Tag.collection.parent(user.key).filter('tag_id', '==', tag_id).get() if db_tag is not None: @@ -62,6 +62,9 @@ def put_tag(tag_id, user): if request_json.get('name'): db_tag.name = request_json['name'].strip() + if request_json.get('time_objects') is not None: + db_tag.time_objects = request_json['time_objects'] + if request_json.get('tracks') is not None: db_tag.tracks = [ { diff --git a/music/model/tag.py b/music/model/tag.py index bb9faa7..9182ed3 100644 --- a/music/model/tag.py +++ b/music/model/tag.py @@ -1,5 +1,5 @@ from fireo.models import Model -from fireo.fields import TextField, DateTime, NumberField, ListField +from fireo.fields import TextField, DateTime, NumberField, ListField, BooleanField class Tag(Model): @@ -20,6 +20,10 @@ class Tag(Model): last_updated = DateTime() + time_objects = BooleanField(default=False) + total_time = TextField(default='00:00:00') + total_time_ms = NumberField(default=0) + def to_dict(self): to_return = super().to_dict() diff --git a/music/tasks/update_tag.py b/music/tasks/update_tag.py index c605ce1..45701d0 100644 --- a/music/tasks/update_tag.py +++ b/music/tasks/update_tag.py @@ -7,6 +7,8 @@ from music.model.tag import Tag from fmframework.net.network import LastFMNetworkException +from spotfm.timer import time, seconds_to_time_str + logger = logging.getLogger(__name__) @@ -29,26 +31,48 @@ def update_tag(username, tag_id): return net = database.get_authed_lastfm_network(user) - if net is None: - logger.error(f'no last.fm network returned for {username}') + logger.error(f'no last.fm network returned for {username} / {tag_id}') return - tag_count = 0 + if tag.time_objects: + if user.spotify_linked: + spotnet = database.get_authed_spotify_network(user) + else: + logger.warning(f'timing objects requested but no spotify linked {username} / {tag_id}') + + tag.count = 0 + tag.total_time_ms = 0 + try: user_scrobbles = net.user_scrobble_count() except LastFMNetworkException: logger.exception(f'error retrieving scrobble count {username} / {tag_id}') - user_scrobbles = 0 + user_scrobbles = 1 artists = [] for artist in tag.artists: try: - net_artist = net.artist(name=artist['name']) + if tag.time_objects and user.spotify_linked: + total_ms, timed_tracks = time(spotnet=spotnet, fmnet=net, + artist=artist['name'], username=user.lastfm_username, + return_tracks=True) + scrobbles = sum(i[0].user_scrobbles for i in timed_tracks) - if net_artist is not None: - artist['count'] = net_artist.user_scrobbles - tag_count += net_artist.user_scrobbles + artist['time_ms'] = total_ms + artist['time'] = seconds_to_time_str(milliseconds=total_ms) + tag.total_time_ms += total_ms + + else: + net_artist = net.artist(name=artist['name']) + + if net_artist is not None: + scrobbles = net_artist.user_scrobbles + else: + scrobbles = 0 + + artist['count'] = scrobbles + tag.count += scrobbles except LastFMNetworkException: logger.exception(f'error during artist retrieval {username} / {tag_id}') @@ -57,13 +81,28 @@ def update_tag(username, tag_id): albums = [] for album in tag.albums: try: - net_album = net.album(name=album['name'], artist=album['artist']) + if tag.time_objects and user.spotify_linked: + total_ms, timed_tracks = time(spotnet=spotnet, fmnet=net, + album=album['name'], artist=album['artist'], + username=user.lastfm_username, return_tracks=True) + scrobbles = sum(i[0].user_scrobbles for i in timed_tracks) - if net_album is not None: - album['count'] = net_album.user_scrobbles + album['time_ms'] = total_ms + album['time'] = seconds_to_time_str(milliseconds=total_ms) + tag.total_time_ms += total_ms - if album['artist'].lower() not in [i.lower() for i in [j['name'] for j in artists]]: - tag_count += net_album.user_scrobbles + else: + net_album = net.album(name=album['name'], artist=album['artist']) + + if net_album is not None: + scrobbles = net_album.user_scrobbles + else: + scrobbles = 0 + + album['count'] = scrobbles + + if album['artist'].lower() not in [i.lower() for i in [j['name'] for j in artists]]: + tag.count += scrobbles except LastFMNetworkException: logger.exception(f'error during album retrieval {username} / {tag_id}') @@ -72,13 +111,28 @@ def update_tag(username, tag_id): tracks = [] for track in tag.tracks: try: - net_track = net.track(name=track['name'], artist=track['artist']) + if tag.time_objects and user.spotify_linked: + total_ms, timed_tracks = time(spotnet=spotnet, fmnet=net, + track=track['name'], artist=track['artist'], + username=user.lastfm_username, return_tracks=True) + scrobbles = sum(i[0].user_scrobbles for i in timed_tracks) - if net_track is not None: - track['count'] = net_track.user_scrobbles + track['time_ms'] = total_ms + track['time'] = seconds_to_time_str(milliseconds=total_ms) + tag.total_time_ms += total_ms - if track['artist'].lower() not in [i.lower() for i in [j['name'] for j in artists]]: - tag_count += net_track.user_scrobbles + else: + net_track = net.track(name=track['name'], artist=track['artist']) + + if net_track is not None: + scrobbles = net_track.user_scrobbles + else: + scrobbles = 0 + + track['count'] = scrobbles + + if track['artist'].lower() not in [i.lower() for i in [j['name'] for j in artists]]: + tag.count += scrobbles except LastFMNetworkException: logger.exception(f'error during track retrieval {username} / {tag_id}') @@ -88,9 +142,9 @@ def update_tag(username, tag_id): tag.albums = albums tag.artists = artists + tag.total_time = seconds_to_time_str(milliseconds=tag.total_time_ms) tag.total_user_scrobbles = user_scrobbles - tag.count = tag_count - tag.proportion = (tag_count / user_scrobbles) * 100 + tag.proportion = (tag.count / user_scrobbles) * 100 tag.last_updated = datetime.utcnow() tag.update() diff --git a/requirements.txt b/requirements.txt index 8197f51..285e817 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ astroid==2.4.2 +beautifulsoup4==4.9.1 cachetools==4.1.0 certifi==2020.6.20 chardet==3.0.4 click==7.1.2 +colorama==0.4.3 fireo==1.3.3 Flask==1.1.2 google-api-core==1.21.0 @@ -32,6 +34,7 @@ pytz==2020.1 requests==2.24.0 rsa==4.6 six==1.15.0 +soupsieve==2.0.1 tabulate==0.8.7 toml==0.10.1 urllib3==1.25.9 diff --git a/src/js/Tag/View.js b/src/js/Tag/View.js index 92cfcf0..5e52a7d 100644 --- a/src/js/Tag/View.js +++ b/src/js/Tag/View.js @@ -1,7 +1,7 @@ import React, { Component } from "react"; const axios = require('axios'); -import { Card, Button, CircularProgress, CardActions, CardContent, FormControl, InputLabel, Select, Typography, Grid, TextField, MenuItem } from '@material-ui/core'; +import { Card, Button, CircularProgress, CardActions, CardContent, FormControl, InputLabel, Select, Typography, Grid, TextField, MenuItem, FormControlLabel, Checkbox } from '@material-ui/core'; import { Delete } from '@material-ui/icons'; import { makeStyles } from '@material-ui/core/styles'; @@ -41,25 +41,28 @@ class View extends Component{ this.handleRun = this.handleRun.bind(this); this.handleRemoveObj = this.handleRemoveObj.bind(this); + this.handleCheckChange = this.handleCheckChange.bind(this); + this.makeNetworkUpdate = this.makeNetworkUpdate.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); + // var intervalId = setInterval(() => {this.getTag(false)}, 5000); + // var timeoutId = setTimeout(() => {clearInterval(this.state.intervalId)}, 300000); - this.setState({ - intervalId: intervalId, - timeoutId: timeoutId - }); + // this.setState({ + // intervalId: intervalId, + // timeoutId: timeoutId + // }); } - componentWillUnmount(){ - clearInterval(this.state.intervalId); - clearTimeout(this.state.timeoutId); - } + // componentWillUnmount(){ + // clearInterval(this.state.intervalId); + // clearTimeout(this.state.timeoutId); + // } getTag(error_toast = true){ axios.get(`/api/tag/${ this.state.tag_id }`) @@ -105,6 +108,24 @@ class View extends Component{ } + handleCheckChange(event){ + let payload = {...this.state.tag}; + payload[event.target.name] = event.target.checked; + + this.setState({tag: payload}); + + switch(event.target.name){ + default: + this.makeNetworkUpdate({[event.target.name]: event.target.checked}); + } + } + + makeNetworkUpdate(changes){ + axios.put(`/api/tag/${this.state.tag_id}`, changes).catch((error) => { + showMessage(`Error updating ${Object.keys(changes).join(", ")} (${error.response.status})`); + }); + } + handleRun(event){ axios.get('/api/user') .then((response) => { @@ -256,13 +277,13 @@ class View extends Component{ { this.state.tag.artists.length > 0 && Artists } - { this.state.tag.artists.length > 0 && } + { this.state.tag.artists.length > 0 && } { this.state.tag.albums.length > 0 && Albums } - { this.state.tag.albums.length > 0 && } + { this.state.tag.albums.length > 0 && } { this.state.tag.tracks.length > 0 && Tracks } - { this.state.tag.tracks.length > 0 && } + { this.state.tag.tracks.length > 0 && } - + + + } + label="Time Tag" + labelPlacement="bottom" + /> + + @@ -331,7 +361,7 @@ function ListBlock(props) { alignItems="flex-start" style={{padding: '24px'}}> {props.list.map((music_obj) => )} + handler={ props.handler } addType={ props.addType } showTime={ props.showTime }/>)} } @@ -352,7 +382,12 @@ function BlockGridItem (props) { } { 'count' in props.music_obj && - { props.music_obj.count } + 📈 { props.music_obj.count } + + } + { 'time' in props.music_obj && props.showTime && + + 🕒 { props.music_obj.time } } @@ -375,11 +410,16 @@ function StatsCard (props) { - = { props.count } + 📈 { props.count } { props.proportion.toFixed(2) }% + {props.showTime && + + 🕒 { props.time } + + }