More fiddly bits that probably no one else cares about.
[pyTivo/wgw.git] / plugins / webvideo / webvideo.py
blob34ccf50a000a48c08f027e645c542d14c00203a9
1 import Queue
2 import logging
3 import os
4 import shutil
5 import threading
6 import time
7 import urllib
8 import urllib2
9 import urlparse
10 import warnings
12 try:
13 import xml.etree.ElementTree as ElementTree
14 except ImportError:
15 try:
16 import elementtree.ElementTree as ElementTree
17 except ImportError:
18 warnings.warn('Python 2.5 or higher or elementtree is ' +
19 'needed to use the TivoPush')
21 import xmpp
22 import mind
23 import config
24 from plugins.video.video import Video, VideoDetails
26 CLASS_NAME = 'WebVideo'
28 class WebVideo(Video):
30 CONTENT_TYPE = 'x-not-for/tivo'
32 def init(self):
33 self.__logger = logging.getLogger('pyTivo.webvideo')
34 self.work_queue = Queue.Queue()
35 self.download_thread_num = 1
36 self.in_progress = {}
37 self.in_progress_lock = threading.Lock()
39 self.startXMPP()
40 self.startWorkerThreads()
42 def startXMPP(self):
43 m = mind.getMind()
44 xmpp_info = m.getXMPPLoginInfo()
46 jid=xmpp.protocol.JID(xmpp_info['username'] + '/pyTivo')
47 cl=xmpp.Client(
48 server=xmpp_info['server'],
49 port=xmpp_info['port'],
50 debug=[],
52 self.__logger.debug('Connecting to %s:%s' % (xmpp_info['server'],
53 xmpp_info['port']))
54 cl.connect()
55 cl.RegisterHandler('message', self.processMessage)
56 self.__logger.debug('Loging in as %s/pyTivo' % xmpp_info['username'])
57 cl.auth(user=jid.getNode(), password=config.getTivoPassword(),
58 resource='pyTivo')
60 cl.sendInitPresence(requestRoster=0)
62 for user_name in xmpp_info['presence_list']:
63 self.__logger.debug('Sending presence to %s' % user_name)
64 jid = xmpp.protocol.JID(user_name)
65 cl.sendPresence(jid)
67 t = threading.Thread(target=self.processXMPP, args=(cl,))
68 t.setDaemon(True)
69 t.start()
71 def startWorkerThreads(self):
72 for i in range(self.download_thread_num):
73 t = threading.Thread(target=self.processDlRequest,
74 name='webvideo downloader')
75 t.setDaemon(True)
76 t.start()
78 t = threading.Thread(target=self.watchQueue,
79 name='webvideo queue watcher')
80 t.setDaemon(True)
81 t.start()
83 def processXMPP(self, client):
84 while client.Process(3):
85 pass
87 def processMessage(self, sess, mess):
88 self.__logger.debug('Got message\n %s' % mess.getBody())
89 xmpp_action = ElementTree.fromstring(mess.getBody())
91 method_name = 'xmpp_' + xmpp_action.findtext('action').lower()
92 if not hasattr(self, method_name):
93 return False
95 method = getattr(self, method_name)
96 method(xmpp_action)
98 def watchQueue(self):
99 while True:
100 self.xmpp_cdsupdate()
101 time.sleep(60*15)
103 def xmpp_cdsupdate(self, xml=None):
104 m = mind.getMind()
106 self.in_progress_lock.acquire()
107 try:
108 for request in m.getDownloadRequests():
109 if not request['bodyOfferId'] in self.in_progress:
110 self.__logger.debug('Adding request to queue, %s' % request)
111 self.in_progress[request['bodyOfferId']] = True
112 self.work_queue.put(request)
113 finally:
114 self.in_progress_lock.release()
116 def processDlRequest(self):
118 while True:
119 data = self.work_queue.get()
121 for share_name, settings in config.getShares():
122 if settings['type'] == 'webvideo':
123 break
124 self.__logger.debug('Processing request: %s' % data)
126 path = settings['path']
128 file_name = os.path.join(path, '%s-%s' %
129 (data['bodyOfferId'].replace(':', '-'),
130 data['url'].split('/')[-1]))
132 self.downloadFile(data['url'], file_name)
134 tsn = data['bodyId']
135 file_info = VideoDetails()
136 file_info.update(self.metadata_full(file_name, tsn))
138 import socket
139 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
140 s.connect(('tivo.com',123))
141 ip = s.getsockname()[0]
142 port = config.getPort()
144 data['url'] = ('http://%s:%s' % (ip, port) +
145 urllib.quote('/%s/%s' % (share_name,
146 os.path.split(file_name)[-1])))
147 data['duration'] = file_info['duration'] / 1000
148 data['size'] = file_info['size']
150 self.__logger.debug('Complete request: %s' % data)
152 m = mind.getMind()
153 m.completeDownloadRequest(data)
155 self.in_progress_lock.acquire()
156 try:
157 del self.in_progress[data['bodyOfferId']]
158 finally:
159 self.in_progress_lock.release()
161 def downloadFile(self, url, file_path):
162 self.__logger.info('Downloading %s to %s' % (url, file_path))
164 outfile = open(file_path, 'awb')
165 size = os.path.getsize(file_path)
166 r = urllib2.Request(url)
167 if size:
168 r.add_header('Range', 'bytes=%s-' % size)
170 try:
171 infile = urllib2.urlopen(r)
172 except urllib2.HTTPError, e:
173 if not e.code == 416:
174 raise
175 infile = urllib2.urlopen(url)
176 if int(infile.info()['Content-Length']) == size:
177 self.__logger.debug('File was alraedy done. %s' % url)
178 return
179 else:
180 self.__logger.debug('File was not done but could not resume. %s'
181 % url)
182 outfile.close()
183 outfile = open(file_path, 'wb')
185 shutil.copyfileobj(infile, outfile, 8192)
187 self.__logger.info('Done downloading %s to %s' % (url, file_path))
189 def send_file(self, handler, container, name):
190 Video.send_file(self, handler, container, name)
192 o = urlparse.urlparse("http://fake.host" + handler.path)
193 path = urllib.unquote(o[2])
194 file_path = container['path'] + path[len(name) + 1:]
195 if os.path.exists(file_path):
196 self.__logger.info('Deleting file %s' % file_path)
197 os.unlink(file_path)