Partial shuffle support
This commit is contained in:
parent
b6daecdd5e
commit
efcef1f5df
@ -118,12 +118,12 @@ class PysonicApi(object):
|
||||
doc.append(albumlist)
|
||||
|
||||
for album in albumset:
|
||||
album_meta = self.library.db.decode_metadata(album['metadata'])
|
||||
album_meta = album['metadata']
|
||||
tag = doc.new_tag("album",
|
||||
id=album["id"],
|
||||
parent=album["parent"],
|
||||
isDir="true" if album['isdir'] else "false",
|
||||
title=album_meta.get("id3_title", album["name"]),
|
||||
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"]),
|
||||
artist=album_meta.get("id3_artist", album["artist"]),
|
||||
# X year="2014"
|
||||
@ -151,7 +151,7 @@ class PysonicApi(object):
|
||||
dirtag = doc.new_tag("directory")
|
||||
|
||||
directory = self.library.get_dir(dir_id)
|
||||
dir_meta = self.db.decode_metadata(directory["metadata"])
|
||||
dir_meta = directory["metadata"]
|
||||
children = self.library.get_dir_children(dir_id)
|
||||
dirtag.attrs.update(name=directory['name'], id=directory['id'],
|
||||
parent=directory['parent'], playCount=10)
|
||||
@ -161,7 +161,7 @@ class PysonicApi(object):
|
||||
# omit not dirs and media in browser
|
||||
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
||||
continue
|
||||
item_meta = self.db.decode_metadata(item['metadata'])
|
||||
item_meta = item['metadata']
|
||||
dirtag.append(self.render_node(doc, item, item_meta, directory, dir_meta))
|
||||
yield doc.prettify()
|
||||
|
||||
@ -327,23 +327,34 @@ class PysonicApi(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def getStarred_view(self, **kwargs):
|
||||
children = self.library.get_starred(cherrypy.request.login)
|
||||
|
||||
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
|
||||
doc, root = self.response()
|
||||
|
||||
tag = doc.new_tag("starred")
|
||||
|
||||
#directory = self.library.get_dir(dir_id)
|
||||
#dir_meta = self.db.decode_metadata(directory["metadata"])
|
||||
#children = self.library.get_dir_children(dir_id)
|
||||
root.append(tag)
|
||||
|
||||
children = self.library.get_starred(cherrypy.request.login)
|
||||
for item in children:
|
||||
# omit not dirs and media in browser
|
||||
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
||||
continue
|
||||
item_meta = self.db.decode_metadata(item['metadata'])
|
||||
item_meta = item['metadata']
|
||||
itemtype = "song" if item["type"] in MUSIC_TYPES else "album"
|
||||
tag.append(self.render_node(doc, item, item_meta, {}, {}, tagname=itemtype))
|
||||
yield doc.prettify()
|
||||
|
||||
@cherrypy.expose
|
||||
def getRandomSongs_view(self, size=50, genre=None, fromYear=0, toYear=0, **kwargs):
|
||||
cherrypy.response.headers['Content-Type'] = 'text/xml; charset=utf-8'
|
||||
doc, root = self.response()
|
||||
tag = doc.new_tag("randomSongs")
|
||||
root.append(tag)
|
||||
|
||||
children = self.library.get_songs(size, shuffle=True)
|
||||
for item in children:
|
||||
# omit not dirs and media in browser
|
||||
if not item["isdir"] and item["type"] not in MUSIC_TYPES:
|
||||
continue
|
||||
item_meta = item['metadata']
|
||||
itemtype = "song" if item["type"] in MUSIC_TYPES else "album"
|
||||
tag.append(self.render_node(doc, item, item_meta, {}, {}, tagname=itemtype))
|
||||
yield doc.prettify()
|
||||
|
@ -87,13 +87,56 @@ class PysonicDatabase(object):
|
||||
|
||||
# Virtual file tree
|
||||
def getnode(self, node_id):
|
||||
with closing(self.db.cursor()) as cursor:
|
||||
return cursor.execute("SELECT * FROM nodes WHERE id=?;", (node_id, )).fetchone()
|
||||
return self.getnodes(node_id=node_id)[0]
|
||||
|
||||
def _populate_meta(self, node):
|
||||
node['metadata'] = self.decode_metadata(node['metadata'])
|
||||
return node
|
||||
|
||||
def getnodes(self, *parent_ids, node_id=None, types=None, limit=None, order=None):
|
||||
"""
|
||||
Find nodes that match the passed paramters.
|
||||
:param parent_ids: one or more parents to find children of
|
||||
:type parent_ids: int
|
||||
:param node_id: single node id to return
|
||||
:type node_id: int
|
||||
:param types: filter by type column
|
||||
:type types: list
|
||||
:param limit: number of records to limit to
|
||||
:param order: one of ("rand") to select ordering mode
|
||||
"""
|
||||
query = "SELECT * FROM nodes WHERE "
|
||||
qargs = []
|
||||
|
||||
def add_filter(name, values):
|
||||
nonlocal query
|
||||
nonlocal qargs
|
||||
query += "{} in (".format(name)
|
||||
for value in (values if type(values) in [list, tuple] else [values]):
|
||||
query += "?, "
|
||||
qargs += [value]
|
||||
query = query.rstrip(", ")
|
||||
query += ") AND"
|
||||
|
||||
if node_id:
|
||||
add_filter("id", node_id)
|
||||
if parent_ids:
|
||||
add_filter("parent", parent_ids)
|
||||
if types:
|
||||
add_filter("type", types)
|
||||
|
||||
query = query.rstrip(" AND")
|
||||
|
||||
if order:
|
||||
query += "ORDER BY "
|
||||
if order == "rand":
|
||||
query += "RANDOM()"
|
||||
|
||||
if limit: # TODO 2-item tuple limit
|
||||
query += " limit {}".format(limit)
|
||||
|
||||
def getnodes(self, *parent_ids):
|
||||
with closing(self.db.cursor()) as cursor:
|
||||
return list(chain(*[cursor.execute("SELECT * FROM nodes WHERE parent=?;", (parent_id, )).fetchall()
|
||||
for parent_id in parent_ids]))
|
||||
return list(map(self._populate_meta, cursor.execute(query, qargs).fetchall()))
|
||||
|
||||
def addnode(self, parent_id, fspath, name):
|
||||
fullpath = os.path.join(fspath, name)
|
||||
@ -132,9 +175,9 @@ class PysonicDatabase(object):
|
||||
def get_metadata(self, node_id):
|
||||
keys_in_table = ["title", "album", "artist", "type"]
|
||||
node = self.getnode(node_id)
|
||||
metadata = self.decode_metadata(node["metadata"])
|
||||
metadata.update({item: node[item] for item in ["title", "album", "artist", "type"]})
|
||||
return metadata
|
||||
meta = node["metadata"]
|
||||
meta.update({item: node[item] for item in keys_in_table})
|
||||
return meta
|
||||
|
||||
def decode_metadata(self, metadata):
|
||||
if metadata:
|
||||
@ -169,7 +212,10 @@ class PysonicDatabase(object):
|
||||
query = "INSERT INTO stars (userid, nodeid) VALUES (?, ?);"
|
||||
else:
|
||||
query = "DELETE FROM stars WHERE userid=? and nodeid=?;"
|
||||
cursor.execute(query, (user_id, node_id))
|
||||
try:
|
||||
cursor.execute(query, (user_id, node_id))
|
||||
except sqlite3.IntegrityError:
|
||||
pass
|
||||
|
||||
def get_starred_items(self, for_user_id=None):
|
||||
with closing(self.db.cursor()) as cursor:
|
||||
@ -178,6 +224,5 @@ class PysonicDatabase(object):
|
||||
if for_user_id:
|
||||
q += """ AND userid=?"""
|
||||
qargs += [int(for_user_id)]
|
||||
print(q)
|
||||
print(qargs)
|
||||
return cursor.execute(q, qargs).fetchall()
|
||||
return list(map(self._populate_meta,
|
||||
cursor.execute(q, qargs).fetchall()))
|
||||
|
@ -1,7 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from pysonic.scanner import PysonicFilesystemScanner
|
||||
from pysonic.types import MUSIC_TYPES
|
||||
|
||||
|
||||
LETTER_GROUPS = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
|
||||
@ -43,21 +43,21 @@ class PysonicLibrary(object):
|
||||
|
||||
def add_dir(self, dir_path):
|
||||
dir_path = os.path.abspath(os.path.normpath(dir_path))
|
||||
libraries = [self.db.decode_metadata(i['metadata'])['fspath'] for i in self.db.getnodes(-1)]
|
||||
libraries = [i['metadata']['fspath'] for i in self.db.getnodes(-1)]
|
||||
if dir_path in libraries:
|
||||
raise DuplicateRootException("Dir already in library")
|
||||
else:
|
||||
new_root = self.db._addnode(-1, 'New Library', is_dir=True)
|
||||
self.db.update_metadata(new_root['id'], fspath=dir_path)
|
||||
|
||||
@memoize
|
||||
#@memoize
|
||||
def get_libraries(self):
|
||||
"""
|
||||
Libraries are top-level nodes
|
||||
"""
|
||||
return self.db.getnodes(-1)
|
||||
|
||||
@memoize
|
||||
#@memoize
|
||||
def get_artists(self):
|
||||
# Assume artists are second level dirs
|
||||
return self.db.getnodes(*[item["id"] for item in self.get_libraries()])
|
||||
@ -68,18 +68,18 @@ class PysonicLibrary(object):
|
||||
def get_dir_children(self, dirid):
|
||||
return self.db.getnodes(dirid)
|
||||
|
||||
@memoize
|
||||
#@memoize
|
||||
def get_albums(self):
|
||||
return self.db.getnodes(*[item["id"] for item in self.get_artists()])
|
||||
|
||||
@memoize
|
||||
#@memoize
|
||||
def get_filepath(self, nodeid):
|
||||
parents = [self.db.getnode(nodeid)]
|
||||
while parents[-1]['parent'] != -1:
|
||||
parents.append(self.db.getnode(parents[-1]['parent']))
|
||||
root = parents.pop()
|
||||
parents.reverse()
|
||||
return os.path.join(json.loads(root['metadata'])['fspath'], *[i['name'] for i in parents])
|
||||
return os.path.join(root['metadata']['fspath'], *[i['name'] for i in parents])
|
||||
|
||||
def get_file_metadata(self, nodeid):
|
||||
return self.db.get_metadata(nodeid)
|
||||
@ -105,3 +105,6 @@ class PysonicLibrary(object):
|
||||
|
||||
def get_starred(self, username):
|
||||
return self.db.get_starred_items(self.db.get_user(username)["id"])
|
||||
|
||||
def get_songs(self, limit=50, shuffle=True):
|
||||
return self.db.getnodes(types=MUSIC_TYPES, limit=limit, order="rand")
|
||||
|
@ -1,6 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
from time import time
|
||||
@ -28,7 +27,7 @@ class PysonicFilesystemScanner(object):
|
||||
logging.warning("Beginning library rescan")
|
||||
start = time()
|
||||
for parent in self.library.get_libraries():
|
||||
meta = json.loads(parent["metadata"])
|
||||
meta = parent["metadata"]
|
||||
logging.info("Scanning {}".format(meta["fspath"]))
|
||||
|
||||
def recurse_dir(path, parent):
|
||||
@ -70,7 +69,7 @@ class PysonicFilesystemScanner(object):
|
||||
artist = artist_dir["name"]
|
||||
for album_dir in self.library.db.getnodes(artist_dir["id"]):
|
||||
album = album_dir["name"]
|
||||
album_meta = self.library.db.get_metadata(album_dir["id"])
|
||||
album_meta = album_dir["metadata"]
|
||||
for track_file in self.library.db.getnodes(album_dir["id"]):
|
||||
title = track_file["name"]
|
||||
if not track_file["title"]:
|
||||
@ -106,9 +105,9 @@ class PysonicFilesystemScanner(object):
|
||||
artist = artist_dir["name"]
|
||||
for album_dir in self.library.db.getnodes(artist_dir["id"]):
|
||||
album = album_dir["name"]
|
||||
album_meta = self.library.db.get_metadata(album_dir["id"])
|
||||
album_meta = album_dir["metadata"]
|
||||
for track_file in self.library.db.getnodes(album_dir["id"]):
|
||||
track_meta = self.library.db.decode_metadata(track_file['metadata'])
|
||||
track_meta = track_file['metadata']
|
||||
title = track_file["name"]
|
||||
fpath = self.library.get_filepath(track_file["id"])
|
||||
if track_meta.get('id3_done', False) or track_file.get("type", "x") not in MUSIC_TYPES:
|
||||
|
Loading…
Reference in New Issue
Block a user