def __init__(self, dport, lport, sport, pport, paswd, max_queue = MAX_QUEUE):
     self.__disc_port = int(dport)
     self.__disc_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     self.__disc_sock.bind(('', self.__disc_port))
     self.__disc_sock.settimeout(MAX_SLEEP)
     self.__disc_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     self.__disc_thread = threading.Thread(target=self.__bcast_responder)
     self.__disc_msg = bytearray(DISCOVERY_PACKET_SIZE)
     self.__list_server = TCPServer(\
         "new connection", lport, self.__handle_client,\
         self.__log)
     self.__str_server = TCPServer(\
         "stream", sport, self.__handle_stream,\
         self.__log)
     self.__play_server = TCPServer(\
         "playback", pport, self.__handle_playback,\
         self.__log)
     struct.pack_into('>ii%ds'%len(DISCOVERY_HEADER),\
             self.__disc_msg, 0, self.__list_server.get_port(),\
             self.__play_server.get_port(), DISCOVERY_HEADER)
     self.__password = paswd
     self.__is_done = threading.Event()
     self.__next_song = threading.Event()
     self.__pause = threading.Event()
     self.__device_queue = queue.Queue(max_queue)
     self.__stream_queue = queue.Queue()
     self.__metalock = threading.Lock()
     self.__metalist = []
     self.__next_stream = None
     self.__log_queue = queue.Queue()
     self.__log_thread = threading.Thread(target=self.__logger)
     self.__accept_thread = threading.Thread(\
         target=self.__accept_streams)
     self.__audio_lock = threading.Semaphore(1)
     self.__stream_lock = threading.Semaphore(1)
class DigiboxServer:
    class FinishedException(Exception):
        pass
    class __QueuedDevice:
        def __init__(self, ip_addr):
            self.ip_addr = ip_addr
            self.port = None
            self.sem = threading.Semaphore(0)
        def ready(self):
            self.sem.acquire()
        def set_port(self, port):
            self.port = port
            self.sem.release()
    class __QueuedStream:
        def __init__(self, token):
            self.token = token
            self.ev = threading.Event()
        def check_token(self, token):
            ret = self.token == token
            if ret:
                self.ev.set()
            return ret
        def checked(self):
            return self.ev.is_set()
    def __init__(self, dport, lport, sport, pport, paswd, max_queue = MAX_QUEUE):
        self.__disc_port = int(dport)
        self.__disc_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.__disc_sock.bind(('', self.__disc_port))
        self.__disc_sock.settimeout(MAX_SLEEP)
        self.__disc_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.__disc_thread = threading.Thread(target=self.__bcast_responder)
        self.__disc_msg = bytearray(DISCOVERY_PACKET_SIZE)
        self.__list_server = TCPServer(\
            "new connection", lport, self.__handle_client,\
            self.__log)
        self.__str_server = TCPServer(\
            "stream", sport, self.__handle_stream,\
            self.__log)
        self.__play_server = TCPServer(\
            "playback", pport, self.__handle_playback,\
            self.__log)
        struct.pack_into('>ii%ds'%len(DISCOVERY_HEADER),\
                self.__disc_msg, 0, self.__list_server.get_port(),\
                self.__play_server.get_port(), DISCOVERY_HEADER)
        self.__password = paswd
        self.__is_done = threading.Event()
        self.__next_song = threading.Event()
        self.__pause = threading.Event()
        self.__device_queue = queue.Queue(max_queue)
        self.__stream_queue = queue.Queue()
        self.__metalock = threading.Lock()
        self.__metalist = []
        self.__next_stream = None
        self.__log_queue = queue.Queue()
        self.__log_thread = threading.Thread(target=self.__logger)
        self.__accept_thread = threading.Thread(\
            target=self.__accept_streams)
        self.__audio_lock = threading.Semaphore(1)
        self.__stream_lock = threading.Semaphore(1)
    def __check_password(self, pwd):
        return pwd == self.__password
    def __play_pause(self):
        if self.__pause.is_set():
            self.__pause.clear()
        else:
            self.__pause.set()
    def __next(self):
        self.__next_song.set()
    def __bcast_responder(self):
        print("Responding to broadcasts on port %d"%self.__disc_port)
        while self.__go():
            try:
                _, addr = self.__disc_sock.recvfrom(0)
                self.__disc_sock.sendto(self.__disc_msg, addr)
                print("Responded to broadcast at %s:%d"%addr)
            except socket.timeout:
                pass
    def __handle_client(self, conn):
        c_sock, c_addr = conn
        self.__log("Received a new client connection from %s:%d"%c_addr)
        def target():
            try:
                m_data = read_dict(c_sock, on_timeout=self.__gohard)
                self.__log("Received metadata from %r: %r"%(c_addr, m_data))
                self.__queue_device(conn, m_data)
            finally:
                c_sock.close()
        t = threading.Thread(target=target)
        t.start()
    def __queue_device(self, conn, m_data):
        c_sock, c_addr = conn
        device = DigiboxServer.__QueuedDevice(c_addr[0])
        try:
            self.__device_queue.put_nowait(device)
            self.__metalock.acquire()
            self.__metalist.append(m_data)
            self.__metalock.release()
            self.__log("Queued device at %s"%c_addr[0])
            write_string(c_sock, b'wait')
            port = read_int(c_sock, self.__gohard)
            device.set_port(port)
            self.__log("Device at %s is waiting on UDP %d"%(c_addr[0], port))
        except queue.Full:
            self.__log("Queue is full, sorry %r"%c_addr[0])
            write_string(c_sock, b'full')
            write_int(c_sock, self.__device_queue.qsize())
    def __accept_streams(self):
        while self.__go():
            try:
                self.__stream_lock.acquire()
                qd = self.__device_queue.get(True, MAX_SLEEP)
                qd.ready()
                token = self.__gen_token()
                stream = DigiboxServer.__QueuedStream(token)
                self.__next_stream = stream
                udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                tries = 0
                msg = struct.pack('>i', self.__str_server.get_port())\
                    + token
                while self.__go() and not stream.checked()\
                        and tries < NOTIFY_ATTEMPTS:
                    udp_sock.sendto(msg, (qd.ip_addr, qd.port))
                    int_sleep(NOTIFY_PAUSE, self.__go)
                    tries += 1
                    self.__log("Finished attempt %d to contact %s:UDP%d"%\
                            (tries, qd.ip_addr, qd.port))
                if not stream.checked():
                    self.__log(("Failed to contact %s for streaming, "\
                        + "moving on")%qd.ip_addr)
                    self.__stream_lock.release()
                    self.__metalock.acquire()
                    self.__metalist.pop(0)
                    self.__metalock.release()
                    self.__next_stream = None
            except queue.Empty:
                self.__stream_lock.release()
    def __handle_stream(self, conn):
        c_sock, c_addr = conn
        c_sock.settimeout(MAX_SLEEP)
        self.__log("Handling stream connection from %s:%d"%c_addr)
        def target():
            try:
                token = read_bytes(c_sock, TOKEN_LENGTH, self.__gohard)
                done = False
                try:
                    while self.__go() and self.__next_stream is None:
                        sleep(MAX_SLEEP)
                    if self.__next_stream.check_token(token):
                        self.__next_stream = None
                        write_string(c_sock, b'go')
                        self.__audio_lock.acquire()
                        try:
                            if self.__go():
                                self.__playback(c_sock)
                        finally:
                            self.__audio_lock.release()
                    else:
                        write_string(c_sock, b'fail')
                except Exception as e:
                    self.__log(("There was a problem streaming "
                        + "from %s: %r")%\
                            (c_addr[0], e))
                finally:
                    self.__next_stream = None
            except Exception as e:
                self.__log("There was a problem with the stream: %r"%e)
            finally:
                c_sock.close()
        threading.Thread(target=target).start()
    def __get_info(self):
        def ser_str(string):
            return struct.pack('>i', len(string)) + string
        def ser_dict(dct):
            return struct.pack('>i', len(dct)) +\
                    b''.join(ser_str(k) + ser_str(v) for k, v in dct.items())
        data = dict()
        self.__metalock.acquire()
        data[b'songs'] = struct.pack('>i', len(self.__metalist)) +\
                b''.join(ser_dict(s) for s in self.__metalist)
        if not len(self.__metalist):
            status = b'empty'
        elif self.__pause.is_set():
            status = b'paused'
        else:
            status = b'playing'
        self.__metalock.release()
        data[b'status'] = status
        return data
    def __handle_playback(self, conn):
        sock, addr = conn
        actions = {\
                b'play': self.__play_pause,\
                b'pause': self.__play_pause,\
                b'next': self.__next\
                }
        def check_then_play(vals):
            if self.__check_password(vals[b'password']):
                try:
                    actions[vals[b'action']]()
                    write_string(sock, b'done')
                    return
                except Exception as e:
                    self.__log("password check passed but there was a problem: %r"%e)
            write_string(sock, b'fail')
        def request_info(_):
            write_dict(sock, self.__get_info())
        requests = {\
                b'playback': check_then_play,\
                b'getinfo': request_info,\
                }
        def target():
            req = read_dict(sock, on_timeout=self.__gohard)
            self.__log("Client requested %r"%req)
            try:
                requests[req[b'request']](req)
            except Exception as e:
                self.__log("request %r failed: %s"%(req, e))
            sock.close()
        threading.Thread(target=target).start()
    def __playback(self, sock):
        ffplay = subprocess.Popen(['ffplay', '-i', '-', '-nodisp', '-autoexit'], \
                stdin=subprocess.PIPE, stderr=open(os.devnull, 'wb'))
        try:
            length = 1
            while length:
                if self.__next_song.is_set():
                    length = 0
                    self.__next_song.clear()
                elif self.__pause.is_set():
                    sleep(MAX_SLEEP)
                else:
                    bts = read_string(sock, self.__gohard)
                    ffplay.stdin.write(bts)
                    length = len(bts)
        finally:
            ffplay.stdin.write(b'')
            ffplay.stdin.close()
            print("Closed ffplay STDIN")
            self.__stream_lock.release()
            ffplay.communicate()
            self.__metalock.acquire()
            self.__metalist.pop(0)
            self.__metalock.release()
            print("ffplay finished")
    def __log(self, msg):
        self.__log_queue.put(msg)
    def __logger(self):
        while self.__go():
            try:
                print(self.__log_queue.get(True, MAX_SLEEP))
            except queue.Empty:
                pass
    def __gen_token(self):
        return os.urandom(TOKEN_LENGTH)
    def __go(self):
        return not self.__is_done.is_set()
    def __gohard(self):
        if self.__is_done.is_set():
            raise DigiboxServer.FinishedException()
        return True
    def start(self):
        try:
            self.__log_thread.start()
            self.__str_server.start(self.__go)
            self.__accept_thread.start()
            self.__list_server.start(self.__go)
            self.__play_server.start(self.__go)
            sleep(MAX_SLEEP)
            self.__disc_thread.start()
            while True:
                key = input()
                if key == 'n':
                    self.__next()
                elif key == 'p':
                    self.__play_pause()
        except KeyboardInterrupt:
            self.__is_done.set()
            self.__audio_lock.release()
            self.__stream_lock.release()
            self.__next()