2020-10-05 23:06:07 -07:00
|
|
|
import os
|
2017-08-13 18:56:13 -07:00
|
|
|
import sqlite3
|
2017-08-13 21:13:46 -07:00
|
|
|
import logging
|
2017-08-15 20:26:03 -07:00
|
|
|
from hashlib import sha512
|
2018-04-05 19:02:17 -07:00
|
|
|
from time import time
|
2017-08-13 18:56:13 -07:00
|
|
|
from contextlib import closing
|
2018-04-05 19:02:17 -07:00
|
|
|
from collections import Iterable
|
2017-08-13 18:56:13 -07:00
|
|
|
|
2020-10-05 23:06:07 -07:00
|
|
|
|
|
|
|
from pysonic.scanner import PysonicFilesystemScanner
|
|
|
|
|
|
|
|
|
2017-08-13 21:13:46 -07:00
|
|
|
logging = logging.getLogger("database")
|
2020-10-05 23:06:07 -07:00
|
|
|
|
|
|
|
|
|
|
|
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", "xyz", "0123456789"]
|
|
|
|
|
|
|
|
|
2017-08-19 22:03:09 -07:00
|
|
|
keys_in_table = ["title", "album", "artist", "type", "size"]
|
2017-08-13 21:13:46 -07:00
|
|
|
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
def dict_factory(c, row):
|
2017-08-13 18:56:13 -07:00
|
|
|
d = {}
|
2020-10-05 22:41:01 -07:00
|
|
|
for idx, col in enumerate(c.description):
|
2017-08-13 18:56:13 -07:00
|
|
|
d[col[0]] = row[idx]
|
|
|
|
return d
|
|
|
|
|
|
|
|
|
2017-08-15 21:40:38 -07:00
|
|
|
class NotFoundError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2018-04-05 19:02:17 -07:00
|
|
|
class DuplicateRootException(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def hash_password(unicode_string):
|
2020-09-23 22:57:26 -07:00
|
|
|
return sha512(unicode_string.encode('UTF-8')).hexdigest()
|
2018-04-05 19:02:17 -07:00
|
|
|
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
def cursor(func):
|
2018-04-05 19:02:17 -07:00
|
|
|
"""
|
|
|
|
Provides a cursor to the wrapped method as the first arg.
|
|
|
|
"""
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
self = args[0]
|
|
|
|
if len(args) >= 2 and isinstance(args[1], sqlite3.Cursor):
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
else:
|
2020-10-05 22:41:01 -07:00
|
|
|
with closing(self.db.cursor()) as c:
|
|
|
|
return func(self, c, *args[1:], **kwargs)
|
2018-04-05 19:02:17 -07:00
|
|
|
return wrapped
|
|
|
|
|
|
|
|
|
2017-08-13 18:56:13 -07:00
|
|
|
class PysonicDatabase(object):
|
|
|
|
def __init__(self, path):
|
2018-04-05 19:02:17 -07:00
|
|
|
self.sqlite_opts = dict(check_same_thread=False)
|
2017-08-13 18:56:13 -07:00
|
|
|
self.path = path
|
|
|
|
self.db = None
|
|
|
|
self.open()
|
|
|
|
self.migrate()
|
2020-10-05 23:06:07 -07:00
|
|
|
self.scanner = PysonicFilesystemScanner(self)
|
2017-08-13 18:56:13 -07:00
|
|
|
|
|
|
|
def open(self):
|
|
|
|
self.db = sqlite3.connect(self.path, **self.sqlite_opts)
|
|
|
|
self.db.row_factory = dict_factory
|
|
|
|
|
2020-10-05 23:06:07 -07:00
|
|
|
def update(self):
|
|
|
|
"""
|
|
|
|
Start the library media scanner ands
|
|
|
|
"""
|
|
|
|
self.scanner.init_scan()
|
|
|
|
|
2017-08-13 18:56:13 -07:00
|
|
|
def migrate(self):
|
|
|
|
# Create db
|
2018-04-05 19:02:17 -07:00
|
|
|
queries = ["""CREATE TABLE 'libraries' (
|
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
'name' TEXT,
|
|
|
|
'path' TEXT UNIQUE);""",
|
|
|
|
"""CREATE TABLE 'dirs' (
|
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
'library' INTEGER,
|
|
|
|
'parent' INTEGER,
|
|
|
|
'name' TEXT,
|
|
|
|
UNIQUE(parent, name)
|
|
|
|
)""",
|
|
|
|
"""CREATE TABLE 'genres' (
|
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
'name' TEXT UNIQUE)""",
|
|
|
|
"""CREATE TABLE 'artists' (
|
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
'libraryid' INTEGER,
|
|
|
|
'dir' INTEGER UNIQUE,
|
|
|
|
'name' TEXT)""",
|
|
|
|
"""CREATE TABLE 'albums' (
|
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
'artistid' INTEGER,
|
|
|
|
'coverid' INTEGER,
|
|
|
|
'dir' INTEGER,
|
|
|
|
'name' TEXT,
|
|
|
|
'added' INTEGER NOT NULL DEFAULT -1,
|
2018-04-07 15:24:02 -07:00
|
|
|
'played' INTEGER,
|
|
|
|
'plays' INTEGER NOT NULL DEFAULT 0,
|
2018-04-05 19:02:17 -07:00
|
|
|
UNIQUE (artistid, dir));""",
|
|
|
|
"""CREATE TABLE 'songs' (
|
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
'library' INTEGER,
|
|
|
|
'albumid' BOOLEAN,
|
|
|
|
'genre' INTEGER DEFAULT NULL,
|
|
|
|
'file' TEXT UNIQUE, -- path from the library root
|
|
|
|
'size' INTEGER NOT NULL DEFAULT -1,
|
|
|
|
'title' TEXT NOT NULL,
|
|
|
|
'lastscan' INTEGER NOT NULL DEFAULT -1,
|
|
|
|
'format' TEXT,
|
|
|
|
'length' INTEGER,
|
|
|
|
'bitrate' INTEGER,
|
|
|
|
'track' INTEGER,
|
2020-10-05 22:12:40 -07:00
|
|
|
'year' INTEGER,
|
|
|
|
'plays' INTEGER NOT NULL DEFAULT 0
|
2017-08-19 22:03:09 -07:00
|
|
|
)""",
|
2018-04-05 19:02:17 -07:00
|
|
|
"""CREATE TABLE 'covers' (
|
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
'library' INTEGER,
|
|
|
|
'type' TEXT,
|
|
|
|
'size' TEXT,
|
|
|
|
'path' TEXT UNIQUE);""",
|
2017-08-19 22:03:09 -07:00
|
|
|
"""CREATE TABLE 'users' (
|
2018-04-05 19:02:17 -07:00
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
|
|
'username' TEXT UNIQUE NOT NULL,
|
|
|
|
'password' TEXT NOT NULL,
|
|
|
|
'admin' BOOLEAN DEFAULT 0,
|
|
|
|
'email' TEXT)""",
|
2017-08-19 22:03:09 -07:00
|
|
|
"""CREATE TABLE 'stars' (
|
2018-04-05 19:02:17 -07:00
|
|
|
'userid' INTEGER,
|
|
|
|
'songid' INTEGER,
|
|
|
|
primary key ('userid', 'songid'))""",
|
|
|
|
"""CREATE TABLE 'playlists' (
|
|
|
|
'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
|
|
'ownerid' INTEGER,
|
|
|
|
'name' TEXT,
|
|
|
|
'public' BOOLEAN,
|
|
|
|
'created' INTEGER,
|
|
|
|
'changed' INTEGER,
|
|
|
|
'cover' INTEGER,
|
|
|
|
UNIQUE ('ownerid', 'name'))""",
|
|
|
|
"""CREATE TABLE 'playlist_entries' (
|
|
|
|
'playlistid' INTEGER,
|
|
|
|
'songid' INTEGER,
|
|
|
|
'order' FLOAT)""",
|
|
|
|
"""CREATE TABLE 'meta' (
|
|
|
|
'key' TEXT PRIMARY KEY NOT NULL,
|
|
|
|
'value' TEXT);""",
|
|
|
|
"""INSERT INTO meta VALUES ('db_version', '1');"""]
|
2017-08-13 18:56:13 -07:00
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
with closing(self.db.cursor()) as c:
|
|
|
|
c.execute("SELECT * FROM sqlite_master WHERE type='table' AND name='meta'")
|
2017-08-13 18:56:13 -07:00
|
|
|
|
|
|
|
# Initialize DB
|
2020-10-05 22:41:01 -07:00
|
|
|
if len(c.fetchall()) == 0:
|
2017-08-13 22:08:40 -07:00
|
|
|
logging.warning("Initializing database")
|
2017-08-13 18:56:13 -07:00
|
|
|
for query in queries:
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute(query)
|
|
|
|
c.execute("COMMIT")
|
2017-08-13 18:56:13 -07:00
|
|
|
else:
|
|
|
|
# Migrate if old db exists
|
2020-10-05 22:41:01 -07:00
|
|
|
# c.execute("""UPDATE meta SET value=? WHERE key="db_version";""", (str(version), ))
|
2018-04-05 19:02:17 -07:00
|
|
|
# logging.warning("db schema is version {}".format(version))
|
|
|
|
pass
|
|
|
|
|
2020-10-05 23:06:07 -07:00
|
|
|
def get_artist_info(self, item_id):
|
|
|
|
#TODO
|
|
|
|
return {"biography": "placeholder biography",
|
|
|
|
"musicBrainzId": "playerholder",
|
|
|
|
"lastFmUrl": "https://www.last.fm/music/Placeholder",
|
|
|
|
"smallImageUrl": "",
|
|
|
|
"mediumImageUrl": "",
|
|
|
|
"largeImageUrl": "",
|
|
|
|
"similarArtists": []}
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_stats(self, c):
|
|
|
|
songs = c.execute("SELECT COUNT(*) as cnt FROM songs").fetchone()['cnt']
|
|
|
|
artists = c.execute("SELECT COUNT(*) as cnt FROM artists").fetchone()['cnt']
|
|
|
|
albums = c.execute("SELECT COUNT(*) as cnt FROM albums").fetchone()['cnt']
|
2018-04-07 15:04:41 -07:00
|
|
|
return dict(songs=songs, artists=artists, albums=albums)
|
|
|
|
|
2018-04-05 19:02:17 -07:00
|
|
|
# Music related
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def add_root(self, c, path, name="Library"):
|
2017-08-16 00:05:26 -07:00
|
|
|
"""
|
2018-04-05 19:02:17 -07:00
|
|
|
Add a new library root. Returns the root ID or raises on collision
|
|
|
|
:param path: normalized absolute path to add to the library
|
|
|
|
:type path: str:
|
|
|
|
:return: int
|
|
|
|
:raises: sqlite3.IntegrityError
|
|
|
|
"""
|
2020-10-05 23:06:07 -07:00
|
|
|
path = os.path.abspath(os.path.normpath(path))
|
2018-04-05 19:02:17 -07:00
|
|
|
try:
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute("INSERT INTO libraries ('name', 'path') VALUES (?, ?)", (name, path, ))
|
|
|
|
c.execute("COMMIT")
|
|
|
|
return c.lastrowid
|
2018-04-05 19:02:17 -07:00
|
|
|
except sqlite3.IntegrityError:
|
|
|
|
raise DuplicateRootException("Root '{}' already exists".format(path))
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_libraries(self, c, id=None):
|
2018-04-05 19:02:17 -07:00
|
|
|
libs = []
|
|
|
|
q = "SELECT * FROM libraries"
|
|
|
|
params = []
|
|
|
|
conditions = []
|
|
|
|
if id:
|
|
|
|
conditions.append("id = ?")
|
|
|
|
params.append(id)
|
|
|
|
if conditions:
|
|
|
|
q += " WHERE " + " AND ".join(conditions)
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute(q, params)
|
|
|
|
for row in c:
|
2018-04-05 19:02:17 -07:00
|
|
|
libs.append(row)
|
|
|
|
return libs
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_artists(self, c, id=None, dirid=None, sortby="name", order=None, name_contains=None):
|
2018-04-05 19:02:17 -07:00
|
|
|
assert order in ["asc", "desc", None]
|
|
|
|
artists = []
|
|
|
|
q = "SELECT * FROM artists"
|
|
|
|
params = []
|
|
|
|
conditions = []
|
|
|
|
if id:
|
|
|
|
conditions.append("id = ?")
|
|
|
|
params.append(id)
|
|
|
|
if dirid:
|
|
|
|
conditions.append("dir = ?")
|
|
|
|
params.append(dirid)
|
2020-10-05 22:19:48 -07:00
|
|
|
if name_contains:
|
|
|
|
conditions.append("name LIKE ?")
|
|
|
|
params.append("%{}%".format(name_contains))
|
2018-04-05 19:02:17 -07:00
|
|
|
if conditions:
|
|
|
|
q += " WHERE " + " AND ".join(conditions)
|
|
|
|
if sortby:
|
|
|
|
q += " ORDER BY {} {}".format(sortby, order.upper() if order else "ASC")
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute(q, params)
|
|
|
|
for row in c:
|
2018-04-05 19:02:17 -07:00
|
|
|
artists.append(row)
|
|
|
|
return artists
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_albums(self, c, id=None, artist=None, sortby="name", order=None, limit=None, name_contains=None):
|
2018-04-05 19:02:17 -07:00
|
|
|
"""
|
|
|
|
:param limit: int or tuple of int, int. translates directly to sql logic.
|
2017-08-16 00:05:26 -07:00
|
|
|
"""
|
|
|
|
if order:
|
2018-04-05 19:02:17 -07:00
|
|
|
order = {"asc": "ASC", "desc": "DESC"}[order]
|
|
|
|
|
2020-10-05 20:11:58 -07:00
|
|
|
if sortby == "random":
|
2018-04-05 19:02:17 -07:00
|
|
|
sortby = "RANDOM()"
|
|
|
|
|
|
|
|
albums = []
|
|
|
|
|
|
|
|
q = """
|
|
|
|
SELECT
|
|
|
|
alb.*,
|
|
|
|
art.name as artistname,
|
|
|
|
dirs.parent as artistdir
|
|
|
|
FROM albums as alb
|
|
|
|
INNER JOIN artists as art
|
|
|
|
on alb.artistid = art.id
|
|
|
|
INNER JOIN dirs
|
|
|
|
on dirs.id = alb.dir
|
|
|
|
"""
|
|
|
|
params = []
|
|
|
|
|
|
|
|
conditions = []
|
|
|
|
if id:
|
|
|
|
conditions.append("id = ?")
|
|
|
|
params.append(id)
|
|
|
|
if artist:
|
|
|
|
conditions.append("artistid = ?")
|
|
|
|
params.append(artist)
|
2020-10-05 22:19:48 -07:00
|
|
|
if name_contains:
|
|
|
|
conditions.append("alb.name LIKE ?")
|
|
|
|
params.append("%{}%".format(name_contains))
|
2018-04-05 19:02:17 -07:00
|
|
|
if conditions:
|
|
|
|
q += " WHERE " + " AND ".join(conditions)
|
|
|
|
|
|
|
|
if sortby:
|
|
|
|
q += " ORDER BY {}".format(sortby)
|
|
|
|
if order:
|
|
|
|
q += " {}".format(order)
|
|
|
|
|
|
|
|
if limit:
|
|
|
|
q += " LIMIT {}".format(limit) if isinstance(limit, int) \
|
|
|
|
else " LIMIT {}, {}".format(*limit)
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute(q, params)
|
|
|
|
for row in c:
|
2018-04-05 19:02:17 -07:00
|
|
|
albums.append(row)
|
|
|
|
return albums
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_songs(self, c, id=None, genre=None, sortby="title", order=None, limit=None, title_contains=None):
|
2018-04-05 19:02:17 -07:00
|
|
|
# TODO make this query massively uglier by joining albums and artists so that artistid etc can be a filter
|
|
|
|
# or maybe lookup those IDs in the library layer?
|
|
|
|
if order:
|
|
|
|
order = {"asc": "ASC", "desc": "DESC"}[order]
|
|
|
|
|
2020-10-05 20:11:58 -07:00
|
|
|
if sortby == "random":
|
2018-04-05 19:02:17 -07:00
|
|
|
sortby = "RANDOM()"
|
|
|
|
|
|
|
|
songs = []
|
|
|
|
|
|
|
|
q = """
|
|
|
|
SELECT
|
|
|
|
s.*,
|
|
|
|
alb.name as albumname,
|
|
|
|
alb.coverid as albumcoverid,
|
|
|
|
art.name as artistname,
|
2020-10-05 20:11:58 -07:00
|
|
|
g.name as genrename,
|
|
|
|
albdir.id as albumdir
|
2018-04-05 19:02:17 -07:00
|
|
|
FROM songs as s
|
|
|
|
INNER JOIN albums as alb
|
|
|
|
on s.albumid == alb.id
|
2020-10-05 20:11:58 -07:00
|
|
|
INNER JOIN dirs as albdir
|
|
|
|
on albdir.id = alb.dir
|
2018-04-05 19:02:17 -07:00
|
|
|
INNER JOIN artists as art
|
|
|
|
on alb.artistid = art.id
|
|
|
|
LEFT JOIN genres as g
|
|
|
|
on s.genre == g.id
|
|
|
|
"""
|
|
|
|
|
|
|
|
params = []
|
|
|
|
|
|
|
|
conditions = []
|
|
|
|
if id and isinstance(id, int):
|
|
|
|
conditions.append("s.id = ?")
|
|
|
|
params.append(id)
|
|
|
|
elif id and isinstance(id, Iterable):
|
|
|
|
conditions.append("s.id IN ({})".format(",".join("?" * len(id))))
|
|
|
|
params += id
|
|
|
|
if genre:
|
|
|
|
conditions.append("g.name = ?")
|
|
|
|
params.append(genre)
|
2020-10-05 22:19:48 -07:00
|
|
|
if title_contains:
|
|
|
|
conditions.append("s.title LIKE ?")
|
|
|
|
params.append("%{}%".format(title_contains))
|
2018-04-05 19:02:17 -07:00
|
|
|
if conditions:
|
|
|
|
q += " WHERE " + " AND ".join(conditions)
|
|
|
|
|
|
|
|
if sortby:
|
|
|
|
q += " ORDER BY {}".format(sortby)
|
|
|
|
if order:
|
|
|
|
q += " {}".format(order)
|
|
|
|
|
|
|
|
if limit:
|
|
|
|
q += " LIMIT {}".format(limit) # TODO support limit pagination
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute(q, params)
|
|
|
|
for row in c:
|
2018-04-05 19:02:17 -07:00
|
|
|
songs.append(row)
|
|
|
|
return songs
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_genres(self, c, genre_id=None):
|
2018-04-05 19:02:17 -07:00
|
|
|
genres = []
|
|
|
|
q = "SELECT * FROM genres"
|
|
|
|
params = []
|
|
|
|
conditions = []
|
|
|
|
if genre_id:
|
|
|
|
conditions.append("id = ?")
|
|
|
|
params.append(genre_id)
|
|
|
|
if conditions:
|
|
|
|
q += " WHERE " + " AND ".join(conditions)
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute(q, params)
|
|
|
|
for row in c:
|
2018-04-05 19:02:17 -07:00
|
|
|
genres.append(row)
|
|
|
|
return genres
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
2020-10-05 23:06:07 -07:00
|
|
|
def get_cover(self, c, cover_id):
|
2018-04-05 19:02:17 -07:00
|
|
|
cover = None
|
2020-10-05 23:06:07 -07:00
|
|
|
for cover in c.execute("SELECT * FROM covers WHERE id = ?", (cover_id, )):
|
2018-04-05 19:02:17 -07:00
|
|
|
return cover
|
|
|
|
|
2020-10-05 23:06:07 -07:00
|
|
|
def get_cover_path(self, cover_id):
|
|
|
|
cover = self.get_cover(cover_id)
|
|
|
|
library = self.get_libraries(cover["library"])[0]
|
|
|
|
return os.path.join(library["path"], cover["path"])
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_subsonic_musicdir(self, c, dirid):
|
2018-04-05 19:02:17 -07:00
|
|
|
"""
|
|
|
|
The world is a harsh place.
|
|
|
|
Again, this bullshit exists only to serve subsonic clients. Given a directory ID it returns a dict containing:
|
|
|
|
- the directory itself
|
|
|
|
- its parent
|
|
|
|
- its child dirs
|
|
|
|
- its child media
|
|
|
|
|
|
|
|
that's a lie, it's a tuple and it's full of BS. read the code
|
|
|
|
"""
|
|
|
|
# find directory
|
|
|
|
dirinfo = None
|
2020-10-05 22:41:01 -07:00
|
|
|
for dirinfo in c.execute("SELECT * FROM dirs WHERE id = ?", (dirid, )):
|
2018-04-05 19:02:17 -07:00
|
|
|
pass
|
|
|
|
assert dirinfo
|
|
|
|
|
|
|
|
ret = None
|
|
|
|
|
|
|
|
# see if it matches the artists or albums table
|
|
|
|
artist = None
|
2020-10-05 22:41:01 -07:00
|
|
|
for artist in c.execute("SELECT * FROM artists WHERE dir = ?", (dirid, )):
|
2018-04-05 19:02:17 -07:00
|
|
|
pass
|
|
|
|
|
|
|
|
# if artist:
|
|
|
|
# get child albums
|
|
|
|
if artist:
|
|
|
|
ret = ("artist", dirinfo, artist)
|
|
|
|
children = []
|
2020-10-05 22:41:01 -07:00
|
|
|
for album in c.execute("SELECT * FROM albums WHERE artistid = ?", (artist["id"], )):
|
2018-04-05 19:02:17 -07:00
|
|
|
children.append(("album", album))
|
|
|
|
ret[2]['children'] = children
|
|
|
|
return ret
|
|
|
|
|
|
|
|
# else if album:
|
|
|
|
# get child tracks
|
|
|
|
album = None
|
2020-10-05 22:41:01 -07:00
|
|
|
for album in c.execute("SELECT * FROM albums WHERE dir = ?", (dirid, )):
|
2018-04-05 19:02:17 -07:00
|
|
|
pass
|
|
|
|
if album:
|
|
|
|
ret = ("album", dirinfo, album)
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
artist_info = c.execute("SELECT * FROM artists WHERE id = ?", (album["artistid"], )).fetchall()[0]
|
2018-04-05 19:02:17 -07:00
|
|
|
|
|
|
|
children = []
|
2020-10-05 22:41:01 -07:00
|
|
|
for song in c.execute("SELECT * FROM songs WHERE albumid = ? ORDER BY track, title ASC;", (album["id"], )):
|
2018-04-05 19:02:17 -07:00
|
|
|
song["_artist"] = artist_info
|
|
|
|
children.append(("song", song))
|
|
|
|
ret[2]['children'] = children
|
|
|
|
return ret
|
|
|
|
|
|
|
|
# Playlist related
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def add_playlist(self, c, ownerid, name, song_ids, public=False):
|
2018-04-05 19:02:17 -07:00
|
|
|
"""
|
|
|
|
Create a playlist
|
|
|
|
"""
|
|
|
|
now = time()
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute("INSERT INTO playlists (ownerid, name, public, created, changed) VALUES (?, ?, ?, ?, ?)",
|
|
|
|
(ownerid, name, public, now, now))
|
|
|
|
plid = c.lastrowid
|
2018-04-05 19:02:17 -07:00
|
|
|
for song_id in song_ids:
|
2020-10-05 22:41:01 -07:00
|
|
|
self.add_to_playlist(c, plid, song_id)
|
|
|
|
c.execute("COMMIT")
|
2018-04-05 19:02:17 -07:00
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def add_to_playlist(self, c, playlist_id, song_id):
|
2018-04-05 19:02:17 -07:00
|
|
|
# TODO deal with order column
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute("INSERT INTO playlist_entries (playlistid, songid) VALUES (?, ?)", (playlist_id, song_id))
|
2018-04-05 19:02:17 -07:00
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_playlist(self, c, playlist_id):
|
|
|
|
return c.execute("SELECT * FROM playlists WHERE id=?", (playlist_id, )).fetchone()
|
2018-04-05 19:02:17 -07:00
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_playlist_songs(self, c, playlist_id):
|
2018-04-05 19:02:17 -07:00
|
|
|
songs = []
|
|
|
|
q = """
|
|
|
|
SELECT
|
|
|
|
s.*,
|
|
|
|
alb.name as albumname,
|
|
|
|
alb.coverid as albumcoverid,
|
|
|
|
art.name as artistname,
|
|
|
|
art.name as artistid,
|
|
|
|
g.name as genrename
|
|
|
|
FROM playlist_entries as pe
|
|
|
|
INNER JOIN songs as s
|
|
|
|
on pe.songid == s.id
|
|
|
|
INNER JOIN albums as alb
|
|
|
|
on s.albumid == alb.id
|
|
|
|
INNER JOIN artists as art
|
|
|
|
on alb.artistid = art.id
|
|
|
|
LEFT JOIN genres as g
|
|
|
|
on s.genre == g.id
|
|
|
|
WHERE pe.playlistid = ?
|
|
|
|
ORDER BY pe.'order' ASC;
|
|
|
|
"""
|
2020-10-05 22:41:01 -07:00
|
|
|
for row in c.execute(q, (playlist_id, )):
|
2018-04-05 19:02:17 -07:00
|
|
|
songs.append(row)
|
|
|
|
return songs
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def get_playlists(self, c, user_id):
|
2018-04-05 19:02:17 -07:00
|
|
|
playlists = []
|
2020-10-05 22:41:01 -07:00
|
|
|
for row in c.execute("SELECT * FROM playlists WHERE ownerid=? or public=1", (user_id, )):
|
2018-04-05 19:02:17 -07:00
|
|
|
playlists.append(row)
|
|
|
|
return playlists
|
|
|
|
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def remove_index_from_playlist(self, c, playlist_id, index):
|
|
|
|
c.execute("DELETE FROM playlist_entries WHERE playlistid=? LIMIT ?, 1", (playlist_id, index, ))
|
|
|
|
c.execute("COMMIT")
|
|
|
|
|
|
|
|
@cursor
|
|
|
|
def empty_playlist(self, c, playlist_id):
|
2020-10-05 23:06:07 -07:00
|
|
|
#TODO combine with delete_playlist
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute("DELETE FROM playlist_entries WHERE playlistid=?", (playlist_id, ))
|
|
|
|
c.execute("COMMIT")
|
|
|
|
|
|
|
|
@cursor
|
|
|
|
def delete_playlist(self, c, playlist_id):
|
2020-10-05 23:06:07 -07:00
|
|
|
c.execute("DELETE FROM playlist_entries WHERE playlistid=?", (playlist_id, ))
|
2020-10-05 22:41:01 -07:00
|
|
|
c.execute("DELETE FROM playlists WHERE id=?", (playlist_id, ))
|
|
|
|
c.execute("COMMIT")
|
|
|
|
|
|
|
|
@cursor
|
|
|
|
def update_album_played(self, c, album_id, last_played=None):
|
|
|
|
c.execute("UPDATE albums SET played=? WHERE id=?", (last_played, album_id, ))
|
|
|
|
c.execute("COMMIT")
|
|
|
|
|
|
|
|
@cursor
|
|
|
|
def increment_album_plays(self, c, album_id):
|
|
|
|
c.execute("UPDATE albums SET plays = plays + 1 WHERE id=?", (album_id, ))
|
|
|
|
c.execute("COMMIT")
|
|
|
|
|
|
|
|
@cursor
|
|
|
|
def increment_track_plays(self, c, track_id):
|
|
|
|
c.execute("UPDATE songs SET plays = plays + 1 WHERE id=?", (track_id, ))
|
|
|
|
c.execute("COMMIT")
|
2020-10-05 22:12:40 -07:00
|
|
|
|
2018-04-05 19:02:17 -07:00
|
|
|
# User related
|
2020-10-05 22:41:01 -07:00
|
|
|
@cursor
|
|
|
|
def add_user(self, c, username, password, is_admin=False):
|
|
|
|
c.execute("INSERT INTO users (username, password, admin) VALUES (?, ?, ?)",
|
|
|
|
(username, hash_password(password), is_admin))
|
|
|
|
c.execute("COMMIT")
|
|
|
|
|
|
|
|
@cursor
|
|
|
|
def update_user(self, c, username, password, is_admin=False):
|
|
|
|
c.execute("UPDATE users SET password=?, admin=? WHERE username=?;",
|
|
|
|
(hash_password(password), is_admin, username))
|
|
|
|
c.execute("COMMIT")
|
|
|
|
|
|
|
|
@cursor
|
|
|
|
def get_user(self, c, user):
|
2018-04-05 19:02:17 -07:00
|
|
|
try:
|
|
|
|
column = "id" if type(user) is int else "username"
|
2020-10-05 22:41:01 -07:00
|
|
|
return c.execute("SELECT * FROM users WHERE {}=?;".format(column), (user, )).fetchall()[0]
|
2018-04-05 19:02:17 -07:00
|
|
|
except IndexError:
|
|
|
|
raise NotFoundError("User doesn't exist")
|