From 441be436c691491e5d7f942af715d8a56386bcb8 Mon Sep 17 00:00:00 2001 From: Krateng Date: Mon, 8 Apr 2019 13:04:31 +0200 Subject: [PATCH] Significant rework of internal / URI key handling --- database.py | 30 ++-- htmlgenerators.py | 173 +------------------- htmlmodules.py | 44 ++--- maloja | 2 + malojatime.py | 17 +- rules/predefined/krateng_kpopgirlgroups.tsv | 2 +- scrobblers/maloja-scrobbler.zip | Bin 0 -> 26991 bytes server.py | 7 +- urihandler.py | 133 +++++++++++++++ website/artist.py | 5 +- website/charts_artists.py | 4 +- website/charts_tracks.py | 5 +- website/pulse.py | 5 +- website/scrobbles.py | 5 +- website/top_artists.py | 5 +- website/top_tracks.py | 5 +- website/track.py | 7 +- 17 files changed, 214 insertions(+), 235 deletions(-) create mode 100644 scrobblers/maloja-scrobbler.zip create mode 100644 urihandler.py diff --git a/database.py b/database.py index e3432c3..35db8dc 100644 --- a/database.py +++ b/database.py @@ -5,7 +5,7 @@ import waitress from cleanup import * from utilities import * from malojatime import * -from htmlgenerators import KeySplit +from urihandler import uri_to_internal # doreah toolkit from doreah.logging import log from doreah import tsv @@ -196,7 +196,7 @@ def test_server(): @dbserver.route("/scrobbles") def get_scrobbles_external(): keys = FormsDict.decode(request.query) - k_filter, k_time, _, k_amount = KeySplit(keys) + k_filter, k_time, _, k_amount = uri_to_internal(keys) ckeys = {**k_filter, **k_time, **k_amount} result = get_scrobbles(**ckeys) @@ -226,7 +226,7 @@ def get_scrobbles(**keys): @dbserver.route("/numscrobbles") def get_scrobbles_num_external(): keys = FormsDict.decode(request.query) - k_filter, k_time, _, k_amount = KeySplit(keys) + k_filter, k_time, _, k_amount = uri_to_internal(keys) ckeys = {**k_filter, **k_time, **k_amount} result = get_scrobbles_num(**ckeys) @@ -291,7 +291,7 @@ def get_scrobbles_num(**keys): @dbserver.route("/tracks") def get_tracks_external(): keys = FormsDict.decode(request.query) - k_filter, _, _, _ = KeySplit(keys,forceArtist=True) + k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True) ckeys = {**k_filter} result = get_tracks(**ckeys) @@ -329,7 +329,7 @@ def get_artists(): @dbserver.route("/charts/artists") def get_charts_artists_external(): keys = FormsDict.decode(request.query) - _, k_time, _, _ = KeySplit(keys) + _, k_time, _, _ = uri_to_internal(keys) ckeys = {**k_time} result = get_charts_artists(**ckeys) @@ -346,7 +346,7 @@ def get_charts_artists(**keys): @dbserver.route("/charts/tracks") def get_charts_tracks_external(): keys = FormsDict.decode(request.query) - k_filter, k_time, _, _ = KeySplit(keys,forceArtist=True) + k_filter, k_time, _, _ = uri_to_internal(keys,forceArtist=True) ckeys = {**k_filter, **k_time} result = get_charts_tracks(**ckeys) @@ -366,7 +366,7 @@ def get_charts_tracks(**keys): @dbserver.route("/pulse") def get_pulse_external(): keys = FormsDict.decode(request.query) - k_filter, k_time, k_internal, k_amount = KeySplit(keys) + k_filter, k_time, k_internal, k_amount = uri_to_internal(keys) ckeys = {**k_filter, **k_time, **k_internal, **k_amount} results = get_pulse(**ckeys) @@ -394,7 +394,7 @@ def get_pulse(**keys): def get_top_artists_external(): keys = FormsDict.decode(request.query) - _, k_time, k_internal, _ = KeySplit(keys) + _, k_time, k_internal, _ = uri_to_internal(keys) ckeys = {**k_time, **k_internal} results = get_top_artists(**ckeys) @@ -408,9 +408,9 @@ def get_top_artists(**keys): for (a,b) in rngs: try: res = db_aggregate(since=a,to=b,by="ARTIST")[0] - results.append({"from":a,"to":b,"artist":res["artist"],"counting":res["counting"],"scrobbles":res["scrobbles"]}) + results.append({"since":a,"to":b,"artist":res["artist"],"counting":res["counting"],"scrobbles":res["scrobbles"]}) except: - results.append({"from":a,"to":b,"artist":None,"scrobbles":0}) + results.append({"since":a,"to":b,"artist":None,"scrobbles":0}) return results @@ -426,7 +426,7 @@ def get_top_artists(**keys): @dbserver.route("/top/tracks") def get_top_tracks_external(): keys = FormsDict.decode(request.query) - _, k_time, k_internal, _ = KeySplit(keys) + _, k_time, k_internal, _ = uri_to_internal(keys) ckeys = {**k_time, **k_internal} # IMPLEMENT THIS FOR TOP TRACKS OF ARTIST AS WELL? @@ -442,9 +442,9 @@ def get_top_tracks(**keys): for (a,b) in rngs: try: res = db_aggregate(since=a,to=b,by="TRACK")[0] - results.append({"from":a,"to":b,"track":res["track"],"scrobbles":res["scrobbles"]}) + results.append({"since":a,"to":b,"track":res["track"],"scrobbles":res["scrobbles"]}) except: - results.append({"from":a,"to":b,"track":None,"scrobbles":0}) + results.append({"since":a,"to":b,"track":None,"scrobbles":0}) return results @@ -461,7 +461,7 @@ def get_top_tracks(**keys): @dbserver.route("/artistinfo") def artistInfo_external(): keys = FormsDict.decode(request.query) - k_filter, _, _, _ = KeySplit(keys,forceArtist=True) + k_filter, _, _, _ = uri_to_internal(keys,forceArtist=True) ckeys = {**k_filter} results = artistInfo(**ckeys) @@ -490,7 +490,7 @@ def artistInfo(artist): @dbserver.route("/trackinfo") def trackInfo_external(): keys = FormsDict.decode(request.query) - k_filter, _, _, _ = KeySplit(keys,forceTrack=True) + k_filter, _, _, _ = uri_to_internal(keys,forceTrack=True) ckeys = {**k_filter} results = trackInfo(**ckeys) diff --git a/htmlgenerators.py b/htmlgenerators.py index 62915f0..bb6e39a 100644 --- a/htmlgenerators.py +++ b/htmlgenerators.py @@ -1,7 +1,7 @@ import urllib from bottle import FormsDict import datetime -from malojatime import uri_to_internal, internal_to_uri +from urihandler import compose_querystring # returns the proper column(s) for an artist or track @@ -40,175 +40,19 @@ def trackLink(track): def scrobblesTrackLink(track,timekeys,amount=None,percent=None): artists,title = track["artists"],track["title"] inner = str(amount) if amount is not None else "
" - return "" + inner + "" + return "" + inner + "" def scrobblesArtistLink(artist,timekeys,amount=None,percent=None,associated=False): inner = str(amount) if amount is not None else "
" askey = "&associated" if associated else "" - return "" + inner + "" + return "" + inner + "" def scrobblesLink(timekeys,amount=None,percent=None,artist=None,track=None,associated=False): if track is not None: return scrobblesTrackLink(track,timekeys,amount,percent) if artist is not None: return scrobblesArtistLink(artist,timekeys,amount,percent,associated) inner = str(amount) if amount is not None else "
" - return "" + inner + "" + return "" + inner + "" -# necessary because urllib.parse.urlencode doesnt handle multidicts -def keysToUrl(*dicts,exclude=[]): - for dict in dicts: - for key in dict: - dict[key] = internal_to_uri(dict[key]) - st = "" - keys = removeIdentical(*dicts) - for k in keys: - if k in exclude: continue - values = keys.getall(k) - st += "&".join([urllib.parse.urlencode({k:v},safe="/") for v in values]) - st += "&" - return st - - -def removeIdentical(*dicts): - #combine multiple dicts - keys = FormsDict() - for d in dicts: - for k in d: - try: #multidicts - for v in d.getall(k): - keys.append(k,v) - except: #normaldicts - v = d.get(k) - keys.append(k,v) - - new = FormsDict() - for k in keys: - values = set(keys.getall(k)) - for v in values: - new.append(k,v) - - return new - -#def getTimeDesc(timestamp,short=False): -# tim = datetime.datetime.utcfromtimestamp(timestamp) -# if short: -# now = datetime.datetime.now(tz=datetime.timezone.utc) -# difference = int(now.timestamp() - timestamp) -# -# if difference < 10: return "just now" -# if difference < 60: return str(difference) + " seconds ago" -# difference = int(difference/60) -# if difference < 60: return str(difference) + " minutes ago" if difference>1 else str(difference) + " minute ago" -# difference = int(difference/60) -# if difference < 24: return str(difference) + " hours ago" if difference>1 else str(difference) + " hour ago" -# difference = int(difference/24) -# if difference < 5: return tim.strftime("%A") -# if difference < 31: return str(difference) + " days ago" if difference>1 else str(difference) + " day ago" -# #if difference < 300 and tim.year == now.year: return tim.strftime("%B") -# #if difference < 300: return tim.strftime("%B %Y") -# -# return tim.strftime("%d. %B %Y") -# else: -# return tim.strftime("%d. %b %Y %I:%M %p") - -#def getRangeDesc(since=None,to=None,inclusiveB=True): -# # string to list -# if isinstance(timeA,str): timeA = timeA.split("/") -# if isinstance(timeB,str): timeB = timeB.split("/") -# -# # if lists, we have it potentially much easier: -# if isinstance(timeA,list) and isinstance(timeB,list): -# if timeA == timeB: -# date = [1970,1,1] -# date[:len(timeA)] = timeA -# dto = datetime.datetime(date[0],date[1],date[2],tzinfo=datetime.timezone.utc) -# if len(timeA) == 3: -# return dto.strftime("%d. %b %Y") -# if len(timeA) == 2: -# return dto.strftime("%B %Y") -# if len(timeA) == 1: -# return dto.strftime("%Y") -# -# -# -# (timeA, timeB) = getTimestamps(since=timeA, to=timeB) -# -# -# return getTimeDesc(timeA) + " to " + getTimeDesc(timeB) - - - -# finds out if we want an artist or a track -#def interpretURLKeys(keys): -# if "title" in keys: -# return {"track":{"artists":keys.getall("artist"),"title":keys.get("title")}} -# if "artist" in keys: -# return {"artist":keys.get("artist")} -# -# return {} - -# alright this is the last one -# one ultimate method to rule them all -# one method to take html keys and convert them into internal keys -# it renames them, interprets what's being asked, removes duplicates -# it gets rid of multidicts -# it does fecking everything -# it's the best -# fantastic -def KeySplit(keys,forceTrack=False,forceArtist=False): - - # output: - # 1 keys that define the filtered object like artist or track - # 2 keys that define time limits of the whole thing - # 3 keys that define interal time ranges - # 4 keys that define amount limits - - # 1 - if "title" in keys and not forceArtist: - resultkeys1 = {"track":{"artists":keys.getall("artist"),"title":keys.get("title")}} - elif "artist" in keys and not forceTrack: - resultkeys1 = {"artist":keys.get("artist")} - if "associated" in keys: resultkeys1["associated"] = True - else: - resultkeys1 = {} - - # 2 - resultkeys2 = {} - if "since" in keys: resultkeys2["since"] = keys.get("since") - elif "from" in keys: resultkeys2["since"] = keys.get("from") - elif "start" in keys: resultkeys2["since"] = keys.get("start") - # - if "to" in keys: resultkeys2["to"] = keys.get("to") - elif "until" in keys: resultkeys2["to"] = keys.get("until") - elif "end" in keys: resultkeys2["to"] = keys.get("end") - # - if "since" in resultkeys2 and "to" in resultkeys2 and resultkeys2["since"] == resultkeys2["to"]: - resultkeys2["within"] = resultkeys2["since"] - del resultkeys2["since"] - del resultkeys2["to"] - # - if "in" in keys: resultkeys2["within"] = keys.get("in") - elif "within" in keys: resultkeys2["within"] = keys.get("within") - elif "during" in keys: resultkeys2["within"] = keys.get("during") - if "within" in resultkeys2: - if "since" in resultkeys2: - del resultkeys2["since"] - if "to" in resultkeys2: - del resultkeys2["to"] - - - #3 - resultkeys3 = {} - if "step" in keys: [resultkeys3["step"],resultkeys3["stepn"]] = (keys["step"].split("-") + [1])[:2] - if "stepn" in keys: resultkeys3["stepn"] = keys["stepn"] #overwrite if explicitly given - if "stepn" in resultkeys3: resultkeys3["stepn"] = int(resultkeys3["stepn"]) #in both cases, convert it here - if "trail" in keys: resultkeys3["trail"] = int(keys["trail"]) - - - #4 - resultkeys4 = {} - if "max" in keys: resultkeys4["max_"] = int(keys["max"]) - - return resultkeys1, resultkeys2, resultkeys3, resultkeys4 # limit a multidict to only the specified keys @@ -226,12 +70,3 @@ def pickKeys(d,*keys): finald.append(k,v) return finald - -# removes all duplicate keys, except artists when a title is specified -#def clean(d): -# if isinstance(d,dict): -# return -# else: -# for k in d: -# if (k != "artist") or "title" not in d: -# d[k] = d.pop(k) diff --git a/htmlmodules.py b/htmlmodules.py index 9c5dd33..bdcb61c 100644 --- a/htmlmodules.py +++ b/htmlmodules.py @@ -2,6 +2,7 @@ from htmlgenerators import * import database from utilities import getArtistImage, getTrackImage from malojatime import * +from urihandler import compose_querystring, internal_to_uri import urllib @@ -144,8 +145,8 @@ def module_trackcharts(max_=None,**kwargs): # track html += entity_column(e["track"]) # scrobbles - html += "" + scrobblesTrackLink(e["track"],kwargs_time,amount=e["scrobbles"]) + "" - html += "" + scrobblesTrackLink(e["track"],kwargs_time,percent=e["scrobbles"]*100/maxbar) + "" + html += "" + scrobblesTrackLink(e["track"],internal_to_uri(kwargs_time),amount=e["scrobbles"]) + "" + html += "" + scrobblesTrackLink(e["track"],internal_to_uri(kwargs_time),percent=e["scrobbles"]*100/maxbar) + "" html += "" prev = e html += "" @@ -205,8 +206,8 @@ def module_artistcharts(max_=None,**kwargs): # artist html += entity_column(e["artist"],counting=e["counting"]) # scrobbles - html += "" + scrobblesArtistLink(e["artist"],kwargs_time,amount=e["scrobbles"],associated=True) + "" - html += "" + scrobblesArtistLink(e["artist"],kwargs_time,percent=e["scrobbles"]*100/maxbar,associated=True) + "" + html += "" + scrobblesArtistLink(e["artist"],internal_to_uri(kwargs_time),amount=e["scrobbles"],associated=True) + "" + html += "" + scrobblesArtistLink(e["artist"],internal_to_uri(kwargs_time),percent=e["scrobbles"]*100/maxbar,associated=True) + "" html += "" prev = e @@ -245,14 +246,15 @@ def module_toptracks(pictures=True,**kwargs): html = "" for e in tracks: - fromstr = "/".join([str(p) for p in e["from"]]) - tostr = "/".join([str(p) for p in e["to"]]) + #fromstr = "/".join([str(p) for p in e["from"]]) + #tostr = "/".join([str(p) for p in e["to"]]) + limits = pickKeys(e,"since","to") i += 1 html += "" - html += "" + html += "" if e["track"] is None: if pictures: html += "" @@ -265,8 +267,8 @@ def module_toptracks(pictures=True,**kwargs): img = getTrackImage(e["track"]["artists"],e["track"]["title"],fast=True) else: img = None html += entity_column(e["track"],image=img) - html += "" - html += "" + html += "" + html += "" html += "" prev = e html += "
" + range_desc(e["from"],e["to"],short=True) + "" + range_desc(e["since"],e["to"],short=True) + "
" + scrobblesTrackLink(e["track"],{"since":fromstr,"to":tostr},amount=e["scrobbles"]) + "" + scrobblesTrackLink(e["track"],{"since":fromstr,"to":tostr},percent=e["scrobbles"]*100/maxbar) + "" + scrobblesTrackLink(e["track"],internal_to_uri(limits),amount=e["scrobbles"]) + "" + scrobblesTrackLink(e["track"],internal_to_uri(limits),percent=e["scrobbles"]*100/maxbar) + "
" @@ -300,14 +302,15 @@ def module_topartists(pictures=True,**kwargs): html = "" for e in artists: - fromstr = "/".join([str(p) for p in e["from"]]) - tostr = "/".join([str(p) for p in e["to"]]) + #fromstr = "/".join([str(p) for p in e["from"]]) + #tostr = "/".join([str(p) for p in e["to"]]) + limits = pickKeys(e,"since","to") i += 1 html += "" - html += "" + html += "" if e["artist"] is None: if pictures: @@ -320,8 +323,8 @@ def module_topartists(pictures=True,**kwargs): img = getArtistImage(e["artist"],fast=True) else: img = None html += entity_column(e["artist"],image=img) - html += "" - html += "" + html += "" + html += "" html += "" prev = e html += "
" + range_desc(e["from"],e["to"],short=True) + "" + range_desc(e["since"],e["to"],short=True) + "" + scrobblesArtistLink(e["artist"],{"since":fromstr,"to":tostr},amount=e["scrobbles"],associated=True) + "" + scrobblesArtistLink(e["artist"],{"since":fromstr,"to":tostr},percent=e["scrobbles"]*100/maxbar,associated=True) + "" + scrobblesArtistLink(e["artist"],internal_to_uri(limits),amount=e["scrobbles"],associated=True) + "" + scrobblesArtistLink(e["artist"],internal_to_uri(limits),percent=e["scrobbles"]*100/maxbar,associated=True) + "
" @@ -439,17 +442,14 @@ def module_trackcharts_tiles(**kwargs): return html - +# THIS FUNCTION USES THE ORIGINAL URI KEYS!!! def module_filterselection(keys,time=True,delimit=False): - # all other keys that will not be changed by clicking another filter - html = "" - if time: - - keystr = "?" + keysToUrl(keys,exclude=["since","to","in"]) + # all other keys that will not be changed by clicking another filter + keystr = "?" + compose_querystring(keys,exclude=["since","to","in"]) # wonky selector for precise date range @@ -506,7 +506,7 @@ def module_filterselection(keys,time=True,delimit=False): if delimit: - keystr = "?" + keysToUrl(keys,exclude=["step","stepn"]) + keystr = "?" + compose_querystring(keys,exclude=["step","stepn"]) html += "
" if keys.get("step") == "day": @@ -530,7 +530,7 @@ def module_filterselection(keys,time=True,delimit=False): - keystr = "?" + keysToUrl(keys,exclude=["trail"]) + keystr = "?" + compose_querystring(keys,exclude=["trail"]) html += "
" if keys.get("trail") == "1" or keys.get("trail") is None: diff --git a/maloja b/maloja index 1bc7f0a..a129e82 100755 --- a/maloja +++ b/maloja @@ -280,6 +280,8 @@ def update(): os.chmod("./maloja",os.stat("./maloja").st_mode | stat.S_IXUSR) + print("Make sure to install the latest version of doreah! (" + yellow("pip3 install --upgrade --no-cache-dir doreah") + ")") + if stop(): start() #stop returns whether it was running before, in which case we restart it diff --git a/malojatime.py b/malojatime.py index 6a1e803..2c6fc12 100644 --- a/malojatime.py +++ b/malojatime.py @@ -22,15 +22,18 @@ def end_of_scrobbling(): -def uri_to_internal(t): - return time_fix(t) +#def uri_to_internal(t): +# return time_fix(t) +# +#def internal_to_uri(t): +# if isinstance(t,list) or isinstance(t,tuple): +# return "/".join(str(t)) +# +# return str(t) -def internal_to_uri(t): - if isinstance(t,list) or isinstance(t,tuple): - return "/".join(str(t)) - - return str(t) +def time_str(t): + return "/".join(str(tp) for tp in t) # converts strings and stuff to lists def time_fix(t): diff --git a/rules/predefined/krateng_kpopgirlgroups.tsv b/rules/predefined/krateng_kpopgirlgroups.tsv index 7fd8699..754210a 100644 --- a/rules/predefined/krateng_kpopgirlgroups.tsv +++ b/rules/predefined/krateng_kpopgirlgroups.tsv @@ -75,7 +75,7 @@ replaceartist Ace of Angels AOA replacetitle 사뿐사뿐 (Like a Cat) Like a Cat replacetitle Like A Cat (Japanese Version) Like a Cat (Japanese Version) replacetitle MOYA (inst) Moya -replacetitle MOYA (instrumental) Moya +replacetitle MOYA (instrumental) Moya (instrumental) countas AOA Black AOA # Girl's Day diff --git a/scrobblers/maloja-scrobbler.zip b/scrobblers/maloja-scrobbler.zip new file mode 100644 index 0000000000000000000000000000000000000000..b23ab38f62d4e999950f25115fccc47c246f06e4 GIT binary patch literal 26991 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
  • A@1e@gBH&+}Wi3BlG({$!O| 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 R*AYCR<#lk7Y4R)de*hMt#6ti8 literal 0 HcmV?d00001 diff --git a/server.py b/server.py index e64ee23..c21e0aa 100755 --- a/server.py +++ b/server.py @@ -5,9 +5,8 @@ from bottle import Bottle, route, get, post, error, run, template, static_file, import waitress # rest of the project import database -from htmlgenerators import removeIdentical from utilities import * -from htmlgenerators import KeySplit +from urihandler import uri_to_internal, remove_identical # doreah toolkit from doreah import settings from doreah.logging import log @@ -113,7 +112,7 @@ def graceful_exit(sig=None,frame=None): @webserver.route("/image") def dynamic_image(): keys = FormsDict.decode(request.query) - relevant, _, _, _ = KeySplit(keys) + relevant, _, _, _ = uri_to_internal(keys) result = resolveImage(**relevant) if result == "": return "" redirect(result,307) @@ -162,7 +161,7 @@ def static(name): @webserver.route("/") def static_html(name): linkheaders = ["; rel=preload; as=style"] - keys = removeIdentical(FormsDict.decode(request.query)) + keys = remove_identical(FormsDict.decode(request.query)) with open("website/" + name + ".html") as htmlfile: html = htmlfile.read() diff --git a/urihandler.py b/urihandler.py new file mode 100644 index 0000000..9448b3c --- /dev/null +++ b/urihandler.py @@ -0,0 +1,133 @@ +import urllib +from bottle import FormsDict +from malojatime import time_fix, time_str + + +# necessary because urllib.parse.urlencode doesnt handle multidicts +def compose_querystring(*dicts,exclude=[]): + + st = "" + keys = remove_identical(*dicts) + for k in keys: + if k in exclude: continue + values = keys.getall(k) + st += "&".join([urllib.parse.urlencode({k:v},safe="/") for v in values]) + st += "&" + return st + + +# takes any number of multidicts and normal dicts and creates a formsdict with duplicate values removed +def remove_identical(*dicts): + #combine multiple dicts + keys = FormsDict() + for d in dicts: + for k in d: + try: #multidicts + for v in d.getall(k): + keys.append(k,v) + except: #normaldicts + v = d.get(k) + keys.append(k,v) + + new = FormsDict() + for k in keys: + #values = set(keys.getall(k)) + values = keys.getall(k) # NO IDENTICAL REMOVAL FOR NOW + for v in values: + new.append(k,v) + + return new + + + +def uri_to_internal(keys,forceTrack=False,forceArtist=False): + + # output: + # 1 keys that define the filtered object like artist or track + # 2 keys that define time limits of the whole thing + # 3 keys that define interal time ranges + # 4 keys that define amount limits + + # 1 + if "title" in keys and not forceArtist: + resultkeys1 = {"track":{"artists":keys.getall("artist"),"title":keys.get("title")}} + elif "artist" in keys and not forceTrack: + resultkeys1 = {"artist":keys.get("artist")} + if "associated" in keys: resultkeys1["associated"] = True + else: + resultkeys1 = {} + + # 2 + resultkeys2 = {} + if "since" in keys: resultkeys2["since"] = time_fix(keys.get("since")) + elif "from" in keys: resultkeys2["since"] = time_fix(keys.get("from")) + elif "start" in keys: resultkeys2["since"] = time_fix(keys.get("start")) + # + if "to" in keys: resultkeys2["to"] = time_fix(keys.get("to")) + elif "until" in keys: resultkeys2["to"] = time_fix(keys.get("until")) + elif "end" in keys: resultkeys2["to"] = time_fix(keys.get("end")) + # + if "since" in resultkeys2 and "to" in resultkeys2 and resultkeys2["since"] == resultkeys2["to"]: + resultkeys2["within"] = resultkeys2["since"] + del resultkeys2["since"] + del resultkeys2["to"] + # + if "in" in keys: resultkeys2["within"] = time_fix(keys.get("in")) + elif "within" in keys: resultkeys2["within"] = time_fix(keys.get("within")) + elif "during" in keys: resultkeys2["within"] = time_fix(keys.get("during")) + if "within" in resultkeys2: + if "since" in resultkeys2: + del resultkeys2["since"] + if "to" in resultkeys2: + del resultkeys2["to"] + + + #3 + resultkeys3 = {} + if "step" in keys: [resultkeys3["step"],resultkeys3["stepn"]] = (keys["step"].split("-") + [1])[:2] + if "stepn" in keys: resultkeys3["stepn"] = keys["stepn"] #overwrite if explicitly given + if "stepn" in resultkeys3: resultkeys3["stepn"] = int(resultkeys3["stepn"]) #in both cases, convert it here + if "trail" in keys: resultkeys3["trail"] = int(keys["trail"]) + + + #4 + resultkeys4 = {} + if "max" in keys: resultkeys4["max_"] = int(keys["max"]) + + return resultkeys1, resultkeys2, resultkeys3, resultkeys4 + +def internal_to_uri(keys): + urikeys = FormsDict() + + #filter + if "artist" in keys: + urikeys.append("artist",keys["artist"]) + if keys.get("associated"): urikeys.append("associated","yes") + elif "track" in keys: + for a in keys["track"]["artists"]: + urikeys.append("artist",a) + urikeys.append("title",keys["track"]["title"]) + + #time + if "within" in keys: + urikeys.append("in",time_str(keys["within"])) + else: + if "since" in keys: + urikeys.append("since",time_str(keys["since"])) + if "to" in keys: + urikeys.append("to",time_str(keys["to"])) + + # delimit + if "step" in keys: + urikeys.append("step",keys["step"]) + if "stepn" in keys: + urikeys.append("stepn",str(keys["stepn"])) + if "trail" in keys: + urikeys.append("trail",str(keys["trail"])) + + # stuff + if "max_" in keys: + urikeys.append("max",str(keys["max_"])) + + + return urikeys diff --git a/website/artist.py b/website/artist.py index 5ceb8ec..185874c 100644 --- a/website/artist.py +++ b/website/artist.py @@ -4,10 +4,11 @@ import database def instructions(keys): from utilities import getArtistImage - from htmlgenerators import artistLink, artistLinks, KeySplit + from htmlgenerators import artistLink, artistLinks + from urihandler import compose_querystring, uri_to_internal from htmlmodules import module_pulse, module_trackcharts - filterkeys, _, _, _ = KeySplit(keys,forceArtist=True) + filterkeys, _, _, _ = uri_to_internal(keys,forceArtist=True) imgurl = getArtistImage(filterkeys["artist"],fast=True) pushresources = [{"file":imgurl,"type":"image"}] if imgurl.startswith("/") else [] diff --git a/website/charts_artists.py b/website/charts_artists.py index 1e7010d..b39cd36 100644 --- a/website/charts_artists.py +++ b/website/charts_artists.py @@ -3,12 +3,12 @@ import urllib def instructions(keys): from utilities import getArtistImage - from htmlgenerators import KeySplit + from urihandler import compose_querystring, uri_to_internal from htmlmodules import module_artistcharts, module_filterselection from malojatime import range_desc - _, timekeys, _, amountkeys = KeySplit(keys) + _, timekeys, _, amountkeys = uri_to_internal(keys) limitstring = range_desc(**timekeys) diff --git a/website/charts_tracks.py b/website/charts_tracks.py index 32930bd..30c96b7 100644 --- a/website/charts_tracks.py +++ b/website/charts_tracks.py @@ -3,11 +3,12 @@ import urllib def instructions(keys): from utilities import getArtistImage, getTrackImage - from htmlgenerators import artistLink, KeySplit + from htmlgenerators import artistLink + from urihandler import compose_querystring, uri_to_internal from htmlmodules import module_trackcharts, module_filterselection from malojatime import range_desc - filterkeys, timekeys, _, amountkeys = KeySplit(keys) + filterkeys, timekeys, _, amountkeys = uri_to_internal(keys) limitstring = "" diff --git a/website/pulse.py b/website/pulse.py index f04e0f3..c79bfed 100644 --- a/website/pulse.py +++ b/website/pulse.py @@ -4,11 +4,12 @@ import database def instructions(keys): from utilities import getArtistImage, getTrackImage - from htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink, keysToUrl, KeySplit + from htmlgenerators import artistLink, artistLinks, trackLink, scrobblesLink + from urihandler import compose_querystring, uri_to_internal from htmlmodules import module_pulse, module_filterselection from malojatime import range_desc, delimit_desc - filterkeys, timekeys, delimitkeys, _ = KeySplit(keys) + filterkeys, timekeys, delimitkeys, _ = uri_to_internal(keys) # describe the scope (and creating a key for the relevant artist or track) diff --git a/website/scrobbles.py b/website/scrobbles.py index 1566200..a846eb2 100644 --- a/website/scrobbles.py +++ b/website/scrobbles.py @@ -4,12 +4,13 @@ import database def instructions(keys): from utilities import getArtistImage, getTrackImage - from htmlgenerators import artistLink, artistLinks, trackLink, KeySplit + from htmlgenerators import artistLink, artistLinks, trackLink + from urihandler import compose_querystring, uri_to_internal from htmlmodules import module_scrobblelist, module_filterselection from malojatime import range_desc - filterkeys, timekeys, _, amountkeys = KeySplit(keys) + filterkeys, timekeys, _, amountkeys = uri_to_internal(keys) # describe the scope limitstring = "" diff --git a/website/top_artists.py b/website/top_artists.py index 2476159..1756ba2 100644 --- a/website/top_artists.py +++ b/website/top_artists.py @@ -3,11 +3,12 @@ import urllib def instructions(keys): from utilities import getArtistImage, getTrackImage - from htmlgenerators import artistLink, KeySplit + from htmlgenerators import artistLink + from urihandler import compose_querystring, uri_to_internal from htmlmodules import module_topartists, module_filterselection from malojatime import range_desc - _, timekeys, delimitkeys, _ = KeySplit(keys) + _, timekeys, delimitkeys, _ = uri_to_internal(keys) limitstring = "" diff --git a/website/top_tracks.py b/website/top_tracks.py index 565b1eb..bf7fddf 100644 --- a/website/top_tracks.py +++ b/website/top_tracks.py @@ -3,11 +3,12 @@ import urllib def instructions(keys): from utilities import getArtistImage, getTrackImage - from htmlgenerators import artistLink, KeySplit + from htmlgenerators import artistLink + from urihandler import compose_querystring, uri_to_internal from htmlmodules import module_toptracks, module_filterselection from malojatime import range_desc - filterkeys, timekeys, delimitkeys, _ = KeySplit(keys) + filterkeys, timekeys, delimitkeys, _ = uri_to_internal(keys) limitstring = "" diff --git a/website/track.py b/website/track.py index 5af1bd2..24b3f8c 100644 --- a/website/track.py +++ b/website/track.py @@ -4,11 +4,12 @@ import database def instructions(keys): from utilities import getArtistImage, getTrackImage - from htmlgenerators import artistLinks, keysToUrl, KeySplit + from htmlgenerators import artistLinks + from urihandler import compose_querystring from htmlmodules import module_scrobblelist, module_pulse - filterkeys, _, _, _ = KeySplit(keys,forceTrack=True) + filterkeys, _, _, _ = uri_to_internal(keys,forceTrack=True) track = filterkeys.get("track") imgurl = getTrackImage(track["artists"],track["title"],fast=True) @@ -40,7 +41,7 @@ def instructions(keys): replace = {"KEY_TRACKTITLE":track.get("title"),"KEY_ARTISTS":artistLinks(track.get("artists")),"KEY_SCROBBLES":scrobblesnum,"KEY_POSITION":pos,"KEY_IMAGEURL":imgurl, - "KEY_SCROBBLELINK":keysToUrl(keys),"KEY_MEDALS":html_medals, + "KEY_SCROBBLELINK":compose_querystring(keys),"KEY_MEDALS":html_medals, "KEY_SCROBBLELIST":html_scrobbles,"KEY_PULSE":html_pulse} return (replace,pushresources)