1# Modified to work with Exaile - Brian Parma 2# 3# Copyright (C) 2008 Erik Hetzner 4 5# This file is part of Spydaap. Spydaap is free software: you can 6# redistribute it and/or modify it under the terms of the GNU General 7# Public License as published by the Free Software Foundation, either 8# version 3 of the License, or (at your option) any later version. 9 10# Spydaap is distributed in the hope that it will be useful, but 11# WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# General Public License for more details. 14 15# You should have received a copy of the GNU General Public License 16# along with Spydaap. If not, see <https://www.gnu.org/licenses/>. 17 18import http.server 19import socketserver 20import logging 21import select 22import socket 23import os 24 25import spydaap 26import spydaap.daap 27import spydaap.metadata 28import spydaap.containers 29import spydaap.cache 30import spydaap.server 31import spydaap.zeroconfimpl 32 33from xl import common, event, xdg 34 35# Notes for debugging: 36# You might want to run 37# handle SIGPIPE nostop 38# when debugging this code in gdb. 39 40""" 41Notes for hunting down errors: 42If you run this plugin and a client stops playback on any file, expect this 43traceback: 44 45 Exception happened during processing of request from ('192.168.122.1', 34394) 46 Traceback (most recent call last): 47 File "/usr/lib64/python2.7/SocketServer.py", line 596, in process_request_thread 48 self.finish_request(request, client_address) 49 File "/usr/lib64/python2.7/SocketServer.py", line 331, in finish_request 50 self.RequestHandlerClass(request, client_address, self) 51 File "/usr/lib64/python2.7/SocketServer.py", line 654, in __init__ 52 self.finish() 53 File "/usr/lib64/python2.7/SocketServer.py", line 713, in finish 54 self.wfile.close() 55 File "/usr/lib64/python2.7/socket.py", line 283, in close 56 self.flush() 57 File "/usr/lib64/python2.7/socket.py", line 307, in flush 58 self._sock.sendall(view[write_offset:write_offset+buffer_size]) 59 error: [Errno 32] broken pipe 60 61This traceback is a result of getting a SIGPIPE, which is expected. The fact 62that it is not being handled in the python standard library is a bug which has 63been reported to https://bugs.python.org/issue14574 64See also: https://stackoverflow.com/questions/6063416/ 65""" 66 67 68logger = logging.getLogger('daapserver') 69 70__all__ = ['DaapServer'] 71 72 73class MyThreadedHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): 74 """Handle requests in a separate thread.""" 75 76 timeout = 1 77 daemon_threads = True 78 79 def __init__(self, *args): 80 if ':' in args[0][0]: 81 self.address_family = socket.AF_INET6 82 http.server.HTTPServer.__init__(self, *args) 83 84 85class DaapServer: 86 def __init__(self, library, name=spydaap.server_name, host='', port=spydaap.port): 87 # Thread.__init__(self) 88 self.host = host 89 self.port = port 90 self.library = library 91 self.name = name 92 self.httpd = None 93 self.handler = None 94 self.__cache = spydaap.cache.Cache(os.path.join(xdg.cache_home, 'daapserver')) 95 self.__cache.clean() 96 97 # Set a callback that will let us propagate library changes to clients 98 event.add_callback(self.update_rev, 'libraries_modified', library.collection) 99 100 def update_rev(self, *args): 101 if self.handler is not None: 102 # Updating the server revision, so if a client checks 103 # it can see the library has changed 104 self.handler.daap_server_revision += 1 105 logger.info( 106 'Libraries Changed, incrementing revision to %d.' 107 % self.handler.daap_server_revision 108 ) 109 self.__cache.clean() 110 111 def set(self, **kwargs): 112 for key in kwargs: 113 setattr(self, key, kwargs[key]) 114 115 @common.threaded 116 def run(self): 117 self.zeroconf = spydaap.zeroconfimpl.ZeroconfImpl( 118 self.name, self.port, stype="_daap._tcp" 119 ) 120 self.handler = spydaap.server.makeDAAPHandlerClass( 121 str(self.name), self.__cache, self.library, [] 122 ) 123 self.httpd = MyThreadedHTTPServer((self.host, self.port), self.handler) 124 125 # signal.signal(signal.SIGTERM, make_shutdown(httpd)) 126 # signal.signal(signal.SIGHUP, rebuild_cache) 127 if self.httpd.address_family == socket.AF_INET: 128 self.zeroconf.publish(ipv4=True, ipv6=False) 129 else: 130 self.zeroconf.publish(ipv4=False, ipv6=True) 131 132 try: 133 try: 134 logger.info("DAAP server: Listening.") 135 self.httpd.serve_forever() 136 except select.error: 137 pass 138 except KeyboardInterrupt: 139 self.httpd.shutdown() 140 141 logger.info("DAAP server: Shutting down.") 142 self.zeroconf.unpublish() 143 self.httpd = None 144 145 def start(self): 146 if self.httpd is None: 147 self.run() 148 return True 149 return False 150 151 def stop(self): 152 if self.httpd is not None: 153 self.httpd.shutdown() 154 self.httpd.socket.close() 155 return True 156 return False 157 158 def stop_server(self): 159 self.stop() 160 161 162# def rebuild_cache(signum=None, frame=None): 163# md_cache.build(os.path.abspath(spydaap.media_path)) 164# container_cache.clean() 165# container_cache.build(md_cache) 166# cache.clean() 167 168# def really_main(): 169# rebuild_cache() 170# zeroconf = spydaap.zeroconfimpl.ZeroconfImpl(spydaap.server_name, 171# spydaap.port, 172# stype="_daap._tcp") 173# zeroconf.publish() 174# logger.warning("Listening.") 175# httpd = MyThreadedHTTPServer(('0.0.0.0', spydaap.port), 176# spydaap.server.makeDAAPHandlerClass(spydaap.server_name, cache, md_cache, container_cache)) 177# 178## signal.signal(signal.SIGTERM, make_shutdown(httpd)) 179## signal.signal(signal.SIGHUP, rebuild_cache) 180# 181# try: 182# try: 183# httpd.serve_forever() 184# except select.error: 185# pass 186# except KeyboardInterrupt: 187# httpd.force_stop() 188# logger.warning("Shutting down.") 189# zeroconf.unpublish() 190 191# def main(): 192# really_main() 193 194# if __name__ == "__main__": 195# main() 196