#!/usr/bin/env python

""" Livestreamer Deamon """

import os
import sys
import time
import atexit
import syslog

from signal import SIGTERM
 
try:
    from http.server import HTTPServer, BaseHTTPRequestHandler
    from socketserver import ThreadingMixIn
    from urllib.parse import unquote
except ImportError:
    from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
    from SocketServer import ThreadingMixIn
    from urllib import unquote

HOST_NAME = ""
PORT_NUMBER = 88

class NoStream(Exception):
    pass

def log(str):
    print(str)
    syslog.syslog(str)

def Stream(wfile, url, quality):
    try:
        Streamer(wfile, url, quality)
    except Exception as e:
        log("Got Exception: %s" % str(e))
        wfile.write(open("/usr/share/offline.mp4", "rb").read())
    wfile.close()


def Streamer(wfile, url, quality):
    from streamlink import Streamlink
    pluginclass, resolved_url = Streamlink().resolve_url(url)
    plugin = pluginclass(resolved_url)
    log("Found matching plugin {0} for URL {1}".format(plugin.module, resolved_url))
    streams = plugin.streams()
    log(str(streams))
    if not streams:
        raise NoStream("No Stream Found!")
    log("Streams: %s" % list(streams.keys()))
    stream = streams[quality]
    log("Stream: %s" % str(stream))
    fd = stream.open()
    while True:
        buff = fd.read(4096)
        if not buff:
           raise Exception("No Data!")
        wfile.write(buff)
    fd.close()
    fd = None
    raise Exception("End Of Data!")


def Resolver(url, quality):
    from streamlink import Streamlink
    pluginclass, resolved_url = Streamlink().resolve_url(url)
    plugin = pluginclass(resolved_url)
    streams = plugin.streams()
    if not streams:
        raise NoStream("No Stream Found!")
    log("Streams: %s" % list(streams.keys()))
    stream = streams[quality]
    log("Stream: %s" %  str(stream))
    return stream.to_url()

class StreamHandler(BaseHTTPRequestHandler):

    def do_HEAD(s):
        s.send_response(200)
        s.send_header("Server", "Enigma2 Livestreamer")
        s.send_header("Content-type", "text/html")
        s.end_headers()

    def do_GET(s):
        """Respond to a GET request."""
        url=unquote(s.path[1:])
        quality="best"

        if url.startswith("q=") and url.index("/") > 0:
            i = url.index("/")
            quality = url[2:i]
            url = url[i+1:]

        log("URL: %s Quality: %s" % (url, quality))

        try: # to resolve quality specified as url
            new_url = Resolver(url, quality)
            log("URL: %s resolved to new URL: %s" % (url, new_url))
            s.send_response(302)
            s.send_header("Server", "Enigma2 Livestreamer")
            s.send_header("Location", new_url)
            s.end_headers()
            return
        except NoStream:
            log("URL: %s has no streams active" % url)
            s.send_response(200)
            s.send_header("Server", "Enigma2 Livestreamer")
            s.send_header("Content-type", "video/mp4")
            s.end_headers()
            s.wfile.write(open("/usr/share/offline.mp4", "rb").read())
            s.wfile.close()
            return
        except:
            pass

        s.send_response(200)
        s.send_header("Server", "Enigma2 Livestreamer")
        s.send_header("Content-type", "application/octet-stream")
        s.end_headers()
        Stream(s.wfile, url, quality)


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""


def start():
    httpd = ThreadedHTTPServer((HOST_NAME, PORT_NUMBER), StreamHandler)
    print("%s Server Starts - %s:%s" % (time.asctime(), HOST_NAME, PORT_NUMBER))
    try:
        # we need valid time in order https certificates to work
        os.system("ntpd -nqp pool.ntp.org")
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()
    print("%s Server Stops - %s:%s" % (time.asctime(), HOST_NAME, PORT_NUMBER))


class Daemon:
	"""
	A generic daemon class.
	
	Usage: subclass the Daemon class and override the run() method
	"""
	def __init__(self, pidfile, stdin="/dev/null", stdout="/dev/null", stderr="/dev/null"):
		self.stdin = stdin
		self.stdout = stdout
		self.stderr = stderr
		self.pidfile = pidfile
	
	def daemonize(self):
		"""
		do the UNIX double-fork magic, see Stevens' "Advanced 
		Programming in the UNIX Environment" for details (ISBN 0201563177)
		http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
		"""
		try: 
			pid = os.fork() 
			if pid > 0:
				# exit first parent
				sys.exit(0) 
		except OSError as e:
			sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
			sys.exit(1)
	
		# decouple from parent environment
		os.chdir("/") 
		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 e:
			sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
			sys.exit(1) 
	
		# redirect standard file descriptors
		sys.stdout.flush()
		sys.stderr.flush()
		si = open(self.stdin, "r")
		so = open(self.stdout, "a+")
		se = open(self.stderr, "ab+", 0)
		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())
		open(self.pidfile,"w+").write("%s\n" % pid)
	
	def delpid(self):
		os.remove(self.pidfile)

	def start(self):
		"""
		Start the daemon
		"""
		# Check for a pidfile to see if the daemon already runs
		try:
			pf = open(self.pidfile,"r")
			pid = int(pf.read().strip())
			pf.close()
		except IOError:
			pid = None
	
		if pid:
			message = "pidfile %s already exist. Daemon already running?\n"
			sys.stderr.write(message % self.pidfile)
			sys.exit(1)
		
		# Start the daemon
		self.daemonize()
		self.run()

	def stop(self):
		"""
		Stop the daemon
		"""
		# Get the pid from the pidfile
		try:
			pf = open(self.pidfile,"r")
			pid = int(pf.read().strip())
			pf.close()
		except IOError:
			pid = None
	
		if not pid:
			message = "pidfile %s does not exist. Daemon not running?\n"
			sys.stderr.write(message % self.pidfile)
			return # not an error in a restart

		# Try killing the daemon process	
		try:
			while 1:
				os.kill(pid, SIGTERM)
				time.sleep(0.1)
		except OSError as err:
			err = str(err)
			if err.find("No such process") > 0:
				if os.path.exists(self.pidfile):
					os.remove(self.pidfile)
			else:
				print(str(err))
				sys.exit(1)

	def restart(self):
		"""
		Restart the daemon
		"""
		self.stop()
		self.start()

	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().
		"""


class LivestreamerDaemon(Daemon):
    def run(self):
        start()


if __name__ == "__main__":
    daemon = LivestreamerDaemon("/var/run/livestream.pid")
    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 "manualstart" == sys.argv[1]:
            start()
        else:
            print("Unknown command")
            sys.exit(2)
        sys.exit(0)
    else:
        print("usage: %s start|stop|restart|manualstart" % sys.argv[0])
        sys.exit(2)

