Browse Source

Partial shuffle support

podcasts
dave 5 years ago
parent
commit
efcef1f5df
  1. 33
      pysonic/api.py
  2. 69
      pysonic/database.py
  3. 17
      pysonic/library.py
  4. 9
      pysonic/scanner.py

33
pysonic/api.py

@ -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")
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 = 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()
#directory = self.library.get_dir(dir_id)
#dir_meta = self.db.decode_metadata(directory["metadata"])
#children = self.library.get_dir_children(dir_id)
@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 = 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()

69
pysonic/database.py

@ -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()))

17
pysonic/library.py

@ -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")

9
pysonic/scanner.py

@ -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…
Cancel
Save