1
0
mirror of https://github.com/krateng/maloja.git synced 2023-08-10 21:12:55 +03:00
maloja/database.py

293 lines
7.2 KiB
Python
Raw Normal View History

2018-11-30 15:39:12 +03:00
from bottle import route, get, post, run, template, static_file, request, response, FormsDict
2018-11-24 18:29:24 +03:00
from importlib.machinery import SourceFileLoader
2018-11-27 21:05:50 +03:00
import urllib
2018-11-24 18:29:24 +03:00
import waitress
import os
import datetime
2018-11-28 19:45:52 +03:00
from cleanup import *
2018-11-30 17:44:30 +03:00
from utilities import *
import sys
2018-11-24 18:29:24 +03:00
SCROBBLES = [] # Format: tuple(track_ref,timestamp,saved)
ARTISTS = [] # Format: artist
TRACKS = [] # Format: tuple(frozenset(artist_ref,...),title)
timestamps = set()
2018-11-28 19:45:52 +03:00
c = CleanerAgent()
2018-11-30 17:44:30 +03:00
clients = []
2018-11-28 19:45:52 +03:00
2018-11-28 15:02:43 +03:00
lastsync = 0
2018-11-30 17:44:30 +03:00
### symmetric keys are fine for now since we hopefully use HTTPS
def loadAPIkeys():
global clients
2018-11-30 18:01:32 +03:00
createTSV("clients/authenticated_machines.tsv")
2018-11-30 17:44:30 +03:00
clients = parseTSV("clients/authenticated_machines.tsv","string","string")
2018-11-30 17:44:30 +03:00
def checkAPIkey(k):
return (k in [k for [k,d] in clients])
def getScrobbleObject(o):
track = getTrackObject(TRACKS[o[0]])
return {"artists":track["artists"],"title":track["title"],"time":o[1]}
def getArtistObject(o):
return o
def getTrackObject(o):
artists = [getArtistObject(ARTISTS[a]) for a in o[0]]
return {"artists":artists,"title":o[1]}
def createScrobble(artists,title,time):
while (time in timestamps):
time += 1
2018-11-29 18:09:46 +03:00
timestamps.add(time)
i = getTrackID(artists,title)
obj = (i,time,False)
SCROBBLES.append(obj)
def readScrobble(artists,title,time):
while (time in timestamps):
time += 1
2018-11-29 18:09:46 +03:00
timestamps.add(time)
i = getTrackID(artists,title)
obj = (i,time,True)
SCROBBLES.append(obj)
def getArtistID(name):
obj = name
try:
i = ARTISTS.index(obj)
except:
i = len(ARTISTS)
ARTISTS.append(obj)
return i
def getTrackID(artists,title):
artistset = set()
for a in artists:
artistset.add(getArtistID(name=a))
obj = (frozenset(artistset),title)
try:
i = TRACKS.index(obj)
except:
i = len(TRACKS)
TRACKS.append(obj)
return i
2018-11-24 18:29:24 +03:00
@route("/scrobbles")
def get_scrobbles():
keys = request.query
2018-11-27 18:21:33 +03:00
r = db_query(artist=keys.get("artist"),track=keys.get("track"),since=keys.get("since"),to=keys.get("to"))
2018-11-24 18:29:24 +03:00
2018-11-25 21:31:03 +03:00
return {"list":r} ##json can't be a list apparently???
2018-11-24 18:29:24 +03:00
@route("/tracks")
def get_tracks():
artist = request.query.get("artist")
2018-11-30 15:39:12 +03:00
if artist is not None:
artistid = ARTISTS.index(artist)
2018-11-28 20:44:33 +03:00
# Option 1
2018-11-28 20:44:33 +03:00
ls = [getTrackObject(t) for t in TRACKS if (artistid in t[0]) or (artistid==None)]
# Option 2 is a bit more elegant but much slower
#tracklist = [getTrackObject(t) for t in TRACKS]
#ls = [t for t in tracklist if (artist in t["artists"]) or (artist==None)]
2018-11-25 16:49:53 +03:00
return {"list":ls}
@route("/artists")
def get_artists():
2018-11-25 21:31:03 +03:00
2018-11-25 16:49:53 +03:00
return {"list":ARTISTS}
@route("/charts")
def get_charts():
since = request.query.get("since")
to = request.query.get("to")
#better do something here to sum up the totals on db level (before converting to dicts)
#results = db_query(since=since,to=to)
#return {"list":results}
2018-11-26 18:21:07 +03:00
2018-11-30 15:39:12 +03:00
@get("/newscrobble")
def pseudo_post_scrobble():
2018-11-27 21:05:50 +03:00
keys = FormsDict.decode(request.query) # The Dal★Shabet handler
2018-11-26 18:21:07 +03:00
artists = keys.get("artist")
title = keys.get("title")
2018-11-28 19:45:52 +03:00
try:
time = int(keys.get("time"))
except:
2018-11-28 17:33:30 +03:00
time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
2018-11-28 19:45:52 +03:00
(artists,title) = c.fullclean(artists,title)
## this is necessary for localhost testing
response.set_header("Access-Control-Allow-Origin","*")
2018-11-26 18:21:07 +03:00
createScrobble(artists,title,time)
2018-11-28 15:02:43 +03:00
if (time - lastsync) > 3600:
sync()
return ""
2018-11-30 15:39:12 +03:00
@post("/newscrobble")
def post_scrobble():
keys = FormsDict.decode(request.forms) # The Dal★Shabet handler
artists = keys.get("artist")
title = keys.get("title")
2018-11-30 17:44:30 +03:00
apikey = keys.get("key")
if not (checkAPIkey(apikey)):
response.status = 403
return ""
2018-11-30 15:39:12 +03:00
try:
time = int(keys.get("time"))
except:
time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
(artists,title) = c.fullclean(artists,title)
## this is necessary for localhost testing
response.set_header("Access-Control-Allow-Origin","*")
createScrobble(artists,title,time)
if (time - lastsync) > 3600:
sync()
return ""
2018-11-28 15:02:43 +03:00
@route("/sync")
def abouttoshutdown():
2018-11-28 15:02:43 +03:00
sync()
#sys.exit()
2018-11-24 18:29:24 +03:00
# Starts the server
def runserver(DATABASE_PORT):
2018-11-28 15:02:43 +03:00
global lastsync
lastsync = time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
#reload()
#buildh()
build_db()
2018-11-24 18:29:24 +03:00
2018-11-30 17:44:30 +03:00
loadAPIkeys()
2018-11-24 18:29:24 +03:00
run(host='0.0.0.0', port=DATABASE_PORT, server='waitress')
def build_db():
2018-11-24 18:29:24 +03:00
2018-11-25 21:31:03 +03:00
global SCROBBLES
SCROBBLESNEW = []
for t in SCROBBLES:
if not t[2]:
SCROBBLESNEW.append(t)
SCROBBLES = SCROBBLESNEW
for f in os.listdir("logs/"):
if not (".tsv" in f):
continue
logfile = open("logs/" + f)
for l in logfile:
l = l.replace("\n","")
data = l.split("\t")
## saving album in the scrobbles is supported, but for now we don't use it. It shouldn't be a defining part of the track (same song from Album or EP), but derived information
artists = data[1].split("")
#album = data[3]
title = data[2]
time = int(data[0])
readScrobble(artists,title,time)
2018-11-24 18:29:24 +03:00
# Saves all cached entries to disk
2018-11-28 15:02:43 +03:00
def sync():
2018-11-27 21:05:50 +03:00
for idx in range(len(SCROBBLES)):
if not SCROBBLES[idx][2]:
2018-11-25 21:31:03 +03:00
2018-11-27 21:05:50 +03:00
t = getScrobbleObject(SCROBBLES[idx])
2018-11-25 21:31:03 +03:00
artistss = "".join(t["artists"])
2018-11-24 18:29:24 +03:00
timestamp = datetime.date.fromtimestamp(t["time"])
entry = "\t".join([str(t["time"]),artistss,t["title"]])
2018-11-24 18:29:24 +03:00
monthfile = open("logs/" + str(timestamp.year) + "_" + str(timestamp.month) + ".tsv","a")
2018-11-24 18:29:24 +03:00
monthfile.write(entry)
monthfile.write("\n")
monthfile.close()
2018-11-27 21:05:50 +03:00
SCROBBLES[idx] = (SCROBBLES[idx][0],SCROBBLES[idx][1],True)
2018-11-24 18:29:24 +03:00
2018-11-28 15:02:43 +03:00
global lastsync
lastsync = time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
2018-11-28 17:33:30 +03:00
print("Database saved to disk.")
2018-11-28 15:02:43 +03:00
2018-11-24 18:29:24 +03:00
# Queries the database
def db_query(artist=None,track=None,since=0,to=9999999999):
2018-11-24 18:29:24 +03:00
if isinstance(since, str):
sdate = [int(x) for x in since.split("/")]
date = [1970,1,1,0,0]
date[:len(sdate)] = sdate
since = int(datetime.datetime(date[0],date[1],date[2],date[3],date[4],tzinfo=datetime.timezone.utc).timestamp())
if isinstance(to, str):
sdate = [int(x) for x in to.split("/")]
date = [1970,1,1,0,0]
date[:len(sdate)] = sdate
to = int(datetime.datetime(date[0],date[1],date[2],date[3],date[4],tzinfo=datetime.timezone.utc).timestamp())
2018-11-27 18:21:33 +03:00
if (since==None):
since = 0
if (to==None):
to = 9999999999
# this is not meant as a search function. we *can* query the db with a string, but it only works if it matches exactly (and title string simply picks the first track with that name)
if isinstance(artist, str):
artist = ARTISTS.index(artist)
if isinstance(track, str):
track = TRACKS.index(track)
return [getScrobbleObject(s) for s in SCROBBLES if (s[0] == track or track==None) and (artist in TRACKS[s[0]][0] or artist==None) and (since < s[1] < to)]
# pointless to check for artist when track is checked because every track has a fixed set of artists, but it's more elegant this way
2018-11-24 18:29:24 +03:00
#thingsweneed = ["artists","title","time"]
#return [{key:t[key] for key in thingsweneed} for t in DATABASE if (artist in t["artists"] or artist==None) and (t["title"]==title or title==None) and (since < t["time"] < to)]
2018-11-24 18:29:24 +03:00
# Search for strings
def db_search(query,type=None):
if type=="ARTIST":
results = []
for a in ARTISTS:
if query.lower() in a.lower():
results.append(a)
if type=="TRACK":
results = []
for t in TRACKS:
if query.lower() in t[1].lower():
results.append(t)
return results