added timing for tags
This commit is contained in:
parent
5096c3c2a9
commit
d5f9681fd8
@ -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 = [
|
||||||
{
|
{
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user