Use generic response renderer

This commit is contained in:
dave 2017-08-20 14:54:13 -07:00
parent 73f0ff1201
commit b44b6fb022
4 changed files with 285 additions and 192 deletions

View File

@ -1,3 +1,4 @@
import re
import json import json
import logging import logging
import subprocess import subprocess
@ -11,55 +12,17 @@ from pysonic.library import LETTER_GROUPS
from pysonic.types import MUSIC_TYPES from pysonic.types import MUSIC_TYPES
CALLBACK_RE = re.compile(r'^[a-zA-Z0-9_]+$')
logging = logging.getLogger("api") logging = logging.getLogger("api")
class ApiResponse(object):
def __init__(self, status="ok", version="1.15.0", top=None, **kwargs):
self.status = status
self.version = version
self.data = {}
self.top = top
if self.top:
self.data[self.top] = kwargs ## kwargs unused TODO
def add_child(self, _type, **kwargs):
if not self.top:
raise Exception("You can't do this?")
if _type not in self.data[self.top]:
self.data[self.top][_type] = []
self.data[self.top][_type].append(kwargs)
def render_json(self):
return json.dumps({"subsonic-response": dict(status=self.status, version="1.15.0", **self.data)}, indent=4)
def render_xml(self):
doc = BeautifulSoup('', features='lxml-xml')
root = doc.new_tag("subsonic-response", xmlns="http://subsonic.org/restapi",
status=self.status,
version=self.version)
doc.append(root)
if self.top:
top = doc.new_tag(self.top)
root.append(top)
# TODO top_attrs ?
for top_child_type, top_child_instances in self.data[self.top].items():
for top_child_attrs in top_child_instances:
child = doc.new_tag(top_child_type)
child.attrs.update(top_child_attrs)
top.append(child)
albumlist = doc.new_tag("albumList")
doc.append(albumlist)
return doc.prettify()
response_formats = defaultdict(lambda: "render_xml") response_formats = defaultdict(lambda: "render_xml")
response_formats["json"] = "render_json" response_formats["json"] = "render_json"
response_formats["jsonp"] = "render_jsonp"
response_headers = defaultdict(lambda: "text/xml; charset=utf-8") response_headers = defaultdict(lambda: "text/xml; charset=utf-8")
response_headers["json"] = "text/json" #TODO is this right? response_headers["json"] = "application/json; charset=utf-8"
response_headers["jsonp"] = "text/javascript; charset=utf-8"
def formatresponse(func): def formatresponse(func):
@ -68,84 +31,169 @@ def formatresponse(func):
""" """
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
response = func(*args, **kwargs) response = func(*args, **kwargs)
cherrypy.response.headers['Content-Type'] = response_headers[kwargs.get("f", "xml")] response_format = kwargs.get("f", "xml")
return getattr(response, response_formats[kwargs.get("f", "xml")])() callback = kwargs.get("callback", None)
cherrypy.response.headers['Content-Type'] = response_headers[response_format]
renderer = getattr(response, response_formats[response_format])
if response_format == "jsonp":
if callback is None:
return response.render_xml().encode('UTF-8') # copy original subsonic behavior
else:
return renderer(callback).encode('UTF-8')
return renderer().encode('UTF-8')
return wrapper return wrapper
class ApiResponse(object):
def __init__(self, status="ok", version="1.15.0"):
"""
ApiResponses are python data structures that can be converted to other formats. The response has a status and a
version. The response data structure is stored in self.data and follows these rules:
- self.data is a dict
- the dict's values become either child nodes or attributes, named by the key
- lists become many oner one child
- dict values are not allowed
- all other types (str, int, NoneType) are attributes
:param status:
:param version:
"""
self.status = status
self.version = version
self.data = defaultdict(lambda: list())
def add_child(self, _type, _parent="", _real_parent=None, **kwargs):
parent = _real_parent if _real_parent else self.get_child(_parent)
m = defaultdict(lambda: list())
m.update(dict(kwargs))
parent[_type].append(m)
return m
def get_child(self, _path):
parent_path = _path.split(".")
parent = self.data
for item in parent_path:
if not item:
continue
parent = parent.get(item)[0]
return parent
def set_attrs(self, _path, **attrs):
parent = self.get_child(_path)
if type(parent) not in (dict, defaultdict):
raise Exception("wot")
parent.update(attrs)
def render_json(self):
def _flatten_json(item):
"""
Convert defaultdicts to dicts and remove lists where node has 1 or no child
"""
listed_attrs = ["folder"]
d = {}
for k, v in item.items():
if type(v) is list:
if len(v) > 1:
d[k] = []
for subitem in v:
d[k].append(_flatten_json(subitem))
elif len(v) == 1:
d[k] = _flatten_json(v[0])
else:
d[k] = {}
else:
d[k] = [v] if k in listed_attrs else v
return d
data = _flatten_json(self.data)
return json.dumps({"subsonic-response": dict(status=self.status, version=self.version, **data)}, indent=4)
def render_jsonp(self, callback):
assert CALLBACK_RE.match(callback), "Invalid callback"
return "{}({});".format(callback, self.render_json())
def render_xml(self):
text_attrs = ['largeImageUrl', 'musicBrainzId', 'smallImageUrl', 'mediumImageUrl', 'lastFmUrl', 'biography',
'folder']
# These attributes will be placed in <hello>{{ value }}</hello> tags instead of hello="{{ value }}" on parent
doc = BeautifulSoup('', features='lxml-xml')
root = doc.new_tag("subsonic-response", xmlns="http://subsonic.org/restapi",
status=self.status,
version=self.version)
doc.append(root)
def _render_xml(node, parent):
"""
For every key in the node dict, the parent gets a new child tag with name == key
If the value is a dict, it becomes the new tag's attrs
If the value is a list, the parent gets many new tags with each dict as attrs
If the value is str int etc, parent gets attrs
"""
for key, value in node.items():
if type(value) in (dict, defaultdict):
tag = doc.new_tag(key)
parent.append(tag)
tag.attrs.update(value)
elif type(value) is list:
for item in value:
tag = doc.new_tag(key)
parent.append(tag)
_render_xml(item, tag)
else:
if key in text_attrs:
tag = doc.new_tag(key)
parent.append(tag)
tag.append(str(value))
else:
parent.attrs[key] = value
_render_xml(self.data, root)
return doc.prettify()
class PysonicApi(object): class PysonicApi(object):
def __init__(self, db, library, options): def __init__(self, db, library, options):
self.db = db self.db = db
self.library = library self.library = library
self.options = options self.options = options
def response(self, status="ok"):
doc = BeautifulSoup('', features='lxml-xml')
root = doc.new_tag("subsonic-response", xmlns="http://subsonic.org/restapi", status=status, version="1.15.0")
doc.append(root)
return doc, root
@cherrypy.expose @cherrypy.expose
@formatresponse
def ping_view(self, **kwargs): def ping_view(self, **kwargs):
# Called when the app hits the "test connection" server option # Called when the app hits the "test connection" server option
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8' return ApiResponse()
doc, root = self.response()
yield doc.prettify()
@cherrypy.expose @cherrypy.expose
@formatresponse
def getLicense_view(self, **kwargs): def getLicense_view(self, **kwargs):
# Called after ping.view # Called after ping.view
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8' response = ApiResponse()
doc, root = self.response() response.add_child("license",
root.append(doc.new_tag("license", valid="true",
valid="true", email="admin@localhost",
email="admin@localhost", licenseExpires="2100-01-01T00:00:00.000Z",
licenseExpires="2100-01-01T00:00:00.000Z", trialExpires="2100-01-01T01:01:00.000Z")
trialExpires="2100-01-01T01:01:00.000Z")) return response
yield doc.prettify()
@cherrypy.expose @cherrypy.expose
@formatresponse
def getMusicFolders_view(self, **kwargs): def getMusicFolders_view(self, **kwargs):
# Get list of configured dirs response = ApiResponse()
# {'c': 'DSub', 's': 'bfk9mir8is02u3m5as8ucsehn0', 'v': '1.2.0', response.add_child("musicFolders")
# 't': 'e2b09fb9233d1bfac9abe3dc73017f1e', 'u': 'dave'}
# Access-Control-Allow-Origin:*
# Content-Encoding:gzip
# Content-Type:text/xml; charset=utf-8
# Server:Jetty(6.1.x)
# Transfer-Encoding:chunked
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
doc, root = self.response()
folder_list = doc.new_tag("musicFolders")
root.append(folder_list)
for folder in self.library.get_libraries(): for folder in self.library.get_libraries():
entry = doc.new_tag("musicFolder", id=folder["id"]) response.add_child("musicFolder", _parent="musicFolders", id=folder["id"], name=folder["name"])
entry.attrs["name"] = folder["name"] return response
folder_list.append(entry)
yield doc.prettify()
@cherrypy.expose @cherrypy.expose
@formatresponse
def getIndexes_view(self, **kwargs): def getIndexes_view(self, **kwargs):
# Get listing of top-level dir # Get listing of top-level dir
# /rest/getIndexes.view?u=dave&s=bfk9mir8is02u3m5as8ucsehn0 response = ApiResponse()
# &t=e2b09fb9233d1bfac9abe3dc73017f1e&v=1.2.0&c=DSub HTTP/1.1 response.add_child("indexes", lastModified="1502310831000", ignoredArticles="The El La Los Las Le Les")
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
doc, root = self.response()
indexes = doc.new_tag("indexes", lastModified="1502310831000", ignoredArticles="The El La Los Las Le Les")
doc.append(indexes)
for letter in LETTER_GROUPS: for letter in LETTER_GROUPS:
index = doc.new_tag("index") index = response.add_child("index", _parent="indexes", name=letter.upper())
index.attrs["name"] = letter.upper()
indexes.append(index)
for artist in self.library.get_artists(): for artist in self.library.get_artists():
if artist["name"][0].lower() in letter: if artist["name"][0].lower() in letter:
artist_tag = doc.new_tag("artist") response.add_child("artist", _real_parent=index, id=artist["id"], name=artist["name"])
artist_tag.attrs.update({"id": artist["id"], "name": artist["name"]}) return response
index.append(artist_tag)
yield doc.prettify()
@cherrypy.expose @cherrypy.expose
def savePlayQueue_view(self, id, current, position, **kwargs): def savePlayQueue_view(self, id, current, position, **kwargs):
@ -174,7 +222,9 @@ class PysonicApi(object):
raise NotImplemented() raise NotImplemented()
albumset = albums[0 + int(offset):int(size) + int(offset)] albumset = albums[0 + int(offset):int(size) + int(offset)]
response = ApiResponse(top="albumList") response = ApiResponse()
response.add_child("albumList")
for album in albumset: for album in albumset:
album_meta = album['metadata'] album_meta = album['metadata']
@ -184,8 +234,6 @@ class PysonicApi(object):
title=album_meta.get("id3_title", album["name"]), #TODO these cant be blank or dsub gets mad title=album_meta.get("id3_title", album["name"]), #TODO these cant be blank or dsub gets mad
album=album_meta.get("id3_album", album["album"]), album=album_meta.get("id3_album", album["album"]),
artist=album_meta.get("id3_artist", album["artist"]), artist=album_meta.get("id3_artist", album["artist"]),
# X year="2014"
# X coverArt="3228"
# playCount="0" # playCount="0"
# created="2016-05-08T05:31:31.000Z"/>) # created="2016-05-08T05:31:31.000Z"/>)
) )
@ -193,10 +241,11 @@ class PysonicApi(object):
album_kw["coverArt"] = album_meta["cover"] album_kw["coverArt"] = album_meta["cover"]
if 'id3_year' in album_meta: if 'id3_year' in album_meta:
album_kw["year"] = album_meta['id3_year'] album_kw["year"] = album_meta['id3_year']
response.add_child("album", **album_kw) response.add_child("album", _parent="albumList", **album_kw)
return response return response
@cherrypy.expose @cherrypy.expose
@formatresponse
def getMusicDirectory_view(self, id, **kwargs): def getMusicDirectory_view(self, id, **kwargs):
""" """
List an artist dir List an artist dir
@ -204,66 +253,67 @@ class PysonicApi(object):
dir_id = int(id) dir_id = int(id)
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8' cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
doc, root = self.response()
dirtag = doc.new_tag("directory") response = ApiResponse()
response.add_child("directory")
directory = self.library.get_dir(dir_id) directory = self.library.get_dir(dir_id)
dir_meta = directory["metadata"] dir_meta = directory["metadata"]
children = self.library.get_dir_children(dir_id) children = self.library.get_dir_children(dir_id)
dirtag.attrs.update(name=directory['name'], id=directory['id'], response.set_attrs(_path="directory", name=directory['name'], id=directory['id'],
parent=directory['parent'], playCount=10) parent=directory['parent'], playCount=10)
root.append(dirtag)
for item in children: for item in children:
# omit not dirs and media in browser # omit not dirs and media in browser
if not item["isdir"] and item["type"] not in MUSIC_TYPES: if not item["isdir"] and item["type"] not in MUSIC_TYPES:
continue continue
item_meta = item['metadata'] item_meta = item['metadata']
dirtag.append(self.render_node(doc, item, item_meta, directory, dir_meta)) response.add_child("child", _parent="directory", **self.render_node2(item, item_meta, directory, dir_meta))
yield doc.prettify()
def render_node(self, doc, item, item_meta, directory, dir_meta, tagname="child"): return response
child = doc.new_tag(tagname,
id=item["id"], def render_node2(self, item, item_meta, directory, dir_meta):
parent=item["id"], """
isDir="true" if item['isdir'] else "false", Given a node and it's parent directory, and meta, return a dict with the keys formatted how the subsonic clients
title=item_meta.get("id3_title", item["name"]), expect them to be
album=item_meta.get("id3_album", item["album"]), :param item:
artist=item_meta.get("id3_artist", item["artist"]), :param item_meta:
# playCount="5", :param directory:
# created="2016-04-25T07:31:33.000Z" :param dir_meta:
# X track="3", """
# X year="2012", child = dict(id=item["id"],
# X coverArt="12835", parent=item["id"],
# X contentType="audio/mpeg" isDir="true" if item['isdir'] else "false",
# X suffix="mp3" title=item_meta.get("id3_title", item["name"]),
# genre="Other", album=item_meta.get("id3_album", item["album"]),
# size="15838864" artist=item_meta.get("id3_artist", item["artist"]),
# duration="395" # playCount="5",
# bitRate="320" # created="2016-04-25T07:31:33.000Z"
# path="Cosmic Gate/Sign Of The Times/03 Flatline (featuring Kyler England).mp3" # genre="Other",
type="music") # path="Cosmic Gate/Sign Of The Times/03 Flatline (featuring Kyler England).mp3"
type="music")
if 'kbitrate' in item_meta:
child["bitrate"] = item_meta["kbitrate"]
if item["size"] != -1: if item["size"] != -1:
child.attrs["size"] = item["size"] child["size"] = item["size"]
if "media_length" in item_meta: if "media_length" in item_meta:
child.attrs["duration"] = item_meta["media_length"] child["duration"] = item_meta["media_length"]
if "albumId" in directory: if "albumId" in directory:
child.attrs["albumId"] = directory["id"] child["albumId"] = directory["id"]
if "artistId" in directory: if "artistId" in directory:
child.attrs["artistId"] = directory["parent"] child["artistId"] = directory["parent"]
if "." in item["name"]: if "." in item["name"]:
child.attrs["suffix"] = item["name"].split(".")[-1] child["suffix"] = item["name"].split(".")[-1]
if item["type"]: if item["type"]:
child.attrs["contentType"] = item["type"] child["contentType"] = item["type"]
if 'cover' in item_meta: if 'cover' in item_meta:
child.attrs["coverArt"] = item_meta["cover"] child["coverArt"] = item_meta["cover"]
elif 'cover' in dir_meta: elif 'cover' in dir_meta:
child.attrs["coverArt"] = dir_meta["cover"] child["coverArt"] = dir_meta["cover"]
if 'track' in item_meta: if 'track' in item_meta:
child.attrs["track"] = item_meta['track'] child["track"] = item_meta['track']
if 'id3_year' in item_meta: if 'id3_year' in item_meta:
child.attrs["year"] = item_meta['id3_year'] child["year"] = item_meta['id3_year']
return child return child
@cherrypy.expose @cherrypy.expose
@ -341,7 +391,8 @@ class PysonicApi(object):
fpath = self.library.get_filepath(id) fpath = self.library.get_filepath(id)
type2ct = { type2ct = {
'jpg': 'image/jpeg', 'jpg': 'image/jpeg',
'png': 'image/png' 'png': 'image/png',
'gif': 'image/gif'
} }
cherrypy.response.headers['Content-Type'] = type2ct[fpath[-3:]] cherrypy.response.headers['Content-Type'] = type2ct[fpath[-3:]]
@ -359,7 +410,52 @@ class PysonicApi(object):
getCoverArt_view._cp_config = {'response.stream': True} getCoverArt_view._cp_config = {'response.stream': True}
def response(self, status="ok"):
doc = BeautifulSoup('', features='lxml-xml')
root = doc.new_tag("subsonic-response", xmlns="http://subsonic.org/restapi", status=status, version="1.15.0")
doc.append(root)
return doc, root
def render_node(self, doc, item, item_meta, directory, dir_meta, tagname="child"):
child = doc.new_tag(tagname,
id=item["id"],
parent=item["id"],
isDir="true" if item['isdir'] else "false",
title=item_meta.get("id3_title", item["name"]),
album=item_meta.get("id3_album", item["album"]),
artist=item_meta.get("id3_artist", item["artist"]),
# playCount="5",
# created="2016-04-25T07:31:33.000Z"
# genre="Other",
# path="Cosmic Gate/Sign Of The Times/03 Flatline (featuring Kyler England).mp3"
type="music")
if 'kbitrate' in item_meta:
child.attrs["bitrate"] = item_meta["kbitrate"]
if item["size"] != -1:
child.attrs["size"] = item["size"]
if "media_length" in item_meta:
child.attrs["duration"] = item_meta["media_length"]
if "albumId" in directory:
child.attrs["albumId"] = directory["id"]
if "artistId" in directory:
child.attrs["artistId"] = directory["parent"]
if "." in item["name"]:
child.attrs["suffix"] = item["name"].split(".")[-1]
if item["type"]:
child.attrs["contentType"] = item["type"]
if 'cover' in item_meta:
child.attrs["coverArt"] = item_meta["cover"]
elif 'cover' in dir_meta:
child.attrs["coverArt"] = dir_meta["cover"]
if 'track' in item_meta:
child.attrs["track"] = item_meta['track']
if 'id3_year' in item_meta:
child.attrs["year"] = item_meta['id3_year']
return child
@cherrypy.expose @cherrypy.expose
@formatresponse
def getArtistInfo_view(self, id, includeNotPresent="true", **kwargs): def getArtistInfo_view(self, id, includeNotPresent="true", **kwargs):
# /rest/getArtistInfo.view? # /rest/getArtistInfo.view?
# u=dave # u=dave
@ -370,83 +466,70 @@ class PysonicApi(object):
# id=7 # id=7
# includeNotPresent=true # includeNotPresent=true
info = self.library.get_artist_info(id) info = self.library.get_artist_info(id)
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
doc, root = self.response()
dirtag = doc.new_tag("artistInfo") response = ApiResponse()
root.append(dirtag) response.add_child("artistInfo")
response.set_attrs("artistInfo", **info)
for key, value in info.items(): return response
if key == "similarArtists":
continue
tag = doc.new_tag(key)
tag.append(str(value))
dirtag.append(tag)
yield doc.prettify()
@cherrypy.expose @cherrypy.expose
@formatresponse
def getUser_view(self, u, username, **kwargs): def getUser_view(self, u, username, **kwargs):
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
doc, root = self.response()
user = {} if self.options.disable_auth else self.library.db.get_user(cherrypy.request.login) user = {} if self.options.disable_auth else self.library.db.get_user(cherrypy.request.login)
tag = doc.new_tag("user", response = ApiResponse()
username=user["username"], response.add_child("user",
email=user["email"], username=user["username"],
scrobblingEnabled="false", email=user["email"],
adminRole="true" if user["admin"] else "false", scrobblingEnabled="false",
settingsRole="false", adminRole="true" if user["admin"] else "false",
downloadRole="true", settingsRole="false",
uploadRole="false", downloadRole="true",
playlistRole="true", uploadRole="false",
coverArtRole="false", playlistRole="true",
commentRole="false", coverArtRole="false",
podcastRole="false", commentRole="false",
streamRole="true", podcastRole="false",
jukeboxRole="false", streamRole="true",
shareRole="true", jukeboxRole="false",
videoConversionRole="false", shareRole="true",
avatarLastChanged="2017-08-07T20:16:24.596Z") videoConversionRole="false",
root.append(tag) avatarLastChanged="2017-08-07T20:16:24.596Z",
folder = doc.new_tag("folder") folder=0)
folder.append("0") return response
tag.append(folder)
yield doc.prettify()
@cherrypy.expose @cherrypy.expose
@formatresponse
def star_view(self, id, **kwargs): def star_view(self, id, **kwargs):
self.library.set_starred(cherrypy.request.login, int(id), starred=True) self.library.set_starred(cherrypy.request.login, int(id), starred=True)
yield self.response()[0].prettify() return ApiResponse()
@cherrypy.expose @cherrypy.expose
@formatresponse
def unstar_view(self, id, **kwargs): def unstar_view(self, id, **kwargs):
self.library.set_starred(cherrypy.request.login, int(id), starred=False) self.library.set_starred(cherrypy.request.login, int(id), starred=False)
yield self.response()[0].prettify() return ApiResponse()
@cherrypy.expose @cherrypy.expose
@formatresponse
def getStarred_view(self, **kwargs): def getStarred_view(self, **kwargs):
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
doc, root = self.response()
tag = doc.new_tag("starred")
root.append(tag)
children = self.library.get_starred(cherrypy.request.login) children = self.library.get_starred(cherrypy.request.login)
response = ApiResponse()
response.add_child("starred")
for item in children: for item in children:
# omit not dirs and media in browser # omit not dirs and media in browser
if not item["isdir"] and item["type"] not in MUSIC_TYPES: if not item["isdir"] and item["type"] not in MUSIC_TYPES:
continue continue
item_meta = item['metadata'] item_meta = item['metadata']
itemtype = "song" if item["type"] in MUSIC_TYPES else "album" itemtype = "song" if item["type"] in MUSIC_TYPES else "album"
tag.append(self.render_node(doc, item, item_meta, {}, {}, tagname=itemtype)) response.add_child(itemtype, _parent="starred", **self.render_node2(item, item_meta, {}, {}))
yield doc.prettify() return response
@cherrypy.expose @cherrypy.expose
@formatresponse
def getRandomSongs_view(self, size=50, genre=None, fromYear=0, toYear=0, **kwargs): def getRandomSongs_view(self, size=50, genre=None, fromYear=0, toYear=0, **kwargs):
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8' response = ApiResponse()
doc, root = self.response() response.add_child("randomSongs")
tag = doc.new_tag("randomSongs")
root.append(tag)
children = self.library.get_songs(size, shuffle=True) children = self.library.get_songs(size, shuffle=True)
for item in children: for item in children:
# omit not dirs and media in browser # omit not dirs and media in browser
@ -454,5 +537,6 @@ class PysonicApi(object):
continue continue
item_meta = item['metadata'] item_meta = item['metadata']
itemtype = "song" if item["type"] in MUSIC_TYPES else "album" itemtype = "song" if item["type"] in MUSIC_TYPES else "album"
tag.append(self.render_node(doc, item, item_meta, {}, self.db.getnode(item["parent"])["metadata"], tagname=itemtype)) response.add_child(itemtype, _parent="randomSongs",
yield doc.prettify() **self.render_node2(item, item_meta, {}, self.db.getnode(item["parent"])["metadata"]))
return response

View File

@ -1,6 +1,7 @@
import os import os
import logging import logging
import cherrypy import cherrypy
from sqlite3 import IntegrityError
from pysonic.api import PysonicApi from pysonic.api import PysonicApi
from pysonic.library import PysonicLibrary, DuplicateRootException from pysonic.library import PysonicLibrary, DuplicateRootException
from pysonic.database import PysonicDatabase from pysonic.database import PysonicDatabase
@ -43,7 +44,10 @@ def main():
library.update() library.update()
for username, password in args.user: for username, password in args.user:
db.add_user(username, password) try:
db.add_user(username, password)
except IntegrityError:
db.update_user(username, password)
logging.warning("Libraries: {}".format([i["name"] for i in library.get_libraries()])) logging.warning("Libraries: {}".format([i["name"] for i in library.get_libraries()]))
logging.warning("Artists: {}".format([i["name"] for i in library.get_artists()])) logging.warning("Artists: {}".format([i["name"] for i in library.get_artists()]))

View File

@ -209,8 +209,13 @@ class PysonicDatabase(object):
def add_user(self, username, password, is_admin=False): def add_user(self, username, password, is_admin=False):
with closing(self.db.cursor()) as cursor: with closing(self.db.cursor()) as cursor:
cursor.execute("REPLACE INTO users (username, password, admin) VALUES (?, ?, ?)", cursor.execute("INSERT INTO users (username, password, admin) VALUES (?, ?, ?)",
(username, self.hashit(password), is_admin)).fetchall() (username, self.hashit(password), is_admin))
def update_user(self, username, password, is_admin=False):
with closing(self.db.cursor()) as cursor:
cursor.execute("UPDATE users SET password=?, admin=? WHERE username=?;",
(self.hashit(password), is_admin, username))
def get_user(self, user): def get_user(self, user):
with closing(self.db.cursor()) as cursor: with closing(self.db.cursor()) as cursor:

View File

@ -4,4 +4,4 @@ MUSIC_TYPES = ["audio/mpeg", "audio/flac", "audio/x-wav"]
MPX_TYPES = ["audio/mpeg"] MPX_TYPES = ["audio/mpeg"]
FLAC_TYPES = ["audio/flac"] FLAC_TYPES = ["audio/flac"]
WAV_TYPES = ["audio/x-wav"] WAV_TYPES = ["audio/x-wav"]
IMAGE_TYPES = ["image/jpeg", "image/png"] IMAGE_TYPES = ["image/jpeg", "image/png", "image/gif"]