added timing for tags

This commit is contained in:
aj 2020-08-13 17:48:20 +01:00
parent 5096c3c2a9
commit d5f9681fd8
5 changed files with 144 additions and 40 deletions

View File

@ -38,7 +38,7 @@ def tag_route(tag_id, user=None):
def get_tag(tag_id, user): 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() db_tag = Tag.collection.parent(user.key).filter('tag_id', '==', tag_id).get()
if db_tag is not None: if db_tag is not None:
@ -62,6 +62,9 @@ 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()
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: if request_json.get('tracks') is not None:
db_tag.tracks = [ db_tag.tracks = [
{ {

View File

@ -1,5 +1,5 @@
from fireo.models import Model 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): class Tag(Model):
@ -20,6 +20,10 @@ class Tag(Model):
last_updated = DateTime() 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): def to_dict(self):
to_return = super().to_dict() to_return = super().to_dict()

View File

@ -7,6 +7,8 @@ from music.model.tag import Tag
from fmframework.net.network import LastFMNetworkException from fmframework.net.network import LastFMNetworkException
from spotfm.timer import time, seconds_to_time_str
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -29,26 +31,48 @@ def update_tag(username, tag_id):
return return
net = database.get_authed_lastfm_network(user) net = database.get_authed_lastfm_network(user)
if net is None: 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 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: try:
user_scrobbles = net.user_scrobble_count() user_scrobbles = net.user_scrobble_count()
except LastFMNetworkException: except LastFMNetworkException:
logger.exception(f'error retrieving scrobble count {username} / {tag_id}') logger.exception(f'error retrieving scrobble count {username} / {tag_id}')
user_scrobbles = 0 user_scrobbles = 1
artists = [] artists = []
for artist in tag.artists: for artist in tag.artists:
try: try:
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)
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']) net_artist = net.artist(name=artist['name'])
if net_artist is not None: if net_artist is not None:
artist['count'] = net_artist.user_scrobbles scrobbles = net_artist.user_scrobbles
tag_count += net_artist.user_scrobbles else:
scrobbles = 0
artist['count'] = scrobbles
tag.count += scrobbles
except LastFMNetworkException: except LastFMNetworkException:
logger.exception(f'error during artist retrieval {username} / {tag_id}') logger.exception(f'error during artist retrieval {username} / {tag_id}')
@ -57,13 +81,28 @@ def update_tag(username, tag_id):
albums = [] albums = []
for album in tag.albums: for album in tag.albums:
try: try:
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)
album['time_ms'] = total_ms
album['time'] = seconds_to_time_str(milliseconds=total_ms)
tag.total_time_ms += total_ms
else:
net_album = net.album(name=album['name'], artist=album['artist']) net_album = net.album(name=album['name'], artist=album['artist'])
if net_album is not None: if net_album is not None:
album['count'] = net_album.user_scrobbles 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]]: if album['artist'].lower() not in [i.lower() for i in [j['name'] for j in artists]]:
tag_count += net_album.user_scrobbles tag.count += scrobbles
except LastFMNetworkException: except LastFMNetworkException:
logger.exception(f'error during album retrieval {username} / {tag_id}') logger.exception(f'error during album retrieval {username} / {tag_id}')
@ -72,13 +111,28 @@ def update_tag(username, tag_id):
tracks = [] tracks = []
for track in tag.tracks: for track in tag.tracks:
try: try:
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)
track['time_ms'] = total_ms
track['time'] = seconds_to_time_str(milliseconds=total_ms)
tag.total_time_ms += total_ms
else:
net_track = net.track(name=track['name'], artist=track['artist']) net_track = net.track(name=track['name'], artist=track['artist'])
if net_track is not None: if net_track is not None:
track['count'] = net_track.user_scrobbles 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]]: if track['artist'].lower() not in [i.lower() for i in [j['name'] for j in artists]]:
tag_count += net_track.user_scrobbles tag.count += scrobbles
except LastFMNetworkException: except LastFMNetworkException:
logger.exception(f'error during track retrieval {username} / {tag_id}') logger.exception(f'error during track retrieval {username} / {tag_id}')
@ -88,9 +142,9 @@ def update_tag(username, tag_id):
tag.albums = albums tag.albums = albums
tag.artists = artists tag.artists = artists
tag.total_time = seconds_to_time_str(milliseconds=tag.total_time_ms)
tag.total_user_scrobbles = user_scrobbles 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.last_updated = datetime.utcnow()
tag.update() tag.update()

View File

@ -1,8 +1,10 @@
astroid==2.4.2 astroid==2.4.2
beautifulsoup4==4.9.1
cachetools==4.1.0 cachetools==4.1.0
certifi==2020.6.20 certifi==2020.6.20
chardet==3.0.4 chardet==3.0.4
click==7.1.2 click==7.1.2
colorama==0.4.3
fireo==1.3.3 fireo==1.3.3
Flask==1.1.2 Flask==1.1.2
google-api-core==1.21.0 google-api-core==1.21.0
@ -32,6 +34,7 @@ pytz==2020.1
requests==2.24.0 requests==2.24.0
rsa==4.6 rsa==4.6
six==1.15.0 six==1.15.0
soupsieve==2.0.1
tabulate==0.8.7 tabulate==0.8.7
toml==0.10.1 toml==0.10.1
urllib3==1.25.9 urllib3==1.25.9

View File

@ -1,7 +1,7 @@
import React, { Component } from "react"; import React, { Component } from "react";
const axios = require('axios'); 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 { Delete } from '@material-ui/icons';
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles';
@ -41,25 +41,28 @@ class View extends Component{
this.handleRun = this.handleRun.bind(this); this.handleRun = this.handleRun.bind(this);
this.handleRemoveObj = this.handleRemoveObj.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.handleAdd = this.handleAdd.bind(this);
this.handleChangeAddType = this.handleChangeAddType.bind(this); this.handleChangeAddType = this.handleChangeAddType.bind(this);
} }
componentDidMount(){ componentDidMount(){
this.getTag(); this.getTag();
var intervalId = setInterval(() => {this.getTag(false)}, 5000); // var intervalId = setInterval(() => {this.getTag(false)}, 5000);
var timeoutId = setTimeout(() => {clearInterval(this.state.intervalId)}, 300000); // var timeoutId = setTimeout(() => {clearInterval(this.state.intervalId)}, 300000);
this.setState({ // this.setState({
intervalId: intervalId, // intervalId: intervalId,
timeoutId: timeoutId // timeoutId: timeoutId
}); // });
} }
componentWillUnmount(){ // componentWillUnmount(){
clearInterval(this.state.intervalId); // clearInterval(this.state.intervalId);
clearTimeout(this.state.timeoutId); // clearTimeout(this.state.timeoutId);
} // }
getTag(error_toast = true){ getTag(error_toast = true){
axios.get(`/api/tag/${ this.state.tag_id }`) 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){ handleRun(event){
axios.get('/api/user') axios.get('/api/user')
.then((response) => { .then((response) => {
@ -256,13 +277,13 @@ class View extends Component{
<Grid container spacing={5}> <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 && <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.artists.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.artists} addType="artists" showTime={this.state.tag.time_objects}/> }
{ this.state.tag.albums.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Albums</Typography></Grid> } { 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.albums.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.albums} addType="albums" showTime={this.state.tag.time_objects}/> }
{ this.state.tag.tracks.length > 0 && <Grid item xs={12} ><Typography color="textSecondary" variant="h4">Tracks</Typography></Grid> } { 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"/> } { this.state.tag.tracks.length > 0 && <ListBlock handler={this.handleRemoveObj} list={this.state.tag.tracks} addType="tracks" showTime={this.state.tag.time_objects}/> }
<Grid item xs={12} sm={this.state.addType != 'artists' ? 3 : 4} md={this.state.addType != 'artists' ? 3 : 4}> <Grid item xs={12} sm={this.state.addType != 'artists' ? 3 : 4} md={this.state.addType != 'artists' ? 3 : 4}>
<TextField <TextField
name="name" name="name"
@ -301,7 +322,16 @@ class View extends Component{
<Grid item xs={12} sm={this.state.addType != 'artists' ? 3 : 4} md={this.state.addType != 'artists' ? 3 : 4}> <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> <Button variant="contained" onClick={this.handleAdd} className="full-width">Add</Button>
</Grid> </Grid>
<StatsCard count={this.state.tag.count} proportion={this.state.tag.proportion}></StatsCard> <Grid item xs={12}>
<FormControlLabel
control={
<Checkbox color="primary" checked={this.state.tag.time_objects} name="time_objects" onChange={this.handleCheckChange} />
}
label="Time Tag"
labelPlacement="bottom"
/>
</Grid>
<StatsCard count={this.state.tag.count} proportion={this.state.tag.proportion} showTime={this.state.tag.time_objects} time={this.state.tag.total_time}></StatsCard>
<Grid item xs={12}> <Grid item xs={12}>
<PieChart data={data}/> <PieChart data={data}/>
</Grid> </Grid>
@ -331,7 +361,7 @@ function ListBlock(props) {
alignItems="flex-start" alignItems="flex-start"
style={{padding: '24px'}}> style={{padding: '24px'}}>
{props.list.map((music_obj) => <BlockGridItem music_obj={ music_obj } key={ music_obj.name } {props.list.map((music_obj) => <BlockGridItem music_obj={ music_obj } key={ music_obj.name }
handler={ props.handler } addType={ props.addType }/>)} handler={ props.handler } addType={ props.addType } showTime={ props.showTime }/>)}
</Grid> </Grid>
} }
@ -352,7 +382,12 @@ function BlockGridItem (props) {
} }
{ 'count' in props.music_obj && { 'count' in props.music_obj &&
<Grid item xs={8}> <Grid item xs={8}>
<Typography variant="h4" color="textPrimary" className={classes.root}>{ props.music_obj.count }</Typography> <Typography variant="h4" color="textPrimary" className={classes.root}>📈 { props.music_obj.count }</Typography>
</Grid>
}
{ 'time' in props.music_obj && props.showTime &&
<Grid item xs={8}>
<Typography variant="body1" color="textSecondary" className={classes.root}>🕒 { props.music_obj.time }</Typography>
</Grid> </Grid>
} }
</Grid> </Grid>
@ -375,11 +410,16 @@ function StatsCard (props) {
<CardContent> <CardContent>
<Grid container spacing={10}> <Grid container spacing={10}>
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="h1" color="textPrimary" className={classes.root}>= { props.count }</Typography> <Typography variant="h1" color="textPrimary" className={classes.root}>📈 { props.count }</Typography>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="h4" color="textSecondary" className={classes.root}>{ props.proportion.toFixed(2) }%</Typography> <Typography variant="h4" color="textSecondary" className={classes.root}>{ props.proportion.toFixed(2) }%</Typography>
</Grid> </Grid>
{props.showTime &&
<Grid item xs={12}>
<Typography variant="h4" color="textSecondary" className={classes.root}>🕒 { props.time }</Typography>
</Grid>
}
</Grid> </Grid>
</CardContent> </CardContent>
</Card> </Card>