Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35ded0f592 | ||
|
|
e1de890f33 | ||
|
|
dc7f8905ae |
@@ -0,0 +1,16 @@
|
||||
[HTTP]
|
||||
port = 8585
|
||||
|
||||
[Daemon]
|
||||
port = 17170
|
||||
|
||||
[Library]
|
||||
musicdir = /media/USBHDD/shares/Music/English
|
||||
jancodir = /mnt/Muziek/
|
||||
|
||||
[Database]
|
||||
username = yjmpd
|
||||
password = qLjStxr6xncrfcna
|
||||
host = imegumii.nl
|
||||
database = yjmpd
|
||||
port = 3306
|
||||
@@ -0,0 +1,3 @@
|
||||
configparser
|
||||
mutagen
|
||||
PyMySQL
|
||||
@@ -0,0 +1,72 @@
|
||||
import sys
|
||||
import os
|
||||
import configparser
|
||||
|
||||
from yjdaemon.yjmpd import YJMPD
|
||||
from yjdaemon.HTTPServer import HTTPServerThread
|
||||
from yjdaemon.Database import Database
|
||||
from yjdaemon.libraryscanner import LibraryScanner
|
||||
|
||||
debug = True
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
try:
|
||||
config.read("config.cfg")
|
||||
HTTP_PORT = int(config.get("HTTP", "port"))
|
||||
DAEMON_PORT = int(config.get("Daemon","port"))
|
||||
MUSIC_DIR = str(config.get("Library", "musicdir"))
|
||||
DB_USERNAME = config.get("Database", "username")
|
||||
DB_PASSWORD = config.get("Database", "password")
|
||||
DB_HOST = config.get("Database", "host")
|
||||
DB_DATABASE = config.get("Database", "database")
|
||||
DB_PORT = config.getint("Database", "port")
|
||||
except Exception as e:
|
||||
print(e.with_traceback())
|
||||
sys.exit(1)
|
||||
|
||||
class MainDaemon(YJMPD):
|
||||
def run(self):
|
||||
HTTP_thread = HTTPServerThread(HTTP_PORT)
|
||||
HTTP_thread.start()
|
||||
|
||||
|
||||
def Test():
|
||||
print("Test")
|
||||
|
||||
# socket_thread = ServiceSocket(DAEMON_PORT)
|
||||
# socket_thread.start()
|
||||
#HTTP_thread = HTTPServerThread(HTTP_PORT)
|
||||
#HTTP_thread.start()
|
||||
db = Database(DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT, DB_DATABASE)
|
||||
LibraryScanner(db, MUSIC_DIR)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if debug:
|
||||
Test()
|
||||
else:
|
||||
if __name__ == "__main__":
|
||||
username = os.getenv('USER')
|
||||
if None == username:
|
||||
dir = "/tmp/.pydaemon.pid"
|
||||
else:
|
||||
dir = "/home/" + username + "/.pydaemon.pid"
|
||||
daemon = MainDaemon(dir, MUSIC_DIR)
|
||||
if len(sys.argv) == 2:
|
||||
if 'start' == sys.argv[1]:
|
||||
daemon.start()
|
||||
elif 'stop' == sys.argv[1]:
|
||||
daemon.stop()
|
||||
elif 'restart' == sys.argv[1]:
|
||||
daemon.restart()
|
||||
elif 'status' == sys.argv[1]:
|
||||
daemon.status()
|
||||
else:
|
||||
print("Unknown command")
|
||||
sys.exit(2)
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("usage: %s start|stop|status|restart" % sys.argv[0])
|
||||
sys.exit(2)
|
||||
@@ -0,0 +1,72 @@
|
||||
import sys
|
||||
import os
|
||||
import configparser
|
||||
|
||||
from yjdaemon.yjmpd import YJMPD
|
||||
from yjdaemon.HTTPServer import HTTPServerThread
|
||||
from yjdaemon.Database import Database
|
||||
from yjdaemon.libraryscanner import LibraryScanner
|
||||
|
||||
debug = True
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
try:
|
||||
config.read("config.cfg")
|
||||
HTTP_PORT = int(config.get("HTTP", "port"))
|
||||
DAEMON_PORT = int(config.get("Daemon","port"))
|
||||
MUSIC_DIR = str(config.get("Library", "musicdir"))
|
||||
DB_USERNAME = config.get("Database", "username")
|
||||
DB_PASSWORD = config.get("Database", "password")
|
||||
DB_HOST = config.get("Database", "host")
|
||||
DB_DATABASE = config.get("Database", "database")
|
||||
DB_PORT = config.getint("Database", "port")
|
||||
except Exception as e:
|
||||
print(e.with_traceback())
|
||||
sys.exit(1)
|
||||
|
||||
class MainDaemon(YJMPD):
|
||||
def run(self):
|
||||
HTTP_thread = HTTPServerThread(HTTP_PORT)
|
||||
HTTP_thread.start()
|
||||
|
||||
|
||||
def Test():
|
||||
print("Test")
|
||||
|
||||
# socket_thread = ServiceSocket(DAEMON_PORT)
|
||||
# socket_thread.start()
|
||||
HTTP_thread = HTTPServerThread(HTTP_PORT)
|
||||
HTTP_thread.start()
|
||||
#db = Database(DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT, DB_DATABASE)
|
||||
#LibraryScanner(db, MUSIC_DIR)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if debug:
|
||||
Test()
|
||||
else:
|
||||
if __name__ == "__main__":
|
||||
username = os.getenv('USER')
|
||||
if None == username:
|
||||
dir = "/tmp/.pydaemon.pid"
|
||||
else:
|
||||
dir = "/home/" + username + "/.pydaemon.pid"
|
||||
daemon = MainDaemon(dir, MUSIC_DIR)
|
||||
if len(sys.argv) == 2:
|
||||
if 'start' == sys.argv[1]:
|
||||
daemon.start()
|
||||
elif 'stop' == sys.argv[1]:
|
||||
daemon.stop()
|
||||
elif 'restart' == sys.argv[1]:
|
||||
daemon.restart()
|
||||
elif 'status' == sys.argv[1]:
|
||||
daemon.status()
|
||||
else:
|
||||
print("Unknown command")
|
||||
sys.exit(2)
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("usage: %s start|stop|status|restart" % sys.argv[0])
|
||||
sys.exit(2)
|
||||
@@ -0,0 +1,169 @@
|
||||
import json
|
||||
|
||||
from yjdaemon.Database import Database as db
|
||||
import configparser
|
||||
import string
|
||||
"""
|
||||
Add a key to the validAPIcalls dictionary, with a corresponding function
|
||||
Function should return jsonified data, so that it can then be passed on to the client.
|
||||
example:
|
||||
|
||||
Add this to the dictionary
|
||||
"getsongs": calls.getsongs
|
||||
|
||||
then implement this function
|
||||
@staticmethod
|
||||
def getsongs():
|
||||
return calls.jsonify({"song": song})
|
||||
|
||||
And the jsonified data will be returned to the client.
|
||||
|
||||
Every function MUST return jsonified data!
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class calls:
|
||||
@staticmethod
|
||||
def APIcall(sanitizedpath):
|
||||
if sanitizedpath in validAPIcalls:
|
||||
return validAPIcalls[sanitizedpath]
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def getfromrawjson(data, param):
|
||||
return calls.dejsonify(data)[param]
|
||||
|
||||
@staticmethod
|
||||
def jsonify(data):
|
||||
return json.dumps(data, sort_keys=True, indent=4).encode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def dejsonify(rawdata):
|
||||
return json.loads(rawdata.decode("utf-8"))
|
||||
|
||||
@staticmethod
|
||||
def getallsongs(args):
|
||||
return calls.jsonify({"songs": db.executequerystatic("SELECT * FROM tracks;"), "args": args , "result": "OK"})
|
||||
|
||||
@staticmethod
|
||||
def getartists(args):
|
||||
return calls.jsonify({"artists": db.executequerystatic("SELECT artistName FROM tracks GROUP BY artistName;"), "args": args, "result" : "OK"})
|
||||
|
||||
@staticmethod
|
||||
def getalbums(args):
|
||||
return calls.jsonify({"albums": db.executequerystatic("SELECT albumName FROM tracks GROUP BY albumName;"), "args": args, "result":"OK"})
|
||||
|
||||
@staticmethod
|
||||
def getgenres(args):
|
||||
return calls.jsonify({"genres": db.executequerystatic("SELECT genre FROM tracks GROUP BY genre;"), "args": args, "result":"OK"})
|
||||
|
||||
@staticmethod
|
||||
def getyears(args):
|
||||
return calls.jsonify({"years": db.executequerystatic("SELECT year FROM tracks GROUP BY year;"), "args": args, "result":"OK"})
|
||||
|
||||
@staticmethod
|
||||
def getalbumnames(args):
|
||||
return calls.jsonify({"albumnames": db.executequerystatic("SELECT albumName FROM tracks GROUP BY albumName;"), "args": args})
|
||||
|
||||
@staticmethod
|
||||
def search(args):
|
||||
data = args.split("&")
|
||||
genquery="""
|
||||
SELECT $ident FROM tracks WHERE LCASE(trackName) LIKE LCASE(\"%$trackname%\") $orgenre LCASE(genre) LIKE LCASE(\"%$genre%\") $oralbum LCASE(albumName) LIKE LCASE(\"%$album%\") $orartist LCASE(artistName) LIKE LCASE(\"%$artistname%\") $end;
|
||||
"""
|
||||
query="""
|
||||
SELECT $ident FROM tracks WHERE ($or1 OR $or2 OR $or3 ) $and1 $and2 $and3 $end;
|
||||
"""
|
||||
test = string.Template(query)
|
||||
genqueryres = ""
|
||||
arguments = []
|
||||
general = ""
|
||||
for entry in data:
|
||||
values = entry.split("=")
|
||||
values[1] = values[1].replace("%20"," ")
|
||||
if values[0] == "q":
|
||||
general = values[1]
|
||||
continue
|
||||
if values[1]:
|
||||
arguments.append(values)
|
||||
print(arguments)
|
||||
finalstring = "" + genquery
|
||||
for arg in arguments:
|
||||
if arg[0] == "artist":
|
||||
finalstring = string.Template(finalstring).safe_substitute(artistname=arg[1], orartist="AND")
|
||||
test = string.Template(test.safe_substitute(and1="AND LCASE(artistName) LIKE LCASE(\"%"+ arg[1] +"%\")"))
|
||||
elif arg[0] == "genre":
|
||||
finalstring = string.Template(finalstring).safe_substitute(genre=arg[1],orgenre="AND")
|
||||
test = string.Template(test.safe_substitute(and2="AND LCASE(genre) LIKE LCASE(\"%"+ arg[1] +"%\")"))
|
||||
elif arg[0] == "album":
|
||||
finalstring = string.Template(finalstring).safe_substitute(album=arg[1],oralbum="AND")
|
||||
test = string.Template(test.safe_substitute(and3="AND LCASE(albumName) LIKE LCASE(\"%"+ arg[1] +"%\")"))
|
||||
print(finalstring)
|
||||
finalstring = string.Template(finalstring).safe_substitute(genre=general,artistname=general,album=general,trackname=general)
|
||||
print(finalstring)
|
||||
test = test.safe_substitute( and1="", and2="",and3="",or1="LCASE(artistName) LIKE LCASE(\"%"+general+"%\")",or2="LCASE(genre) LIKE LCASE(\"%"+general+"%\")",or3="LCASE(albumName) LIKE LCASE(\"%"+general+"%\")")
|
||||
print(test)
|
||||
print(string.Template(test).safe_substitute(ident="*",end=""))
|
||||
return calls.jsonify({"result":"OK", "genres": db.executequerystatic(string.Template(test).safe_substitute(ident="genre",end="GROUP BY genre")),"artists": db.executequerystatic(string.Template(test).safe_substitute(ident="artistName",end="GROUP BY artistName")), "albums": db.executequerystatic(string.Template(test).safe_substitute(ident="albumName",end="GROUP BY albumName")), "songs" : db.executequerystatic(string.Template(test).safe_substitute(ident="*",end=""))})
|
||||
|
||||
@staticmethod
|
||||
def getsongs(args):
|
||||
"""Get song by album, genre, year, artist"""
|
||||
data = args.partition("?")
|
||||
splitstring = data[0].partition("=")
|
||||
name= splitstring[0]
|
||||
value = splitstring[2].replace("%20"," ")
|
||||
if name == "album":
|
||||
songs = db.executequerystatic("SELECT * FROM tracks WHERE albumName = \"" + value + "\"")
|
||||
elif name == "genre":
|
||||
songs = db.executequerystatic("SELECT * FROM tracks WHERE genre = \"" + value + "\"")
|
||||
elif name == "year":
|
||||
songs = db.executequerystatic("SELECT * FROM tracks WHERE year = \"" + value + "\"")
|
||||
elif name == "artist":
|
||||
songs = db.executequerystatic("SELECT * FROM tracks WHERE artistName = \"" + value + "\"")
|
||||
elif name == "search":
|
||||
songs = db.executequerystatic("SELECT * FROM tracks WHERE LCASE(trackName) LIKE LCASE(\"%"+ value + "%\") GROUP BY trackName")
|
||||
else:
|
||||
return calls.jsonify({"result":"NOK", "errormsg": "Not a valid argument"})
|
||||
return calls.jsonify({"result":"OK","songs":songs})
|
||||
|
||||
@staticmethod
|
||||
def getsongbyid(args):
|
||||
config = configparser.ConfigParser()
|
||||
try:
|
||||
config.read("config.cfg")
|
||||
musicdir = config.get("Library","musicdir")
|
||||
port = config.get("HTTP","port")
|
||||
except:
|
||||
return calls.jsonify({"result" : "NOK" , "errormsg" : "I/O error while reading config."})
|
||||
data = args.split("&")
|
||||
splitsting = data[0].split("=")
|
||||
id = splitsting[1]
|
||||
file = db.executequerystatic(
|
||||
"SELECT SUBSTRING_INDEX(trackUrl,'" + musicdir + "',-1) as filedir FROM `tracks` WHERE id = " + id)
|
||||
try:
|
||||
url = str(file[0][0])
|
||||
except:
|
||||
return calls.jsonify({"result": "NOK", "errormsg" : "Song ID does not exist in database."})
|
||||
return calls.jsonify({"result": "OK", "songurl": "http://imegumii.nl:"+ "/music/English"+ url})
|
||||
|
||||
@staticmethod
|
||||
def setsong(args, songname):
|
||||
global song
|
||||
song = songname
|
||||
return calls.jsonify({"result": "OK", "args": args})
|
||||
|
||||
|
||||
validAPIcalls = {"getallsongs": calls.getallsongs,
|
||||
"setsong": calls.setsong,
|
||||
"search": calls.search,
|
||||
"getsongs": calls.getsongs,
|
||||
"getsongbyid": calls.getsongbyid,
|
||||
"getartists": calls.getartists,
|
||||
"getalbums": calls.getalbums,
|
||||
"getgenres": calls.getgenres,
|
||||
"getyears": calls.getyears,
|
||||
"getalbumnames": calls.getalbumnames
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import pymysql
|
||||
import configparser
|
||||
from threading import RLock as Lock
|
||||
from warnings import filterwarnings
|
||||
|
||||
filterwarnings('ignore', category = pymysql.Warning)
|
||||
class Database:
|
||||
|
||||
buffer = []
|
||||
cout = 0
|
||||
lock = Lock()
|
||||
def __init__(self, DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT, DB_DATABASE):
|
||||
"""Init class """
|
||||
self.cnx = pymysql.connect(user=DB_USERNAME, password=DB_PASSWORD, host=DB_HOST, database=DB_DATABASE, port=DB_PORT, charset='utf8')
|
||||
self.cursor = self.cnx.cursor()
|
||||
|
||||
def executeQuery(self, query):
|
||||
try:
|
||||
self.cursor.execute(query)
|
||||
self.cnx.commit()
|
||||
return self.cursor.fetchall()
|
||||
except pymysql.ProgrammingError as e:
|
||||
print(e)
|
||||
print(query)
|
||||
|
||||
@staticmethod
|
||||
def executequerystatic(query):
|
||||
config = configparser.ConfigParser()
|
||||
try:
|
||||
config.read("config.cfg")
|
||||
DB_USERNAME = config.get("Database", "username")
|
||||
DB_PASSWORD = config.get("Database", "password")
|
||||
DB_HOST = config.get("Database", "host")
|
||||
DB_DATABASE = config.get("Database", "database")
|
||||
DB_PORT = config.getint("Database", "port")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
cnx = pymysql.connect(user=DB_USERNAME, password=DB_PASSWORD, host=DB_HOST, database=DB_DATABASE, port=DB_PORT)
|
||||
cursor = cnx.cursor()
|
||||
if query != "":
|
||||
cursor.execute(query)
|
||||
cnx.commit()
|
||||
return cursor.fetchall()
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def turnoffautocommit(self):
|
||||
self.cursor.execute("SET autocommit=0;")
|
||||
self.cnx.commit()
|
||||
|
||||
def removeSong(self, path):
|
||||
self.db.executeQuery("DELETE FROM `tracks` WHERE trackUrl = " + path.replace("'", '\\\''))
|
||||
|
||||
def updateInsertSong(self, genre, trackname, artistname, albumname, albumartist, tracknumber, year, duration, path): #notworking
|
||||
self.db.executeQuery("INSERT INTO `tracks` (`genre`, `trackUrl`, `trackName`, `artistName`, `albumName`, `albumArtist`, `trackNumber`, `year`, `duration`) VALUES ('" + genre + b"','" + path.replace("'", '\\\'') + "','" + trackname + "','" + artistname + "','" + albumname + "','" + albumartist + "','" + tracknumber + "','" + year + "','0') " +
|
||||
b" ON DUPLICATE KEY UPDATE `genre`=VALUES(`genre`) , `trackName` = VALUES(`trackName`) , `artistName` = VALUES(`artistName`) ,`albumName` = VALUES(`albumName`) , `albumArtist` = VALUES(`albumArtist`) , `trackNumber` = VALUES(`trackNumber`) , `year` = VALUES(`year`) , `duration` = VALUES(`duration`)")
|
||||
|
||||
def insertMultipleSongs(self, genre, trackname, artistname, albumname, albumartist, tracknumber, year, duration, path):
|
||||
self.buffer.append([genre, trackname, artistname, albumname, albumartist, tracknumber, year, duration, path])
|
||||
# Database.lock.acquire()
|
||||
if len(self.buffer) > 50:
|
||||
self.pushbuffer()
|
||||
# Database.lock.release()
|
||||
|
||||
def pushbuffer(self):
|
||||
query = ('INSERT INTO `tracks` (`genre`, `trackUrl`, `trackName`, `artistName`, `albumName`, `albumArtist`, `trackNumber`, `year`, `duration`) VALUES ')
|
||||
for song in self.buffer:
|
||||
query += "("
|
||||
for field in song:
|
||||
query += "'" + field + "',"
|
||||
query = query[:-1]
|
||||
query += "),"
|
||||
query = query[:-1]
|
||||
query += " ON DUPLICATE KEY UPDATE `genre`=VALUES(`genre`) , `trackName` = VALUES(`trackName`) , `artistName` = VALUES(`artistName`) ,`albumName` = VALUES(`albumName`) , `albumArtist` = VALUES(`albumArtist`) , `trackNumber` = VALUES(`trackNumber`) , `year` = VALUES(`year`) , `duration` = VALUES(`duration`);"
|
||||
self.executeQuery(query)
|
||||
del self.buffer[:]
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import http.server
|
||||
import socketserver
|
||||
import threading
|
||||
import html
|
||||
|
||||
import yjdaemon.API as API
|
||||
|
||||
"""
|
||||
HTTP request handler.
|
||||
"""
|
||||
|
||||
|
||||
class HTTPServerThread(threading.Thread):
|
||||
def __init__(self, PORT):
|
||||
threading.Thread.__init__(self)
|
||||
self.PORT = PORT
|
||||
|
||||
def run(self):
|
||||
Handler = HTTPHandler
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
httpd = socketserver.TCPServer(("", self.PORT), Handler)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
"""
|
||||
REST API
|
||||
"""
|
||||
|
||||
|
||||
class HTTPHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def send_message(self, status, content_type, data):
|
||||
self.send_response(status)
|
||||
self.send_header("Content-type:", content_type)
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
self.wfile.write("\n".encode("utf-8"))
|
||||
|
||||
def do_GET(self):
|
||||
""" Serve a GET request. """
|
||||
path = html.unescape(self.path)
|
||||
path = str(path).lstrip("/").split("?")[0]
|
||||
retval = API.calls.APIcall(path)
|
||||
if retval is not None: # if call is valid API function
|
||||
try:
|
||||
args = str(html.unescape(self.path)).lstrip("/").split("?")[1]
|
||||
except IndexError as e:
|
||||
print(e)
|
||||
self.send_message(403, "application/json", API.calls.jsonify({"error": "Missing parameters."}))
|
||||
return
|
||||
self.send_message(200, "application/json", retval(args))
|
||||
else: # else parse as normal HTTP request
|
||||
f = self.send_head()
|
||||
if f:
|
||||
try:
|
||||
self.copyfile(f, self.wfile)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def do_POST(self):
|
||||
""" Serve a POST request. """
|
||||
path = html.unescape(self.path)
|
||||
path = str(path).lstrip("/").split("?")[0]
|
||||
retval = API.calls.APIcall(path)
|
||||
if retval is not None: # if call is valid API function
|
||||
try:
|
||||
args = str(html.unescape(self.path)).lstrip("/").split("?")[1] #if no args
|
||||
except IndexError as e:
|
||||
print(e)
|
||||
self.send_message(403, "application/json", API.calls.jsonify({"error": "Missing parameters."}))
|
||||
return
|
||||
if self.headers["Content-Type"] == 'application/json': # if data is json data
|
||||
length = int(self.headers["Content-Length"])
|
||||
rawdata = self.rfile.read(length)
|
||||
result = retval(args, API.calls.getfromrawjson(rawdata, "data"))
|
||||
self.send_message(200, "application/json", result)
|
||||
else: # if not json data
|
||||
self.send_message(422, "application/json", API.calls.jsonify({"error": "Not JSON data."}))
|
||||
else: # if not API function
|
||||
self.send_message(403, "application/json", API.calls.jsonify({"error": "Not an API function"}))
|
||||
@@ -0,0 +1,36 @@
|
||||
import socket
|
||||
import threading
|
||||
|
||||
endline = "\r\n"
|
||||
|
||||
class ServiceSocket(threading.Thread):
|
||||
def __init__(self, PORT):
|
||||
threading.Thread.__init__(self)
|
||||
self.PORT = PORT
|
||||
|
||||
def run(self):
|
||||
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.s.bind(("", self.PORT))
|
||||
self.s.listen(1)
|
||||
while 1:
|
||||
conn, addr = self.s.accept()
|
||||
ClientHandler(conn).start()
|
||||
|
||||
|
||||
|
||||
class ClientHandler(threading.Thread):
|
||||
def __init__(self,conn):
|
||||
threading.Thread.__init__(self)
|
||||
self.conn = conn
|
||||
def run(self):
|
||||
while 1:
|
||||
data = self.conn.recv(1024)
|
||||
if not data:
|
||||
break
|
||||
else:
|
||||
datastring = data.decode('utf-8', "ignore").rstrip(endline).lower()
|
||||
print(datastring)
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from mutagen.easyid3 import EasyID3
|
||||
from mutagen.mp3 import MP3
|
||||
import mutagen._util
|
||||
import os
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
class LibraryScanner:
|
||||
|
||||
def __init__(self, Database, librarypath):
|
||||
"""Init class """
|
||||
self.url = librarypath
|
||||
self.db = Database
|
||||
self.scanRecursif()
|
||||
ob = Observer()
|
||||
ob.schedule(Filehandler(self), self.url, recursive=True)
|
||||
ob.start()
|
||||
|
||||
|
||||
def scanRecursif(self):
|
||||
print("Scanning library "+self.url+" recursively...")
|
||||
# musicdirs = [os.path.join(self.url,o) for o in os.listdir(self.url) if os.path.isdir(os.path.join(self.url,o))]
|
||||
self.db.turnoffautocommit()
|
||||
for root, directories, filenames in os.walk(self.url):
|
||||
self.scandir(filenames,root)
|
||||
|
||||
def scandir(self,filenames, root):
|
||||
for filename in filenames:
|
||||
if filename.lower().endswith(('.mp3')):
|
||||
path = os.path.join(root,filename)
|
||||
try:
|
||||
print(path, end='\r')
|
||||
id3 = EasyID3(path)
|
||||
audio = MP3(path)
|
||||
print(audio.info.length)
|
||||
self.db.insertMultipleSongs(self.getValue(id3, "genre"),path.replace("'", '\\\''),self.getValue(id3, "title"),self.getValue(id3, "artist"),self.getValue(id3, "album"),self.getValue(id3, "performer"),self.getValue(id3, "tracknumber"),self.getValue(id3, "date"),str(audio.info.length))
|
||||
except (mutagen.id3._util.ID3NoHeaderError):
|
||||
print("Error reading ID3 tag", end='\r')
|
||||
|
||||
|
||||
def insertSong(self, path):
|
||||
try:
|
||||
id3 = EasyID3(path)
|
||||
self.db.executeQuery(b"INSERT INTO `tracks` (`genre`, `trackUrl`, `trackName`, `artistName`, `albumName`, `albumArtist`, `trackNumber`, `year`, `duration`) VALUES ('" + self.getValue(id3,"genre") + b"'," + b"'" + path.replace("'", '\\\'').encode('utf8') + b"'," + b"'" + self.getValue(id3, "title") + b"'," + b"'" + self.getValue(id3, "artist") + b"'," + b"'" + self.getValue(id3, "album") + b"'," + b"'" + self.getValue(id3, "performer") + b"'," + b"'" + self.getValue(id3, "tracknumber") + b"'," + b"'" + self.getValue(id3, "date") + b"'," + b"'0') " +
|
||||
b" ON DUPLICATE KEY UPDATE `genre`=VALUES(`genre`) , `trackName` = VALUES(`trackName`) , `artistName` = VALUES(`artistName`) ,`albumName` = VALUES(`albumName`) , `albumArtist` = VALUES(`albumArtist`) , `trackNumber` = VALUES(`trackNumber`) , `year` = VALUES(`year`) , `duration` = VALUES(`duration`)")
|
||||
|
||||
except (mutagen.id3._util.ID3NoHeaderError):
|
||||
pass
|
||||
|
||||
def removeSong(self,path):
|
||||
self.db.removeSong(path)
|
||||
|
||||
def getValue(self, id3, value):
|
||||
try:
|
||||
return id3[value][0].replace("'", "\\'")
|
||||
except (KeyError, IndexError, ValueError):
|
||||
print("Error reading value of ID3 tag", end='\r')
|
||||
return ""
|
||||
|
||||
|
||||
class Filehandler(FileSystemEventHandler):
|
||||
def __init__(self, LibraryScanner):
|
||||
self.libscanner = LibraryScanner
|
||||
|
||||
def process(self, event):
|
||||
if not(event.is_directory):
|
||||
if os.path.isfile(event.src_path) and event.src_path.lower().endswith(('.mp3','.flac', 'm4a')):
|
||||
self.libscanner.insertSong(event.src_path)
|
||||
else:
|
||||
self.libscanner.removeSong(event.src_path)
|
||||
|
||||
def on_modified(self, event):
|
||||
self.process(event)
|
||||
|
||||
def on_created(self, event):
|
||||
self.process(event)
|
||||
@@ -0,0 +1,60 @@
|
||||
import sys
|
||||
import os
|
||||
import configparser
|
||||
from yjdaemon.Database import Database
|
||||
from yjdaemon.libraryscanner import LibraryScanner
|
||||
from yjdaemon.yjmpd import YJMPD
|
||||
from yjdaemon.HTTPServer import HTTPServerThread
|
||||
|
||||
debug = False
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
try:
|
||||
config.read("../config.cfg")
|
||||
HTTP_PORT = int(config.get("HTTP", "port"))
|
||||
DAEMON_PORT = int(config.get("Daemon", "port"))
|
||||
MUSIC_DIR = str(config.get("Library", "jancodir"))
|
||||
|
||||
DB_USERNAME = config.get("Database", "username")
|
||||
DB_PASSWORD = config.get("Database", "password")
|
||||
DB_HOST = config.get("Database", "host")
|
||||
DB_DATABASE = config.get("Database", "database")
|
||||
DB_PORT = config.getint("Database", "port")
|
||||
|
||||
except Exception as e:
|
||||
print(e.with_traceback())
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class MainDaemon(YJMPD):
|
||||
def run(self):
|
||||
HTTP_thread = HTTPServerThread(HTTP_PORT)
|
||||
HTTP_thread.start()
|
||||
db = Database(DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT, DB_DATABASE)
|
||||
LibraryScanner(db, MUSIC_DIR)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
username = os.getenv('USER')
|
||||
if None == username:
|
||||
dir = "/tmp/.pydaemon.pid"
|
||||
else:
|
||||
dir = "/home/" + username + "/.pydaemon.pid"
|
||||
daemon = MainDaemon(dir, MUSIC_DIR)
|
||||
if len(sys.argv) == 2:
|
||||
if 'start' == sys.argv[1]:
|
||||
daemon.start()
|
||||
elif 'stop' == sys.argv[1]:
|
||||
daemon.stop()
|
||||
elif 'restart' == sys.argv[1]:
|
||||
daemon.restart()
|
||||
elif 'status' == sys.argv[1]:
|
||||
daemon.status()
|
||||
else:
|
||||
print("Unknown command")
|
||||
sys.exit(2)
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("usage: %s start|stop|status|restart" % sys.argv[0])
|
||||
sys.exit(2)
|
||||
@@ -0,0 +1,135 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import atexit
|
||||
import signal
|
||||
|
||||
|
||||
class YJMPD:
|
||||
"""A generic yjdaemon class.
|
||||
|
||||
Usage: subclass the yjdaemon class and override the run() method."""
|
||||
|
||||
def __init__(self, pidfile, root):
|
||||
self.pidfile = pidfile
|
||||
self.root = root
|
||||
|
||||
def daemonize(self):
|
||||
"""Deamonize class. UNIX double fork mechanism."""
|
||||
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit first parent
|
||||
sys.exit(0)
|
||||
except OSError as err:
|
||||
sys.stderr.write('fork #1 failed: {0}\n'.format(err))
|
||||
sys.exit(1)
|
||||
|
||||
# decouple from parent environment
|
||||
os.chdir(self.root)
|
||||
os.setsid()
|
||||
os.umask(0)
|
||||
|
||||
# do second fork
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
# exit from second parent
|
||||
sys.exit(0)
|
||||
except OSError as err:
|
||||
sys.stderr.write('fork #2 failed: {0}\n'.format(err))
|
||||
sys.exit(1)
|
||||
|
||||
# redirect standard file descriptors
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
si = open(os.devnull, 'r')
|
||||
so = open(os.devnull, 'a+')
|
||||
se = open(os.devnull, 'a+')
|
||||
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
# write pidfile
|
||||
atexit.register(self.delpid)
|
||||
|
||||
pid = str(os.getpid())
|
||||
with open(self.pidfile, 'w+') as f:
|
||||
f.write(pid + '\n')
|
||||
|
||||
def delpid(self):
|
||||
os.remove(self.pidfile)
|
||||
|
||||
def start(self):
|
||||
"""Start the yjdaemon."""
|
||||
|
||||
# Check for a pidfile to see if the yjdaemon already runs
|
||||
try:
|
||||
with open(self.pidfile, 'r') as pf:
|
||||
|
||||
pid = int(pf.read().strip())
|
||||
except IOError:
|
||||
pid = None
|
||||
|
||||
if pid:
|
||||
message = "pidfile {0} already exist. " + \
|
||||
"Daemon already running?\n"
|
||||
sys.stderr.write(message.format(self.pidfile))
|
||||
sys.exit(1)
|
||||
|
||||
# Start the yjdaemon
|
||||
self.daemonize()
|
||||
self.run()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the yjdaemon."""
|
||||
|
||||
# Get the pid from the pidfile
|
||||
try:
|
||||
with open(self.pidfile, 'r') as pf:
|
||||
pid = int(pf.read().strip())
|
||||
except IOError:
|
||||
pid = None
|
||||
|
||||
if not pid:
|
||||
message = "pidfile {0} does not exist. " + \
|
||||
"Daemon not running?\n"
|
||||
sys.stderr.write(message.format(self.pidfile))
|
||||
return # not an error in a restart
|
||||
|
||||
# Try killing the yjdaemon process
|
||||
try:
|
||||
while 1:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
time.sleep(0.1)
|
||||
except OSError as err:
|
||||
e = str(err.args)
|
||||
if e.find("No such process") > 0:
|
||||
if os.path.exists(self.pidfile):
|
||||
os.remove(self.pidfile)
|
||||
else:
|
||||
print(str(err.args))
|
||||
sys.exit(1)
|
||||
|
||||
def restart(self):
|
||||
"""Restart the yjdaemon."""
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
|
||||
|
||||
def status(self):
|
||||
"""Print out status."""
|
||||
if os.path.exists(self.pidfile):
|
||||
message = "Daemon running.\n"
|
||||
else:
|
||||
message = "Daemon not running.\n"
|
||||
sys.stdout.write(message)
|
||||
|
||||
def run(self):
|
||||
"""You should override this method when you subclass Daemon.
|
||||
|
||||
It will be called after the process has been daemonized by
|
||||
start() or restart()."""
|
||||
@@ -0,0 +1,64 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 4.3.8deb0.1
|
||||
-- http://www.phpmyadmin.net
|
||||
--
|
||||
-- Host: localhost
|
||||
-- Gegenereerd op: 27 sep 2015 om 22:19
|
||||
-- Serverversie: 5.6.19-0ubuntu0.14.04.1
|
||||
-- PHP-versie: 5.5.9-1ubuntu4.11
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8 */;
|
||||
|
||||
--
|
||||
-- Database: `yjmpd`
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Tabelstructuur voor tabel `tracks`
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `tracks` (
|
||||
`id` int(11) NOT NULL,
|
||||
`genre` varchar(100) DEFAULT NULL,
|
||||
`trackUrl` varchar(200) NOT NULL,
|
||||
`trackName` varchar(100) DEFAULT NULL,
|
||||
`artistName` varchar(100) DEFAULT NULL,
|
||||
`albumName` varchar(100) DEFAULT NULL,
|
||||
`albumArtist` varchar(100) DEFAULT NULL,
|
||||
`trackNumber` int(11) DEFAULT NULL,
|
||||
`year` int(11) DEFAULT NULL,
|
||||
`duration` time DEFAULT NULL,
|
||||
`playCount` int(11) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=8135 DEFAULT CHARSET=latin1;
|
||||
|
||||
--
|
||||
-- Indexen voor geëxporteerde tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- Indexen voor tabel `tracks`
|
||||
--
|
||||
ALTER TABLE `tracks`
|
||||
ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `trackUrl` (`trackUrl`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT voor geëxporteerde tabellen
|
||||
--
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT voor een tabel `tracks`
|
||||
--
|
||||
ALTER TABLE `tracks`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=8135;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
Reference in New Issue
Block a user