1414import threading
1515import time
1616import hashlib
17+ from itertools import ifilter
1718
1819import SimpleHTTPServer
1920import SocketServer
@@ -83,7 +84,7 @@ def onScanStarted(self, library):
8384 def onScanFinished (self , library ):
8485 xbmc .Monitor .onScanFinished (self , library )
8586 xbmc .log ('%s: Library scan \' %s\' finished' % (ADDONID , library ))
86- self .watched_status .sync_status ()
87+ self .watched_status .sync_status () # TODO: do in new thread
8788
8889 def onNotification (self , sender , method , data ):
8990 xbmc .Monitor .onNotification (self , sender , method , data )
@@ -92,9 +93,9 @@ def onNotification(self, sender, method, data):
9293 if method == 'VideoLibrary.OnUpdate' :
9394 params = json .loads (data )
9495 item_type = params ['item' ]['type' ]
95- if item_type == 'episode' :
96+ if item_type == 'episode' and 'playcount' in params :
9697 item_id = params ['item' ]['id' ]
97- playcount = params . get ( 'playcount' , 0 )
98+ playcount = params [ 'playcount' ]
9899 self .watched_status .update_server_status (item_id , playcount > 0 )
99100 elif method == 'Player.OnStop' :
100101 params = json .loads (data )
@@ -147,9 +148,10 @@ def update_server_status(self, episode_id, watched):
147148 episode_key = season + '/' + episode
148149 if show_watched_status .get (episode_key ) != watched :
149150 eid = self .get_soap_episode_id (episode_details )
150- xbmc .log ('%s: Updating remote watched status of show \' %s\' season %s episode %s to %s' % (ADDONID , imdb , season , episode , watched ))
151- self .soap_api .mark_watched (eid , watched )
152- show_watched_status [episode_key ] = watched
151+ if eid is not None :
152+ xbmc .log ('%s: Updating remote watched status of show \' %s\' season %s episode %s to %s' % (ADDONID , imdb , season , episode , watched ))
153+ self .soap_api .mark_watched (eid , watched )
154+ show_watched_status [episode_key ] = watched
153155
154156 def sync_status (self ):
155157 for show in KodiApi .get_shows ():
@@ -165,16 +167,14 @@ def sync_status(self):
165167 episode_key = season + '/' + episode
166168 watched = show_watched_status .get (episode_key )
167169 if kodi_watched != watched :
168- xbmc .log ('%s: Updating locale watched status of show \' %s\' season %s episode %s to %s' % (ADDONID , imdb , season , episode , watched ))
170+ xbmc .log ('%s: Updating local watched status of show \' %s\' season %s episode %s to %s' % (ADDONID , imdb , season , episode , watched ))
169171 episode_id = e ['episodeid' ]
170172 KodiApi .set_watched (episode_id , watched )
171173
172174 @staticmethod
173175 def get_soap_episode_id (episode_details ):
174176 url = episode_details ['file' ]
175- parsed_params = urlparse .urlparse (url )
176- query_parsed = urlparse .parse_qs (parsed_params .query )
177- return query_parsed ['id' ][0 ]
177+ return WebHandler .get_episode_id (url )
178178
179179
180180class SoapCache (object ):
@@ -491,7 +491,7 @@ def main(self):
491491
492492 def my_shows (self ):
493493 data = self .client .request (self .MY_SHOWS_URL , use_cache = True )
494- # TODO: tvdb_id is used as IMDB because Kodi uses TVDB internaly for imdbnumber key
494+ # TODO: tvdb_id is used as IMDB because Kodi uses TVDB internally for imdbnumber key
495495 return map (lambda row : {'name' : row ['title' ], 'id' : row ['sid' ], 'IMDB' : row ['tvdb_id' ].replace ('tt' , '' )}, data )
496496
497497 def episodes (self , sid , imdb ):
@@ -547,6 +547,7 @@ def get_episode_url(self, sid, eid, ehash):
547547 return result ['stream' ]
548548
549549 def mark_watched (self , eid , watched ):
550+ # TODO: clean cache for show
550551 url = self .MARK_WATCHED if watched else self .MARK_UNWATCHED
551552 self .client .request (url .format (eid = eid ), {'eid' : eid })
552553
@@ -626,41 +627,46 @@ def set_watched(episode_id, watched):
626627 xbmc .executeJSONRPC (postdata )
627628
628629
630+ # NOTE: standard ?param=value¶m2=value2... notation is not used for url parameters because of
631+ # issue with endless directory scanning by Kodi
632+ # so for folder is used only show name
633+ # and for file name custom prefix containing all required IDs is used
629634class WebHandler (SimpleHTTPServer .SimpleHTTPRequestHandler ):
630635 match = None
631636
632637 def do_GET (self ):
633638 # Parse query data & params to find out what was passed
634639 xbmc .log ('%s: Serve \' %s\' ' % (ADDONID , self .path ))
635640 parsed_params = urlparse .urlparse (self .path )
636- query_parsed = urlparse .parse_qs (parsed_params .query )
637641 path = urllib .unquote (parsed_params .path )
638642
639643 if path == '/' :
640644 xbmc .log ('%s: Listing shows' % ADDONID )
641645 shows = self .server .api .my_shows ()
642646
643- self .out_folders (shows ,
644- lambda s : s ['name' ] + '/' ,
645- lambda s : urllib .quote (s ['name' ]) + '/?id=' + s ['id' ] + '&IMDB=' + s ['IMDB' ])
647+ self .out_folders (map (lambda s : s ['name' ], shows ))
646648 elif self .matches ('^/(.*)/$' , path ):
647649 show = self .match .group (1 )
648- sid = query_parsed ['id' ][0 ]
649- imdb = query_parsed ['IMDB' ][0 ]
650-
651- xbmc .log ('%s: Listing episodes of \' %s\' ' % (ADDONID , show ))
652- episodes = self .server .api .episodes (sid , imdb )
653-
654- self .out_files (episodes ,
655- lambda e : 'S' + e ['season' ] + 'E' + e ['episode' ] + '.avi' ,
656- lambda e : 'S' + e ['season' ] + 'E' + e ['episode' ] + '.avi?sid=' + sid + '&id=' + e ['id' ] + '&hash=' + e ['hash' ])
657- elif self .matches ('^/(.*)/S(\d+)E(\d+).avi$' , path ):
650+ show_details = self .find_show (show )
651+ if show_details is not None :
652+ sid = show_details ['id' ]
653+ imdb = show_details ['IMDB' ]
654+
655+ xbmc .log ('%s: Listing episodes of \' %s\' ' % (ADDONID , show ))
656+ episodes = self .server .api .episodes (sid , imdb )
657+
658+ # format parsable by TVDB scraper
659+ name_lambda = lambda e : sid + '_' + e ['id' ] + '_' + e ['hash' ] + '_S' + e ['season' ] + 'E' + e ['episode' ] + '.avi'
660+ self .out_files (map (name_lambda , episodes ))
661+ else :
662+ xbmc .log ('%s: ERROR: Show \' %s\' not found' % (ADDONID , show ))
663+ elif self .matches ('^/(.*)/(\d+)_(\d+)_([0-9a-f]+)_S(\d+)E(\d+).avi$' , path ):
658664 show = self .match .group (1 )
659- season = self .match .group (2 )
660- episode = self .match .group (3 )
661- sid = query_parsed [ 'sid' ][ 0 ]
662- eid = query_parsed [ 'id' ][ 0 ]
663- ehash = query_parsed [ 'hash' ][ 0 ]
665+ sid = self .match .group (2 )
666+ eid = self .match .group (3 )
667+ ehash = self . match . group ( 4 )
668+ season = self . match . group ( 5 )
669+ episode = self . match . group ( 6 )
664670
665671 xbmc .log ('%s: Requested episode %s from season %s of \' %s\' ' % (ADDONID , episode , season , show ))
666672 url = self .server .api .get_episode_url (sid , eid , ehash )
@@ -678,11 +684,29 @@ def matches(self, regexp, s):
678684 self .match = re .match (regexp , s , re .M | re .I )
679685 return self .match is not None
680686
681- def out_folders (self , folders , name_lambda , url_lambda ):
682- self .out_elements (map (lambda f : " <tr><td valign=\" top\" ><img src=\" /icons/folder.gif\" alt=\" [DIR]\" ></td><td><a href=\" %s\" >%s</a></td><td align=\" right\" >2016-11-01 23:18</td><td align=\" right\" > - </td><td> </td></tr>\n " % (url_lambda (f ), name_lambda (f )), folders ))
683-
684- def out_files (self , files , name_lambda , url_lambda ):
685- self .out_elements (map (lambda f : " <tr><td valign=\" top\" ><img src=\" /icons/movie.gif\" alt=\" [VID]\" ></td><td><a href=\" %s\" >%s</a></td><td align=\" right\" >2016-11-01 23:08</td><td align=\" right\" > 0 </td><td> </td></tr>\n " % (url_lambda (f ), name_lambda (f )), files ))
687+ @staticmethod
688+ def get_episode_id (url ):
689+ parsed_params = urlparse .urlparse (url )
690+ file_name = os .path .basename (parsed_params .path )
691+ return file_name .split ('_' )[1 ]
692+
693+ def out_folders (self , folders ):
694+ self .out_elements (map (lambda f : "<tr>"
695+ " <td valign=\" top\" ><img src=\" /icons/folder.gif\" alt=\" [DIR]\" ></td>"
696+ " <td><a href=\" %s/\" >%s</a></td>"
697+ " <td align=\" right\" >2016-11-01 23:18</td>"
698+ " <td align=\" right\" > - </td>"
699+ " <td> </td>"
700+ "</tr>\n " % (urllib .quote (f ), f ), folders ))
701+
702+ def out_files (self , files ):
703+ self .out_elements (map (lambda f : "<tr> "
704+ " <td valign=\" top\" ><img src=\" /icons/movie.gif\" alt=\" [VID]\" ></td>"
705+ " <td><a href=\" %s\" >%s</a></td>"
706+ " <td align=\" right\" >2016-11-01 23:08</td>"
707+ " <td align=\" right\" > 0 </td>"
708+ " <td> </td>"
709+ "</tr>\n " % (f , f ), files ))
686710
687711 def out_elements (self , elements ):
688712 self .send_response (200 )
@@ -706,6 +730,10 @@ def out_elements(self, elements):
706730 self .wfile .write ("</body></html>\n " )
707731 self .wfile .close ()
708732
733+ def find_show (self , show ):
734+ shows = self .server .api .my_shows () # should be cached
735+ return next (ifilter (lambda s : show == s ['name' ], shows ), None )
736+
709737
710738if __name__ == "__main__" :
711739 xbmc .log ('%s: Version %s started' % (ADDONID , ADDONVERSION ))
0 commit comments