implemented new user feeds
[enigma2-plugins.git] / mytube / src / MyTubeService.py
1 # -*- coding: iso-8859-1 -*-
2 from enigma import ePythonMessagePump
3
4 from __init__ import decrypt_block
5 from ThreadQueue import ThreadQueue
6 import gdata.youtube
7 import gdata.youtube.service
8 from gdata.service import BadAuthentication
9
10 from twisted.web import client
11 from twisted.internet import reactor
12 from urllib2 import Request, URLError, urlopen as urlopen2
13 from socket import gaierror, error
14 import os, socket
15 from urllib import quote, unquote_plus, unquote, urlencode
16 from httplib import HTTPConnection, CannotSendRequest, BadStatusLine, HTTPException
17
18 from urlparse import parse_qs, parse_qsl
19 from threading import Thread
20
21 HTTPConnection.debuglevel = 1
22
23 # python on enimga2 has no https socket support
24 gdata.youtube.service.YOUTUBE_USER_FEED_URI = 'http://gdata.youtube.com/feeds/api/users'
25
26 def validate_cert(cert, key):
27         buf = decrypt_block(cert[8:], key)
28         if buf is None:
29                 return None
30         return buf[36:107] + cert[139:196]
31
32 def get_rnd():
33         try:
34                 rnd = os.urandom(8)
35                 return rnd
36         except:
37                 return None
38
39 std_headers = {
40         'User-Agent': 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100627 Firefox/3.6.6',
41         'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
42         'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
43         'Accept-Language': 'en-us,en;q=0.5',
44 }
45
46 #config.plugins.mytube = ConfigSubsection()
47 #config.plugins.mytube.general = ConfigSubsection()
48 #config.plugins.mytube.general.useHTTPProxy = ConfigYesNo(default = False)
49 #config.plugins.mytube.general.ProxyIP = ConfigIP(default=[0,0,0,0])
50 #config.plugins.mytube.general.ProxyPort = ConfigNumber(default=8080)
51 #class MyOpener(FancyURLopener):
52 #       version = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12'
53
54
55 class GoogleSuggestions():
56         def __init__(self):
57                 self.hl = "en"
58                 self.conn = None
59
60         def prepareQuery(self):
61                 #GET /complete/search?output=toolbar&client=youtube-psuggest&xml=true&ds=yt&hl=en&jsonp=self.gotSuggestions&q=s
62                 self.prepQuerry = "/complete/search?output=toolbar&client=youtube&xml=true&ds=yt&"
63                 if self.hl is not None:
64                         self.prepQuerry = self.prepQuerry + "hl=" + self.hl + "&"
65                 self.prepQuerry = self.prepQuerry + "jsonp=self.gotSuggestions&q="
66                 print "[MyTube - GoogleSuggestions] prepareQuery:",self.prepQuerry
67
68         def getSuggestions(self, queryString):
69                 self.prepareQuery()
70                 if queryString is not "":
71                         query = self.prepQuerry + quote(queryString)
72                         self.conn = HTTPConnection("google.com")
73                         try:
74                                 self.conn = HTTPConnection("google.com")
75                                 self.conn.request("GET", query, "", {"Accept-Encoding": "UTF-8"})
76                         except (CannotSendRequest, gaierror, error):
77                                 self.conn.close()
78                                 print "[MyTube - GoogleSuggestions] Can not send request for suggestions"
79                                 return None
80                         else:
81                                 try:
82                                         response = self.conn.getresponse()
83                                 except BadStatusLine:
84                                         self.conn.close()
85                                         print "[MyTube - GoogleSuggestions] Can not get a response from google"
86                                         return None
87                                 else:
88                                         if response.status == 200:
89                                                 data = response.read()
90                                                 header = response.getheader("Content-Type", "text/xml; charset=ISO-8859-1")
91                                                 charset = "ISO-8859-1"
92                                                 try:
93                                                         charset = header.split(";")[1].split("=")[1]
94                                                         print "[MyTube - GoogleSuggestions] Got charset %s" %charset
95                                                 except:
96                                                         print "[MyTube - GoogleSuggestions] No charset in Header, falling back to %s" %charset
97                                                 data = data.decode(charset).encode("utf-8")
98                                                 self.conn.close()
99                                                 return data
100                                         else:
101                                                 self.conn.close()
102                                                 return None
103                 else:
104                         return None
105
106 class MyTubeFeedEntry():
107         def __init__(self, feed, entry, favoritesFeed = False):
108                 self.feed = feed
109                 self.entry = entry
110                 self.favoritesFeed = favoritesFeed
111                 self.thumbnail = {}
112                 """self.myopener = MyOpener()
113                 urllib.urlopen = MyOpener().open
114                 if config.plugins.mytube.general.useHTTPProxy.value is True:
115                         proxy = {'http': 'http://'+str(config.plugins.mytube.general.ProxyIP.getText())+':'+str(config.plugins.mytube.general.ProxyPort.value)}
116                         self.myopener = MyOpener(proxies=proxy)
117                         urllib.urlopen = MyOpener(proxies=proxy).open
118                 else:
119                         self.myopener = MyOpener()
120                         urllib.urlopen = MyOpener().open"""
121
122         def isPlaylistEntry(self):
123                 return False
124
125         def getTubeId(self):
126                 #print "[MyTubeFeedEntry] getTubeId"
127                 ret = None
128                 if self.entry.media.player:
129                         split = self.entry.media.player.url.split("=")
130                         ret = split.pop()
131                         if ret.startswith('youtube_gdata'):
132                                 tmpval=split.pop()
133                                 if tmpval.endswith("&feature"):
134                                         tmp = tmpval.split("&")
135                                         ret = tmp.pop(0)
136                 return ret
137
138         def getTitle(self):
139                 #print "[MyTubeFeedEntry] getTitle",self.entry.media.title.text
140                 return self.entry.media.title.text
141
142         def getDescription(self):
143                 #print "[MyTubeFeedEntry] getDescription"
144                 if self.entry.media is not None and self.entry.media.description is not None:
145                         return self.entry.media.description.text
146                 return "not vailable"
147
148         def getThumbnailUrl(self, index = 0):
149                 #print "[MyTubeFeedEntry] getThumbnailUrl"
150                 if index < len(self.entry.media.thumbnail):
151                         return self.entry.media.thumbnail[index].url
152                 return None
153
154         def getPublishedDate(self):
155                 if self.entry.published is not None:
156                         return self.entry.published.text
157                 return "unknown"
158
159         def getViews(self):
160                 if self.entry.statistics is not None:
161                         return self.entry.statistics.view_count
162                 return "not available"
163
164         def getDuration(self):
165                 if self.entry.media is not None and self.entry.media.duration is not None:
166                         return self.entry.media.duration.seconds
167                 else:
168                         return 0
169
170         def getRatingAverage(self):
171                 if self.entry.rating is not None:
172                         return self.entry.rating.average
173                 return 0
174
175
176         def getNumRaters(self):
177                 if self.entry.rating is not None:
178                         return self.entry.rating.num_raters
179                 return ""
180
181         def getAuthor(self):
182                 authors = []
183                 for author in self.entry.author:
184                         authors.append(author.name.text)
185                 author = ", ".join(authors)
186                 return author
187
188         def subscribeToUser(self):
189                 username = self.getAuthor()
190                 return myTubeService.SubscribeToUser(username)
191                 
192         def addToFavorites(self):
193                 video_id = self.getTubeId()
194                 return myTubeService.addToFavorites(video_id)
195
196         def PrintEntryDetails(self):
197                 EntryDetails = { 'Title': None, 'TubeID': None, 'Published': None, 'Published': None, 'Description': None, 'Category': None, 'Tags': None, 'Duration': None, 'Views': None, 'Rating': None, 'Thumbnails': None}
198                 EntryDetails['Title'] = self.entry.media.title.text
199                 EntryDetails['TubeID'] = self.getTubeId()
200                 EntryDetails['Description'] = self.getDescription()
201                 EntryDetails['Category'] = self.entry.media.category[0].text
202                 EntryDetails['Tags'] = self.entry.media.keywords.text
203                 EntryDetails['Published'] = self.getPublishedDate()
204                 EntryDetails['Views'] = self.getViews()
205                 EntryDetails['Duration'] = self.getDuration()
206                 EntryDetails['Rating'] = self.getNumRaters()
207                 EntryDetails['RatingAverage'] = self.getRatingAverage()
208                 EntryDetails['Author'] = self.getAuthor()
209                 # show thumbnails
210                 list = []
211                 for thumbnail in self.entry.media.thumbnail:
212                         print 'Thumbnail url: %s' % thumbnail.url
213                         list.append(str(thumbnail.url))
214                 EntryDetails['Thumbnails'] = list
215                 #print EntryDetails
216                 return EntryDetails
217
218         def getVideoUrl(self):
219                 VIDEO_FMT_PRIORITY_MAP = {
220                         '38' : 1, #MP4 Original (HD)
221                         '37' : 2, #MP4 1080p (HD)
222                         '22' : 3, #MP4 720p (HD)
223                         '18' : 4, #MP4 360p
224                         '35' : 5, #FLV 480p
225                         '34' : 6, #FLV 360p
226                 }
227                 video_url = None
228                 video_id = str(self.getTubeId())
229
230                 # Getting video webpage
231                 #URLs for YouTube video pages will change from the format http://www.youtube.com/watch?v=ylLzyHk54Z0 to http://www.youtube.com/watch#!v=ylLzyHk54Z0.
232                 watch_url = 'http://www.youtube.com/watch?v=%s&gl=US&hl=en' % video_id
233                 watchrequest = Request(watch_url, None, std_headers)
234                 try:
235                         print "[MyTube] trying to find out if a HD Stream is available",watch_url
236                         watchvideopage = urlopen2(watchrequest).read()
237                 except (URLError, HTTPException, socket.error), err:
238                         print "[MyTube] Error: Unable to retrieve watchpage - Error code: ", str(err)
239                         return video_url
240
241                 # Get video info
242                 for el in ['&el=embedded', '&el=detailpage', '&el=vevo', '']:
243                         info_url = ('http://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en' % (video_id, el))
244                         request = Request(info_url, None, std_headers)
245                         try:
246                                 infopage = urlopen2(request).read()
247                                 videoinfo = parse_qs(infopage)
248                                 if ('url_encoded_fmt_stream_map' or 'fmt_url_map') in videoinfo:
249                                         break
250                         except (URLError, HTTPException, socket.error), err:
251                                 print "[MyTube] Error: unable to download video infopage",str(err)
252                                 return video_url
253
254                 if ('url_encoded_fmt_stream_map' or 'fmt_url_map') not in videoinfo:
255                         # Attempt to see if YouTube has issued an error message
256                         if 'reason' not in videoinfo:
257                                 print '[MyTube] Error: unable to extract "fmt_url_map" or "url_encoded_fmt_stream_map" parameter for unknown reason'
258                         else:
259                                 reason = unquote_plus(videoinfo['reason'][0])
260                                 print '[MyTube] Error: YouTube said: %s' % reason.decode('utf-8')
261                         return video_url
262
263                 video_fmt_map = {}
264                 fmt_infomap = {}
265                 if videoinfo.has_key('url_encoded_fmt_stream_map'):
266                         tmp_fmtUrlDATA = videoinfo['url_encoded_fmt_stream_map'][0].split(',')
267                 else:
268                         tmp_fmtUrlDATA = videoinfo['fmt_url_map'][0].split(',')
269                 for fmtstring in tmp_fmtUrlDATA:
270                         fmturl = fmtid = fmtsig = ""
271                         if videoinfo.has_key('url_encoded_fmt_stream_map'):
272                                 try:
273                                         for arg in fmtstring.split('&'):
274                                                 if arg.find('=') >= 0:
275                                                         print arg.split('=')
276                                                         key, value = arg.split('=')
277                                                         if key == 'itag':
278                                                                 if len(value) > 3:
279                                                                         value = value[:2]
280                                                                 fmtid = value
281                                                         elif key == 'url':
282                                                                 fmturl = value
283                                                         elif key == 'sig':
284                                                                 fmtsig = value
285                                                                 
286                                         if fmtid != "" and fmturl != "" and fmtsig != ""  and VIDEO_FMT_PRIORITY_MAP.has_key(fmtid):
287                                                 video_fmt_map[VIDEO_FMT_PRIORITY_MAP[fmtid]] = { 'fmtid': fmtid, 'fmturl': unquote_plus(fmturl), 'fmtsig': fmtsig }
288                                                 fmt_infomap[int(fmtid)] = "%s&signature=%s" %(unquote_plus(fmturl), fmtsig)
289                                         fmturl = fmtid = fmtsig = ""
290
291                                 except:
292                                         print "error parsing fmtstring:",fmtstring
293                                         
294                         else:
295                                 (fmtid,fmturl) = fmtstring.split('|')
296                         if VIDEO_FMT_PRIORITY_MAP.has_key(fmtid) and fmtid != "":
297                                 video_fmt_map[VIDEO_FMT_PRIORITY_MAP[fmtid]] = { 'fmtid': fmtid, 'fmturl': unquote_plus(fmturl) }
298                                 fmt_infomap[int(fmtid)] = unquote_plus(fmturl)
299                 print "[MyTube] got",sorted(fmt_infomap.iterkeys())
300                 if video_fmt_map and len(video_fmt_map):
301                         print "[MyTube] found best available video format:",video_fmt_map[sorted(video_fmt_map.iterkeys())[0]]['fmtid']
302                         best_video = video_fmt_map[sorted(video_fmt_map.iterkeys())[0]]
303                         video_url = "%s&signature=%s" %(best_video['fmturl'].split(';')[0], best_video['fmtsig'])
304                         print "[MyTube] found best available video url:",video_url
305
306                 return video_url
307
308         def getRelatedVideos(self):
309                 print "[MyTubeFeedEntry] getRelatedVideos()"
310                 for link in self.entry.link:
311                         #print "Related link: ", link.rel.endswith
312                         if link.rel.endswith("video.related"):
313                                 print "Found Related: ", link.href
314                                 return link.href
315
316         def getResponseVideos(self):
317                 print "[MyTubeFeedEntry] getResponseVideos()"
318                 for link in self.entry.link:
319                         #print "Responses link: ", link.rel.endswith
320                         if link.rel.endswith("video.responses"):
321                                 print "Found Responses: ", link.href
322                                 return link.href
323
324         def getUserVideos(self):
325                 print "[MyTubeFeedEntry] getUserVideos()"
326                 username = self.getAuthor()
327                 myuri = 'http://gdata.youtube.com/feeds/api/users/%s/uploads' % username
328                 print "Found Uservideos: ", myuri
329                 return myuri
330
331 class MyTubePlayerService():
332 #       Do not change the client_id and developer_key in the login-section!
333 #       ClientId: ytapi-dream-MyTubePlayer-i0kqrebg-0
334 #       DeveloperKey: AI39si4AjyvU8GoJGncYzmqMCwelUnqjEMWTFCcUtK-VUzvWygvwPO-sadNwW5tNj9DDCHju3nnJEPvFy4WZZ6hzFYCx8rJ6Mw
335
336         cached_auth_request = {}
337         current_auth_token = None
338
339         def __init__(self):
340                 print "[MyTube] MyTubePlayerService - init"
341                 self.feedentries = []
342                 self.feed = None
343
344         def startService(self):
345                 print "[MyTube] MyTubePlayerService - startService"
346
347                 self.yt_service = gdata.youtube.service.YouTubeService()
348                 
349                 # dont use it on class init; error on post and auth
350                 self.yt_service.developer_key = 'AI39si4AjyvU8GoJGncYzmqMCwelUnqjEMWTFCcUtK-VUzvWygvwPO-sadNwW5tNj9DDCHju3nnJEPvFy4WZZ6hzFYCx8rJ6Mw'
351                 self.yt_service.client_id = 'ytapi-dream-MyTubePlayer-i0kqrebg-0'
352
353                 if self.current_auth_token is not None:
354                         print "[MyTube] MyTubePlayerService - auth_cached"
355                         self.yt_service.SetClientLoginToken(self.current_auth_token)
356                 
357 #               self.loggedIn = False
358                 #os.environ['http_proxy'] = 'http://169.229.50.12:3128'
359                 #proxy = os.environ.get('http_proxy')
360                 #print "FOUND ENV PROXY-->",proxy
361                 #for a in os.environ.keys():
362                 #       print a
363
364         def stopService(self):
365                 print "[MyTube] MyTubePlayerService - stopService"
366                 del self.ytService
367
368         def getLoginTokenOnCurl(self, email, pw):
369
370                 opts = {
371                   'service':'youtube',
372                   'accountType': 'HOSTED_OR_GOOGLE',
373                   'Email': email,
374                   'Passwd': pw,
375                   'source': self.yt_service.client_id,
376                 }
377                 
378                 print "[MyTube] MyTubePlayerService - Starting auth request"
379                 result = os.popen('curl -s -k -X POST "%s" -d "%s"' % (gdata.youtube.service.YOUTUBE_CLIENTLOGIN_AUTHENTICATION_URL , urlencode(opts))).read()
380                 
381                 return result
382         
383         def getFormattedTokenRequest(self, email, pw):
384                 return dict(parse_qsl(self.getLoginTokenOnCurl(email, pw).strip().replace('\n', '&')))
385         
386         def getAuthedUsername(self):
387                 return self.cached_auth_request.get('YouTubeUser')
388
389         def auth_user(self, username, password, use_curl_fallback = True):
390                 print "[MyTube] MyTubePlayerService - auth_use - " + str(username)
391                 
392                 if self.current_auth_token is not None:
393                         print "[MyTube] MyTubePlayerService - auth_cached"
394                         self.yt_service.SetClientLoginToken(self.current_auth_token)
395                         return
396                 
397                 if use_curl_fallback is True:
398                         self.cached_auth_request = self.getFormattedTokenRequest(username, password)
399                         if self.cached_auth_request.get('Auth') is None:
400                                 raise('Got no auth token from curl; you need curl and valid youtube login data')
401                         
402                         self.yt_service.SetClientLoginToken(self.cached_auth_request.get('Auth'))
403                 else:
404                         self.yt_service.email = username
405                         self.yt_service.password  = password
406                         self.yt_service.ProgrammaticLogin()
407                         
408                 # reset any wrong token on wrong logins
409                 if self.is_auth() is False:
410                         print "[MyTube] MyTubePlayerService - auth_use - auth not possible resetting"
411                         self.yt_service.current_token = None
412                         self.yt_service.token_store.remove_all_tokens()
413                 else:
414                         self.current_auth_token = self.auth_token()
415
416         def is_auth(self):
417                 if self.current_auth_token is not None:
418                         return True             
419                 
420                 if self.yt_service.current_token is None:
421                         return False
422                 
423                 return self.yt_service.current_token.get_token_string() != 'None'
424
425         def auth_token(self):
426                 return self.yt_service.current_token.get_token_string()
427
428         def getFeedService(self, feedname):
429                 if feedname == "top_rated":
430                         return self.yt_service.GetTopRatedVideoFeed
431                 elif feedname == "most_viewed":
432                         return self.yt_service.GetMostViewedVideoFeed
433                 elif feedname == "recently_featured":
434                         return self.yt_service.GetRecentlyFeaturedVideoFeed
435                 elif feedname == "top_favorites":
436                         return self.yt_service.GetTopFavoritesVideoFeed
437                 elif feedname == "most_recent":
438                         return self.yt_service.GetMostRecentVideoFeed
439                 elif feedname == "most_discussed":
440                         return self.yt_service.GetMostDiscussedVideoFeed
441                 elif feedname == "most_linked":
442                         return self.yt_service.GetMostLinkedVideoFeed
443                 elif feedname == "most_responded":
444                         return self.yt_service.GetMostRespondedVideoFeed
445                 return self.yt_service.GetYouTubeVideoFeed
446
447         def getFeed(self, url, feedname = "", callback = None, errorback = None):
448                 print "[MyTube] MyTubePlayerService - getFeed:",url, feedname
449                 self.feedentries = []
450                 ytservice = self.yt_service.GetYouTubeVideoFeed
451                 
452                 if feedname == "my_subscriptions":
453                         url = "http://gdata.youtube.com/feeds/api/users/default/newsubscriptionvideos"
454                 elif feedname == "my_favorites":
455                         url = "http://gdata.youtube.com/feeds/api/users/default/favorites"
456                 elif feedname == "my_history":
457                         url = "http://gdata.youtube.com/feeds/api/users/default/watch_history?v=2"
458                 elif feedname == "my_recommendations":
459                         url = "http://gdata.youtube.com/feeds/api/users/default/recommendations?v=2"                                                    
460                 elif feedname == "my_watch_later":
461                         url = "http://gdata.youtube.com/feeds/api/users/default/watch_later?v=2"
462                 elif feedname in ("hd", "most_popular", "most_shared", "on_the_web"):
463                         if feedname == "hd":
464                                 url = "http://gdata.youtube.com/feeds/api/videos/-/HD"
465                         else:
466                                 url = url + feedname
467                 elif feedname in ("top_rated","most_viewed","recently_featured","top_favorites","most_recent","most_discussed","most_linked","most_responded"):
468                         url = None
469                         ytservice = self.getFeedService(feedname)
470
471                 queryThread = YoutubeQueryThread(ytservice, url, self.gotFeed, self.gotFeedError, callback, errorback)
472                 queryThread.start()
473                 return queryThread
474
475         def search(self, searchTerms, startIndex = 1, maxResults = 25,
476                                         orderby = "relevance", time = 'all_time', racy = "include",
477                                         author = "", lr = "", categories = "", sortOrder = "ascending",
478                                         callback = None, errorback = None):
479                 print "[MyTube] MyTubePlayerService - search()"
480                 self.feedentries = []
481                 query = gdata.youtube.service.YouTubeVideoQuery()
482                 query.vq = searchTerms
483                 query.orderby = orderby
484                 query.time = time
485                 query.racy = racy
486                 query.sortorder = sortOrder
487                 if lr is not None:
488                         query.lr = lr
489                 if categories[0] is not None:
490                         query.categories = categories
491                 query.start_index = startIndex
492                 query.max_results = maxResults
493                 queryThread = YoutubeQueryThread(self.yt_service.YouTubeQuery, query, self.gotFeed, self.gotFeedError, callback, errorback)
494                 queryThread.start()
495                 return queryThread
496
497         def gotFeed(self, feed, callback):
498                 if feed is not None:
499                         self.feed = feed
500                         for entry in self.feed.entry:
501                                 MyFeedEntry = MyTubeFeedEntry(self, entry)
502                                 self.feedentries.append(MyFeedEntry)
503                 if callback is not None:
504                         callback(self.feed)
505
506         def gotFeedError(self, exception, errorback):
507                 if errorback is not None:
508                         errorback(exception)
509
510         def SubscribeToUser(self, username):
511                 try:
512                         new_subscription = self.yt_service.AddSubscriptionToChannel(username_to_subscribe_to=username)
513         
514                         if isinstance(new_subscription, gdata.youtube.YouTubeSubscriptionEntry):
515                                 print '[MyTube] MyTubePlayerService: New subscription added'
516                                 return _('New subscription added')
517                         
518                         return _('Unknown error')
519                 except gdata.service.RequestError as req:
520                         return str('Error: ' + str(req[0]["body"]))
521                 except Exception as e:
522                         return str('Error: ' + e)
523         
524         def addToFavorites(self, video_id):
525                 try:
526                         video_entry = self.yt_service.GetYouTubeVideoEntry(video_id=video_id)
527                         response = self.yt_service.AddVideoEntryToFavorites(video_entry)
528                         
529                         # The response, if succesfully posted is a YouTubeVideoEntry
530                         if isinstance(response, gdata.youtube.YouTubeVideoEntry):
531                                 print '[MyTube] MyTubePlayerService: Video successfully added to favorites'
532                                 return _('Video successfully added to favorites')       
533         
534                         return _('Unknown error')
535                 except gdata.service.RequestError as req:
536                         return str('Error: ' + str(req[0]["body"]))
537                 except Exception as e:
538                         return str('Error: ' + e)
539         
540         def getTitle(self):
541                 return self.feed.title.text
542
543         def getEntries(self):
544                 return self.feedentries
545
546         def itemCount(self):
547                 return self.feed.items_per_page.text
548
549         def getTotalResults(self):
550                 if self.feed.total_results is None:
551                         return 0
552                                 
553                 return self.feed.total_results.text
554
555         def getNextFeedEntriesURL(self):
556                 for link in self.feed.link:
557                         if link.rel == "next":
558                                 return link.href
559                 return None
560
561         def getCurrentPage(self):
562                 if self.feed.start_index is None:
563                         return 1
564                 
565                 return int(int(self.feed.start_index.text) / int(self.itemCount())) + 1
566
567 class YoutubeQueryThread(Thread):
568         def __init__(self, query, param, gotFeed, gotFeedError, callback, errorback):
569                 Thread.__init__(self)
570                 self.messagePump = ePythonMessagePump()
571                 self.messages = ThreadQueue()
572                 self.gotFeed = gotFeed
573                 self.gotFeedError = gotFeedError
574                 self.callback = callback
575                 self.errorback = errorback
576                 self.query = query
577                 self.param = param
578                 self.canceled = False
579                 self.messagePump.recv_msg.get().append(self.finished)
580
581         def cancel(self):
582                 self.canceled = True
583
584         def run(self):
585                 try:
586                         if self.param is None:
587                                 feed = self.query()
588                         else:
589                                 feed = self.query(self.param)
590                         self.messages.push((True, feed, self.callback))
591                         self.messagePump.send(0)
592                 except Exception, ex:
593                         self.messages.push((False, ex, self.errorback))
594                         self.messagePump.send(0)
595
596         def finished(self, val):
597                 if not self.canceled:
598                         message = self.messages.pop()
599                         if message[0]:
600                                 self.gotFeed(message[1], message[2])
601                         else:
602                                 self.gotFeedError(message[1], message[2])
603
604 myTubeService = MyTubePlayerService()
605