9 from urllib
import unquote_plus
, quote
, unquote
10 from urlparse
import urlparse
11 from xml
.sax
.saxutils
import escape
13 from Cheetah
.Template
import Template
15 from plugin
import GetPlugin
17 SCRIPTDIR
= os
.path
.dirname(__file__
)
19 VIDEO_FORMATS
= """<?xml version="1.0" encoding="utf-8"?>
21 <ContentType>video/x-tivo-mpeg</ContentType><Description/>
22 </Format></TiVoFormats>"""
24 class TivoHTTPServer(SocketServer
.ThreadingMixIn
, BaseHTTPServer
.HTTPServer
):
27 def __init__(self
, server_address
, RequestHandlerClass
):
28 BaseHTTPServer
.HTTPServer
.__init
__(self
, server_address
,
30 self
.daemon_threads
= True
31 self
.logger
= logging
.getLogger('pyTivo')
33 def add_container(self
, name
, settings
):
34 if name
in self
.containers
or name
== 'TiVoConnect':
35 raise "Container Name in use"
37 settings
['content_type'] = GetPlugin(settings
['type']).CONTENT_TYPE
38 self
.containers
[name
] = settings
40 self
.logger
.error('Unable to add container ' + name
)
43 self
.containers
.clear()
44 for section
, settings
in config
.getShares():
45 self
.add_container(section
, settings
)
47 def handle_error(self
, request
, client_address
):
48 self
.logger
.exception('Exception during request from %s' %
51 def set_beacon(self
, beacon
):
54 class TivoHTTPHandler(BaseHTTPServer
.BaseHTTPRequestHandler
):
58 def __init__(self
, request
, client_address
, server
):
59 self
.wbufsize
= 0x10000
60 BaseHTTPServer
.BaseHTTPRequestHandler
.__init
__(self
, request
,
61 client_address
, server
)
63 def address_string(self
):
64 host
, port
= self
.client_address
[:2]
68 tsn
= self
.headers
.getheader('TiVo_TCD_ID',
69 self
.headers
.getheader('tsn', ''))
71 ip
= self
.address_string()
74 if not tsn
in self
.tivo_names
:
75 self
.tivo_names
[tsn
] = self
.server
.beacon
.get_name(ip
)
77 basepath
= unquote_plus(self
.path
).split('/')[1]
80 for name
, container
in self
.server
.containers
.items():
82 plugin
= GetPlugin(container
['type'])
83 plugin
.send_file(self
, container
, name
)
86 ## Not a file not a TiVo command
87 if not self
.path
.startswith('/TiVoConnect'):
91 o
= urlparse("http://fake.host" + self
.path
)
92 query
= cgi
.parse_qs(o
[4])
94 self
.handle_query(query
)
97 ctype
, pdict
= cgi
.parse_header(self
.headers
.getheader('content-type'))
98 if ctype
== 'multipart/form-data':
99 query
= cgi
.parse_multipart(self
.rfile
, pdict
)
101 length
= int(self
.headers
.getheader('content-length'))
102 qs
= self
.rfile
.read(length
)
103 query
= cgi
.parse_qs(qs
, keep_blank_values
=1)
104 self
.handle_query(query
)
106 def handle_query(self
, query
):
108 if 'Command' in query
and len(query
['Command']) >= 1:
110 command
= query
['Command'][0]
112 # If we are looking at the root container
113 if (command
== 'QueryContainer' and
114 (not 'Container' in query
or query
['Container'][0] == '/')):
115 self
.root_container()
118 if 'Container' in query
:
119 # Dispatch to the container plugin
120 basepath
= unquote(query
['Container'][0].split('/')[0])
121 for name
, container
in self
.server
.containers
.items():
123 plugin
= GetPlugin(container
['type'])
124 if hasattr(plugin
, command
):
125 method
= getattr(plugin
, command
)
131 elif (command
== 'QueryFormats' and 'SourceFormat' in query
and
132 query
['SourceFormat'][0].startswith('video')):
133 self
.send_response(200)
135 self
.wfile
.write(VIDEO_FORMATS
)
138 # If we made it here it means we couldn't match the request to
140 self
.unsupported(query
)
142 def log_message(self
, format
, *args
):
143 self
.server
.logger
.info("%s [%s] %s" % (self
.address_string(),
144 self
.log_date_time_string(), format
%args
))
146 def root_container(self
):
147 tsn
= self
.headers
.getheader('TiVo_TCD_ID', '')
148 tsnshares
= config
.getShares(tsn
)
150 for section
, settings
in tsnshares
:
152 settings
['content_type'] = \
153 GetPlugin(settings
['type']).CONTENT_TYPE
154 tsncontainers
[section
] = settings
155 except Exception, msg
:
156 self
.server
.logger
.error(section
+ ' - ' + msg
)
157 t
= Template(file=os
.path
.join(SCRIPTDIR
, 'templates',
158 'root_container.tmpl'))
159 t
.containers
= tsncontainers
160 t
.hostname
= socket
.gethostname()
163 self
.send_response(200)
168 self
.send_response(200)
169 self
.send_header('Content-type', 'text/html')
171 t
= Template(file=os
.path
.join(SCRIPTDIR
, 'templates',
174 for section
, settings
in config
.getShares():
175 if 'type' in settings
and settings
['type'] == 'admin':
176 t
.admin
+= ('<a href="/TiVoConnect?Command=Admin&Container=' +
178 '">Web Configuration</a><br>' +
179 '<a href="/TiVoConnect?Command=NPL&Container=' +
180 quote(section
) + '">ToGo</a><br>')
182 t
.admin
= ('<br><b>No Admin plugin installed in pyTivo.conf</b>' +
183 '<br> If you wish to use the admin plugin add the ' +
184 'following lines to pyTivo.conf<br><br>' +
185 '[Admin]<br>type=admin')
187 t
.shares
= 'Video shares:<br/>'
188 for section
, settings
in config
.getShares():
189 if settings
.get('type') == 'video':
190 t
.shares
+= ('<a href="TiVoConnect?Command=QueryContainer&' +
191 'Container=' + quote(section
) + '">' + section
+
196 def unsupported(self
, query
):
197 self
.send_response(404)
198 self
.send_header('Content-type', 'text/html')
200 t
= Template(file=os
.path
.join(SCRIPTDIR
, 'templates',
205 if __name__
== '__main__':
207 httpd
= TivoHTTPServer(('', 9032), TivoHTTPHandler
)
208 httpd
.add_container('test', 'x-container/tivo-videos',
209 r
'C:\Documents and Settings\Armooo' +
210 r
'\Desktop\pyTivo\test')
211 httpd
.serve_forever()