Browse Source

args and logging

podcasts
dave 5 years ago
parent
commit
a0d25381c4
  1. 24
      pysonic/api.py
  2. 17
      pysonic/daemon.py
  3. 23
      pysonic/database.py
  4. 21
      pysonic/library.py
  5. 46
      pysonic/scanner.py

24
pysonic/api.py

@ -1,17 +1,17 @@
import sys
import logging
import cherrypy
from bs4 import BeautifulSoup
from pysonic.library import LETTER_GROUPS
logging = logging.getLogger("api")
class PysonicApi(object):
def __init__(self, db, library):
self.db = db
self.library = library
print("Libraries:", [i["name"] for i in self.library.get_libraries()])
print("Artists:", [i["name"] for i in self.library.get_artists()])
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")
@ -33,8 +33,8 @@ class PysonicApi(object):
root.append(doc.new_tag("license",
valid="true",
email="admin@localhost",
licenseExpires="2018-06-22T10:31:49.921Z",
trialExpires="2016-06-29T03:03:58.200Z"))
licenseExpires="2100-01-01T00:00:00.000Z",
trialExpires="2100-01-01T01:01:00.000Z"))
yield doc.prettify()
@cherrypy.expose
@ -74,7 +74,7 @@ class PysonicApi(object):
index.attrs["name"] = letter.upper()
indexes.append(index)
for artist in self.library.get_artists():
if artist["name"][0].lower() == letter:
if artist["name"][0].lower() in letter:
artist_tag = doc.new_tag("artist")
artist_tag.attrs.update({"id": artist["id"], "name": artist["name"]})
index.append(artist_tag)
@ -149,7 +149,7 @@ class PysonicApi(object):
yield data
sys.stdout.write('.')
sys.stdout.flush()
print("\nSent {} bytes for {}".format(total, fpath))
logging.info("\nSent {} bytes for {}".format(total, fpath))
return content()
stream_view._cp_config = {'response.stream': True}
@ -158,7 +158,11 @@ class PysonicApi(object):
# /rest/getCoverArt.view?u=dave&s=bfk9mir8is02u3m5as8ucsehn0
# &t=e2b09fb9233d1bfac9abe3dc73017f1e&v=1.2.0&c=DSub&id=12833
fpath = self.library.get_filepath(id)
cherrypy.response.headers['Content-Type'] = 'image/jpeg'
type2ct = {
'jpg': 'image/jpeg',
'png': 'image/png'
}
cherrypy.response.headers['Content-Type'] = type2ct[fpath[-3:]]
def content():
total = 0
@ -171,7 +175,7 @@ class PysonicApi(object):
yield data
sys.stdout.write('.')
sys.stdout.flush()
print("\nSent {} bytes for {}".format(total, fpath))
logging.info("\nSent {} bytes for {}".format(total, fpath))
return content()
getCoverArt_view._cp_config = {'response.stream': True}
@ -198,8 +202,6 @@ class PysonicApi(object):
continue
tag = doc.new_tag(key)
tag.append(str(value))
# print(dir(tag))
# print(value)
dirtag.append(tag)
yield doc.prettify()

17
pysonic/daemon.py

@ -1,7 +1,8 @@
import os
import logging
import cherrypy
from pysonic.api import PysonicApi
from pysonic.library import PysonicLibrary
from pysonic.library import PysonicLibrary, DuplicateRootException
from pysonic.database import PysonicDatabase
@ -21,14 +22,24 @@ def main():
db = PysonicDatabase(path=args.database_path)
library = PysonicLibrary(db)
for dirname in args.dirs:
assert os.path.exists(dirname) and dirname.startswith("/"), "--dirs must be absolute paths and exist!"
try:
library.add_dir(dirname)
except DuplicateRootException:
pass
library.update()
logging.warning("Libraries: {}".format([i["name"] for i in library.get_libraries()]))
logging.warning("Artists: {}".format([i["name"] for i in library.get_artists()]))
cherrypy.tree.mount(PysonicApi(db, library), '/rest/', {'/': {}})
cherrypy.config.update({
'sessionFilter.on': True,
'tools.sessions.on': True,
'tools.sessions.locking': 'explicit',
'tools.sessions.timeout': 525600,
'tools.gzip.on': True,
'request.show_tracebacks': True,
'server.socket_port': args.port,
'server.thread_pool': 25,
@ -40,7 +51,7 @@ def main():
})
def signal_handler(signum, stack):
print('Got sig {}, exiting...'.format(signum))
logging.critical('Got sig {}, exiting...'.format(signum))
cherrypy.engine.exit()
signal.signal(signal.SIGINT, signal_handler)
@ -50,7 +61,7 @@ def main():
cherrypy.engine.start()
cherrypy.engine.block()
finally:
print("API has shut down")
logging.info("API has shut down")
cherrypy.engine.exit()
if __name__ == '__main__':

23
pysonic/database.py

@ -1,10 +1,14 @@
import os
import json
import sqlite3
import logging
from itertools import chain
from contextlib import closing
logging = logging.getLogger("database")
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
@ -36,26 +40,25 @@ class PysonicDatabase(object):
'parent' INTEGER NOT NULL,
'isdir' BOOLEAN NOT NULL,
'name' TEXT NOT NULL,
'type' TEXT,
'title' TEXT,
'album' TEXT,
'artist' TEXT,
'metadata' TEXT
)""",
"""INSERT INTO nodes (parent, isdir, name, metadata)
VALUES (-1, 1, 'Main Library', '{"fspath": "/home/dave/Code/pysonic/music/"}');"""]
)"""]
with closing(self.db.cursor()) as cursor:
cursor.execute("SELECT * FROM sqlite_master WHERE type='table' AND name='meta';")
# Initialize DB
if len(cursor.fetchall()) == 0:
print("Initializing database")
logging.waring("Initializing database")
for query in queries:
cursor.execute(query)
else:
# Migrate if old db exists
version = int(cursor.execute("SELECT * FROM meta WHERE key='db_version';").fetchone()['value'])
print("db schema is version {}".format(version))
logging.warning("db schema is version {}".format(version))
# Virtual file tree
def getnode(self, node_id):
@ -67,13 +70,15 @@ class PysonicDatabase(object):
return list(chain(*[cursor.execute("SELECT * FROM nodes WHERE parent=?;", (parent_id, )).fetchall()
for parent_id in parent_ids]))
def addnode(self, parent, fspath, name):
def addnode(self, parent_id, fspath, name):
fullpath = os.path.join(fspath, name)
print("Adding ", fullpath)
is_dir = os.path.isdir(fullpath)
return self._addnode(parent_id, name, is_dir)
def _addnode(self, parent_id, name, is_dir=True):
with closing(self.db.cursor()) as cursor:
cursor.execute("INSERT INTO nodes (parent, isdir, name) VALUES (?, ?, ?);",
(parent["id"], 1 if is_dir else 0, name))
(parent_id, 1 if is_dir else 0, name))
return self.getnode(cursor.lastrowid)
def delnode(self, node_id):
@ -86,7 +91,7 @@ class PysonicDatabase(object):
def update_metadata(self, node_id, mergedict=None, **kwargs):
mergedict = mergedict if mergedict else {}
keys_in_table = ["title", "album", "artist"]
keys_in_table = ["title", "album", "artist", "type"]
mergedict.update(kwargs)
with closing(self.db.cursor()) as cursor:
for table_key in keys_in_table:

21
pysonic/library.py

@ -1,10 +1,14 @@
import os
import json
import logging
from pysonic.scanner import PysonicFilesystemScanner
LETTER_GROUPS = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
"u", "v", "w", "x-z", "#"]
"u", "v", "w", "xyz", "0123456789"]
logging = logging.getLogger("library")
def memoize(function):
@ -24,15 +28,28 @@ class NoDataException(Exception):
pass
class DuplicateRootException(Exception):
pass
class PysonicLibrary(object):
def __init__(self, database):
self.db = database
self.scanner = PysonicFilesystemScanner(self)
print("library ready")
logging.info("library ready")
def update(self):
self.scanner.init_scan()
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)]
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
def get_libraries(self):
"""

46
pysonic/scanner.py

@ -1,8 +1,15 @@
import os
import json
import logging
import mimetypes
from time import time
from threading import Thread
KNOWN_MIMES = ["audio/mpeg", "audio/flac", "audio/x-wav", "image/jpeg", "image/png"]
logging = logging.getLogger("scanner")
class PysonicFilesystemScanner(object):
def __init__(self, library):
self.library = library
@ -13,12 +20,14 @@ class PysonicFilesystemScanner(object):
def rescan(self):
# Perform directory scan
logging.warning("Beginning library rescan")
start = time()
for parent in self.library.get_libraries():
meta = json.loads(parent["metadata"])
# print("Scanning {}".format(meta["fspath"]))
logging.info("Scanning {}".format(meta["fspath"]))
def recurse_dir(path, parent):
# print("Scanning {} with parent {}".format(path, parent))
logging.info("Scanning {} with parent {}".format(path, parent))
# create or update the database of nodes by comparing sets of names
fs_entries = set(os.listdir(path))
db_entires = self.library.db.getnodes(parent["id"])
@ -28,16 +37,17 @@ class PysonicFilesystemScanner(object):
# Create any nodes not found in the db
for create in to_create:
new_node = self.library.db.addnode(parent, path, create)
new_node = self.library.db.addnode(parent["id"], path, create)
logging.info("Added", os.path.join(path, create))
db_entires.append(new_node)
# Delete any db nodes not found on disk
for delete in to_delete:
print("Prune ", delete, "in parent", path)
logging.info("Prune ", delete, "in parent", path)
node = [i for i in db_entires if i["name"] == delete]
if node:
deleted = self.library.db.delnode(node[0]["id"])
print("Pruned {}, deleting total of {}".format(node, deleted))
logging.info("Pruned {}, deleting total of {}".format(node, deleted))
for entry in db_entires:
if entry["name"] in to_delete:
@ -60,16 +70,28 @@ class PysonicFilesystemScanner(object):
title = track_file["name"]
if not track_file["title"]:
self.library.db.update_metadata(track_file["id"], artist=artist, album=album, title=title)
print("Adding simple metadata for {}/{}/{} #{}".format(artist, album,
title, track_file["id"]))
logging.info("Adding simple metadata for {}/{}/{} #{}".format(artist, album,
title, track_file["id"]))
if not album_dir["album"]:
self.library.db.update_metadata(album_dir["id"], artist=artist, album=album)
print("Adding simple metadata for {}/{} #{}".format(artist, album, album_dir["id"]))
logging.info("Adding simple metadata for {}/{} #{}".format(artist, album, album_dir["id"]))
if not artist_dir["artist"]:
self.library.db.update_metadata(artist_dir["id"], artist=artist)
print("Adding simple metadata for {} #{}".format(artist, artist_dir["id"]))
if title == "cover.jpg" and 'cover' not in album_meta:
logging.info("Adding simple metadata for {} #{}".format(artist, artist_dir["id"]))
if title in ["cover.jpg", "cover.png"] and 'cover' not in album_meta:
# // add cover art
self.library.db.update_metadata(album_dir["id"], cover=track_file["id"])
print("added cover for {}".format(album_dir['id']))
print("Metadata scan complete.")
logging.info("added cover for {}".format(album_dir['id']))
if track_file["type"] is None:
fpath = self.library.get_filepath(track_file['id'])
ftype, extra = mimetypes.guess_type(fpath)
if ftype in KNOWN_MIMES:
self.library.db.update_metadata(track_file["id"], type=ftype)
logging.info("added type {} for {}".format(ftype, track_file['id']))
else:
logging.warning("Ignoring unreadable file at {}, unknown ftype ({}, {})"
.format(fpath, ftype, extra))
logging.warning("Library scan complete in {}s".format(int(time() - start)))

Loading…
Cancel
Save