SeriesPlugin 1.2.4: Improved episode lookup if we have to search for alternative...
[enigma2-plugins.git] / seriesplugin / src / SeriesPlugin.py
1 # by betonme @2012
2
3 import re
4 import os, sys, traceback
5 from time import localtime, strftime
6 from datetime import datetime
7
8 # Localization
9 from . import _
10
11 from datetime import datetime
12
13 from Components.config import config
14
15 from enigma import eServiceReference, iServiceInformation, eServiceCenter, ePythonMessagePump
16 from ServiceReference import ServiceReference
17
18 # Plugin framework
19 from Modules import Modules
20
21 # Tools
22 from Tools.BoundFunction import boundFunction
23 from Tools.Directories import resolveFilename, SCOPE_PLUGINS
24 from Tools.Notifications import AddPopup
25 from Screens.MessageBox import MessageBox
26
27 # Plugin internal
28 from IdentifierBase import IdentifierBase
29 from ManagerBase import ManagerBase
30 from GuideBase import GuideBase
31 from Channels import ChannelsBase, removeEpisodeInfo, lookupServiceAlternatives
32 from Logger import splog
33 from CancelableThread import QueueWithTimeOut, CancelableThread, synchronized, myLock
34
35 from ThreadQueue import ThreadQueue
36 from threading import Thread
37 from enigma import ePythonMessagePump
38
39
40 try:
41         if(config.plugins.autotimer.timeout.value == 1):
42                 config.plugins.autotimer.timeout.value = 5
43                 config.plugins.autotimer.save()
44 except Exception as e:
45         pass
46
47
48 # Constants
49 AUTOTIMER_PATH  = os.path.join( resolveFilename(SCOPE_PLUGINS), "Extensions/AutoTimer/" )
50 SERIESPLUGIN_PATH  = os.path.join( resolveFilename(SCOPE_PLUGINS), "Extensions/SeriesPlugin/" )
51
52
53 # Globals
54 instance = None
55
56 CompiledRegexpNonDecimal = re.compile(r'[^\d]+')
57
58 def dump(obj):
59         for attr in dir(obj):
60                 splog( "config.plugins.seriesplugin.%s = %s" % (attr, getattr(obj, attr)) )
61
62
63 def getInstance():
64         global instance
65         
66         if instance is None:
67                 
68                 from plugin import VERSION
69                 
70                 splog("SERIESPLUGIN NEW INSTANCE " + VERSION)
71                 
72                 try:
73
74
75                         from Tools.HardwareInfo import HardwareInfo
76                         splog( "DeviceName " + HardwareInfo().get_device_name().strip() )
77                 except:
78                         pass
79                 
80                 try:
81                         from Components.About import about
82                         splog( "EnigmaVersion " + about.getEnigmaVersionString().strip() )
83                         splog( "ImageVersion " + about.getVersionString().strip() )
84                 except:
85                         pass
86                 
87                 try:
88                         #http://stackoverflow.com/questions/1904394/python-selecting-to-read-the-first-line-only
89                         splog( "dreamboxmodel " + open("/proc/stb/info/model").readline().strip() )
90                         splog( "imageversion " + open("/etc/image-version").readline().strip() )
91                         splog( "imageissue " + open("/etc/issue.net").readline().strip() )
92                 except:
93                         pass
94                 
95                 try:
96                         for key, value in config.plugins.seriesplugin.dict().iteritems():
97                                 splog( "config.plugins.seriesplugin.%s = %s" % (key, str(value.value)) )
98                 except Exception as e:
99                         pass
100                 
101                 #try:
102                 #       if os.path.exists(SERIESPLUGIN_PATH):
103                 #               dirList = os.listdir(SERIESPLUGIN_PATH)
104                 #               for fname in dirList:
105                 #                       splog( fname, datetime.fromtimestamp( int( os.path.getctime( os.path.join(SERIESPLUGIN_PATH,fname) ) ) ).strftime('%Y-%m-%d %H:%M:%S') )
106                 #except Exception as e:
107                 #       pass
108                 #try:
109                 #       if os.path.exists(AUTOTIMER_PATH):
110                 #               dirList = os.listdir(AUTOTIMER_PATH)
111                 #               for fname in dirList:
112                 #                       splog( fname, datetime.fromtimestamp( int( os.path.getctime( os.path.join(AUTOTIMER_PATH,fname) ) ) ).strftime('%Y-%m-%d %H:%M:%S') )
113                 #except Exception as e:
114                 #       pass
115                 
116                 instance = SeriesPlugin()
117                 #instance[os.getpid()] = SeriesPlugin()
118                 splog( strftime("%a, %d %b %Y %H:%M:%S", localtime()) )
119         
120         return instance
121
122 def resetInstance():
123         #Rename to closeInstance
124         global instance
125         if instance is not None:
126                 splog("SERIESPLUGIN INSTANCE STOP")
127                 instance.stop()
128                 instance = None
129         from Cacher import cache
130         global cache
131         cache = {}
132
133
134 def refactorTitle(org, data):
135         if data:
136                 season, episode, title, series = data
137                 if config.plugins.seriesplugin.pattern_title.value and not config.plugins.seriesplugin.pattern_title.value == "Off":
138                         #if season == 0 and episode == 0:
139                         #       return config.plugins.seriesplugin.pattern_title.value.strip().format( **{'org': org, 'title': title, 'series': series} )
140                         #else:
141                         return config.plugins.seriesplugin.pattern_title.value.strip().format( **{'org': org, 'season': season, 'episode': episode, 'title': title, 'series': series} )
142                 else:
143                         return org
144         else:
145                 return org
146
147 def refactorDescription(org, data):
148         if data:
149                 season, episode, title, series = data
150                 if config.plugins.seriesplugin.pattern_description.value and not config.plugins.seriesplugin.pattern_description.value == "Off":
151                         #if season == 0 and episode == 0:
152                         #       description = config.plugins.seriesplugin.pattern_description.value.strip().format( **{'org': org, 'title': title, 'series': series} )
153                         #else:
154                         description = config.plugins.seriesplugin.pattern_description.value.strip().format( **{'org': org, 'season': season, 'episode': episode, 'title': title, 'series': series} )
155                         description = description.replace("\n", " ")
156                         return description
157                 else:
158                         return org
159         else:
160                 return org
161                 
162 class ThreadItem:
163         def __init__(self, identifier = None, callback = None, name = None, begin = None, end = None, service = None, channels = None):
164                 self.identifier = identifier
165                 self.callback = callback
166                 self.name = name
167                 self.begin = begin
168                 self.end = end
169                 self.service = service
170                 self.channels = channels
171
172 class SeriesPluginWorker(Thread):
173
174         def __init__(self):
175                 Thread.__init__(self)
176                 self.__running = False
177                 self.__messages = ThreadQueue()
178                 self.__messagePump = ePythonMessagePump()
179                 self.__beginn = None
180                 self.__end = None
181
182         def __getMessagePump(self):
183                 return self.__messagePump
184         MessagePump = property(__getMessagePump)
185
186         def __getMessageQueue(self):
187                 return self.__messages
188         Message = property(__getMessageQueue)
189
190         def __getRunning(self):
191                 return self.__running
192         isRunning = property(__getRunning)
193
194         def Start(self, item):
195                 if not self.__running:
196                         self.__running = True
197                         splog("SeriesPluginRenamer")
198                         #self.__item = item
199                         self.__list = [item]
200                         self.start() # Start blocking code in Thread
201                 else:
202                         self.__list.append(item)
203         
204         def run(self):
205         
206                 while self.__list:
207                         item = self.__list.pop(0)
208                         splog('SeriesPluginWorker is processing: ', item.identifier)
209                         # do processing stuff here
210                         result = None
211                         
212                         try:
213                                 result = item.identifier.getEpisode(
214                                         item.name, item.begin, item.end, item.service, item.channels
215                                 )
216                         except Exception, e:
217                                 splog("SeriesPluginWorker Identifier Exception:", str(e))
218                                 
219                                 # Exception finish job with error
220                                 result = str(e)
221                         
222                         try:
223                                 splog("SeriesPluginWorker result")
224                                 if result and len(result) == 4:
225                                         season, episode, title, series = result
226                                         season = int(CompiledRegexpNonDecimal.sub('', season))
227                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
228                                         title = title.strip()
229                                         splog("SeriesPluginWorker result callback")
230                                         self.__messages.push((2, item.callback, season, episode, title, series,))
231                                         self.__messagePump.send(0)
232                                 else:
233                                         splog("SeriesPluginWorker result failed")
234                                         self.__messages.push((1, item.callback, result))
235                                         self.__messagePump.send(0)
236                         except Exception, e:
237                                 splog("SeriesPluginWorker Callback Exception:", str(e))
238                         
239                         config.plugins.seriesplugin.lookup_counter.value += 1
240                         config.plugins.seriesplugin.lookup_counter.save()
241                         
242                 self.__messages.push((0, result,))
243                 self.__messagePump.send(0)
244                 self.__running = False
245                 Thread.__init__(self)
246                 splog('SeriesPluginWorker] list is emty, done')
247
248 #TBD Because of E2 Update 05.2013
249 #                       if (config.plugins.seriesplugin.lookup_counter.value == 10) \
250 #                               or (config.plugins.seriesplugin.lookup_counter.value == 100) \
251 #                               or (config.plugins.seriesplugin.lookup_counter.value % 1000 == 0):
252 #                               from plugin import ABOUT
253 #                               about = ABOUT.format( **{'lookups': config.plugins.seriesplugin.lookup_counter.value} )
254 #                               AddPopup(
255 #                                       about,
256 #                                       MessageBox.TYPE_INFO,
257 #                                       0,
258 #                                       'SP_PopUp_ID_About'
259 #                               )
260
261 seriespluginworker = SeriesPluginWorker()
262
263 class SeriesPlugin(Modules, ChannelsBase):
264
265         def __init__(self):
266                 splog("SeriesPlugin")
267                 Modules.__init__(self)
268                 ChannelsBase.__init__(self)
269                 
270                 self.serviceHandler = eServiceCenter.getInstance()
271                 self.__pump_recv_msg_conn = seriespluginworker.MessagePump.recv_msg.connect(self.gotThreadMsg_seriespluginworker)
272                 
273                 #http://bugs.python.org/issue7980
274                 datetime.strptime('2012-01-01', '%Y-%m-%d')
275                         
276                 self.identifier_elapsed = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_elapsed.value )
277                 splog(self.identifier_elapsed)
278                 
279                 self.identifier_today = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_today.value )
280                 splog(self.identifier_today)
281                 
282                 self.identifier_future = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_future.value )
283                 splog(self.identifier_future)
284
285         def stop(self):
286                 splog("SeriesPlugin stop")
287                 if config.plugins.seriesplugin.lookup_counter.isChanged():
288                         config.plugins.seriesplugin.lookup_counter.save()
289                 self.saveXML()
290         
291         ################################################
292         # Identifier functions
293         def getIdentifier(self, future=False, today=False, elapsed=False):
294                 if elapsed:
295                         return self.identifier_elapsed and self.identifier_elapsed.getName()
296                 elif today:
297                         return self.identifier_today and self.identifier_today.getName()
298                 elif future:
299                         return self.identifier_future and self.identifier_future.getName()
300                 else:
301                         return None
302         
303         def getEpisode(self, callback, name, begin, end=None, service=None, future=False, today=False, elapsed=False):
304                 #available = False
305                 
306                 if config.plugins.seriesplugin.skip_during_records.value:
307                         try:
308                                 import NavigationInstance
309                                 if NavigationInstance.instance.RecordTimer.isRecording():
310                                         print("[SeriesPlugin]: Skip check during running records")
311                                         return
312                         except:
313                                 pass
314                 
315                 name = removeEpisodeInfo(name)
316                 #name = removeEpisodeInfo(name)
317                 if name.startswith("The ") or name.startswith("Der ") or name.startswith("Die ")or name.startswith("Das "):
318                         name = name[4:]
319                 
320                 begin = datetime.fromtimestamp(begin)
321                 end = datetime.fromtimestamp(end)
322                 
323                 if elapsed:
324                         identifier = self.identifier_elapsed
325                 elif today:
326                         identifier = self.identifier_today
327                 elif future:
328                         identifier = self.identifier_future
329                 else:
330                         identifier = None
331                 
332                 if identifier:
333                 
334                         # Reset title search depth on every new request
335                         identifier.search_depth = 0;
336                         
337                         # Reset the knownids on every new request
338                         identifier.knownids = []
339                         
340                         channels = lookupServiceAlternatives(service)
341
342                         seriespluginworker.Start(ThreadItem(identifier = identifier, callback = callback, name = name, begin = begin, end = end, service = service, channels = channels))
343                         
344                         '''
345                         try:
346                                 result = identifier.getEpisode(
347                                                                 name, begin, end, service, channels
348                                                         )
349                                 
350                                 if result and len(result) == 4:
351                                 
352                                         season, episode, title, series = result
353                                         season = int(CompiledRegexpNonDecimal.sub('', season))
354                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
355                                         #title = CompiledRegexpNonAlphanum.sub(' ', title)
356                                         title = title.strip()
357                                         splog("SeriesPlugin result callback")
358                                         callback( (season, episode, title, series) )
359                                         
360                                         config.plugins.seriesplugin.lookup_counter.value += 1
361                                         config.plugins.seriesplugin.lookup_counter.save()
362                                         
363                                 else:
364                                         splog("SeriesPlugin result failed")
365                                         callback( result )
366                                         
367                         except Exception as e:
368                                 splog("SeriesPlugin Callback Exception:", str(e))
369                                 callback( str(e) )
370                         '''
371                         
372                         return identifier.getName()
373                         
374                 #if not available:
375                 else:
376                         callback( "No identifier available" )
377                         
378         def gotThreadMsg_seriespluginworker(self, msg):
379                 msg = seriespluginworker.Message.pop()
380                 print ('gotThreadMsg_seriespluginrenameservice]msg: %s' %str(msg))
381                 if msg[0] == 2:
382                         callback = msg[1]
383                         if callable(callback):
384                                 callback((msg[2],msg[3],msg[4],msg[5]))
385                 elif msg[0] == 1:
386                         callback = msg[1]
387                         if callable(callback):
388                                 callback(msg[2])
389                 self.renamerCallback()
390
391
392         def renamerCallback(self):
393                 if (config.plugins.seriesplugin.lookup_counter.value == 10) \
394                         or (config.plugins.seriesplugin.lookup_counter.value == 100) \
395                         or (config.plugins.seriesplugin.lookup_counter.value % 1000 == 0):
396                         from plugin import ABOUT
397                         about = ABOUT.format( **{'lookups': config.plugins.seriesplugin.lookup_counter.value} )
398                         AddPopup(
399                                 about,
400                                 MessageBox.TYPE_INFO,
401                                 0,
402                                 'SP_PopUp_ID_About'
403                         )
404
405         def cancel(self):
406                 self.__pump_recv_msg_conn = None
407                 self.stop()