SeriesPlugin 1.0: First public version
[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                 try:
80                         from Components.About import about
81                         splog( "EnigmaVersion " + about.getEnigmaVersionString().strip() )
82                         splog( "ImageVersion " + about.getVersionString().strip() )
83                 except:
84                         pass
85                 try:
86                         #http://stackoverflow.com/questions/1904394/python-selecting-to-read-the-first-line-only
87                         splog( "dreamboxmodel " + open("/proc/stb/info/model").readline().strip() )
88                         splog( "imageversion " + open("/etc/image-version").readline().strip() )
89                         splog( "imageissue " + open("/etc/issue.net").readline().strip() )
90                 except:
91                         pass
92                 try:
93                         for key, value in config.plugins.seriesplugin.dict().iteritems():
94                                 splog( "config.plugins.seriesplugin.%s = %s" % (key, str(value.value)) )
95                 except Exception as e:
96                         pass
97                 try:
98                         if os.path.exists(SERIESPLUGIN_PATH):
99                                 dirList = os.listdir(SERIESPLUGIN_PATH)
100                                 for fname in dirList:
101                                         splog( fname, datetime.fromtimestamp( int( os.path.getctime( os.path.join(SERIESPLUGIN_PATH,fname) ) ) ).strftime('%Y-%m-%d %H:%M:%S') )
102                 except Exception as e:
103                         pass
104                 try:
105                         if os.path.exists(AUTOTIMER_PATH):
106                                 dirList = os.listdir(AUTOTIMER_PATH)
107                                 for fname in dirList:
108                                         splog( fname, datetime.fromtimestamp( int( os.path.getctime( os.path.join(AUTOTIMER_PATH,fname) ) ) ).strftime('%Y-%m-%d %H:%M:%S') )
109                 except Exception as e:
110                         pass
111                 
112                 instance = SeriesPlugin()
113                 #instance[os.getpid()] = SeriesPlugin()
114                 splog( strftime("%a, %d %b %Y %H:%M:%S", localtime()) )
115         
116         return instance
117
118 def resetInstance():
119         #Rename to closeInstance
120         global instance
121         if instance is not None:
122                 splog("SERIESPLUGIN INSTANCE STOP")
123                 instance.stop()
124                 instance = None
125         from Cacher import cache
126         global cache
127         cache = {}
128
129
130 def refactorTitle(org, data):
131         if data:
132                 season, episode, title, series = data
133                 if config.plugins.seriesplugin.pattern_title.value and not config.plugins.seriesplugin.pattern_title.value == "Off":
134                         #if season == 0 and episode == 0:
135                         #       return config.plugins.seriesplugin.pattern_title.value.strip().format( **{'org': org, 'title': title, 'series': series} )
136                         #else:
137                         return config.plugins.seriesplugin.pattern_title.value.strip().format( **{'org': org, 'season': season, 'episode': episode, 'title': title, 'series': series} )
138                 else:
139                         return org
140         else:
141                 return org
142
143 def refactorDescription(org, data):
144         if data:
145                 season, episode, title, series = data
146                 if config.plugins.seriesplugin.pattern_description.value and not config.plugins.seriesplugin.pattern_description.value == "Off":
147                         #if season == 0 and episode == 0:
148                         #       description = config.plugins.seriesplugin.pattern_description.value.strip().format( **{'org': org, 'title': title, 'series': series} )
149                         #else:
150                         description = config.plugins.seriesplugin.pattern_description.value.strip().format( **{'org': org, 'season': season, 'episode': episode, 'title': title, 'series': series} )
151                         description = description.replace("\n", " ")
152                         return description
153                 else:
154                         return org
155         else:
156                 return org
157                 
158 class ThreadItem:
159         def __init__(self, identifier = None, callback = None, name = None, begin = None, end = None, service = None, channels = None):
160                 self.identifier = identifier
161                 self.callback = callback
162                 self.name = name
163                 self.begin = begin
164                 self.end = end
165                 self.service = service
166                 self.channels = channels
167
168 class SeriesPluginWorker(Thread):
169
170         def __init__(self):
171                 Thread.__init__(self)
172                 self.__running = False
173                 self.__messages = ThreadQueue()
174                 self.__messagePump = ePythonMessagePump()
175                 self.__beginn = None
176                 self.__end = None
177
178         def __getMessagePump(self):
179                 return self.__messagePump
180         MessagePump = property(__getMessagePump)
181
182         def __getMessageQueue(self):
183                 return self.__messages
184         Message = property(__getMessageQueue)
185
186         def __getRunning(self):
187                 return self.__running
188         isRunning = property(__getRunning)
189
190         def Start(self, item):
191                 if not self.__running:
192                         self.__running = True
193                         splog("SeriesPluginRenamer")
194                         #self.__item = item
195                         self.__list = [item]
196                         self.start() # Start blocking code in Thread
197                 else:
198                         self.__list.append(item)
199         
200         def run(self):
201         
202                 while self.__list:
203                         item = self.__list.pop(0)
204                         splog('SeriesPluginWorker is processing: ', item.identifier)
205                         # do processing stuff here
206                         result = None
207                         
208                         try:
209                                 result = item.identifier.getEpisode(
210                                         item.name, item.begin, item.end, item.service, item.channels
211                                 )
212                         except Exception, e:
213                                 splog("SeriesPluginWorker Identifier Exception:", str(e))
214                                 
215                                 # Exception finish job with error
216                                 result = str(e)
217                         
218                         try:
219                                 splog("SeriesPluginWorker result")
220                                 if result and len(result) == 4:
221                                         season, episode, title, series = result
222                                         season = int(CompiledRegexpNonDecimal.sub('', season))
223                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
224                                         title = title.strip()
225                                         splog("SeriesPluginWorker result callback")
226                                         self.__messages.push((2, item.callback, season, episode, title, series,))
227                                         self.__messagePump.send(0)
228                                 else:
229                                         splog("SeriesPluginWorker result failed")
230                                         self.__messages.push((1, item.callback, result))
231                                         self.__messagePump.send(0)
232                         except Exception, e:
233                                 splog("SeriesPluginWorker Callback Exception:", str(e))
234                         
235                         config.plugins.seriesplugin.lookup_counter.value += 1
236                         config.plugins.seriesplugin.lookup_counter.save()
237                         
238                 self.__messages.push((0, result,))
239                 self.__messagePump.send(0)
240                 self.__running = False
241                 Thread.__init__(self)
242                 splog('SeriesPluginWorker] list is emty, done')
243
244 #TBD Because of E2 Update 05.2013
245 #                       if (config.plugins.seriesplugin.lookup_counter.value == 10) \
246 #                               or (config.plugins.seriesplugin.lookup_counter.value == 100) \
247 #                               or (config.plugins.seriesplugin.lookup_counter.value % 1000 == 0):
248 #                               from plugin import ABOUT
249 #                               about = ABOUT.format( **{'lookups': config.plugins.seriesplugin.lookup_counter.value} )
250 #                               AddPopup(
251 #                                       about,
252 #                                       MessageBox.TYPE_INFO,
253 #                                       0,
254 #                                       'SP_PopUp_ID_About'
255 #                               )
256
257 seriespluginworker = SeriesPluginWorker()
258
259 class SeriesPlugin(Modules, ChannelsBase):
260
261         def __init__(self):
262                 splog("SeriesPlugin")
263                 Modules.__init__(self)
264                 ChannelsBase.__init__(self)
265                 
266                 self.serviceHandler = eServiceCenter.getInstance()
267                 seriespluginworker.MessagePump.recv_msg.get().append(self.gotThreadMsg_seriespluginworker)
268                 #http://bugs.python.org/issue7980
269                 datetime.strptime('2012-01-01', '%Y-%m-%d')
270                         
271                 self.identifier_elapsed = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_elapsed.value )
272                 splog(self.identifier_elapsed)
273                 
274                 self.identifier_today = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_today.value )
275                 splog(self.identifier_today)
276                 
277                 self.identifier_future = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_future.value )
278                 splog(self.identifier_future)
279
280
281         def stop(self):
282                 splog("SeriesPlugin stop")
283                 if config.plugins.seriesplugin.lookup_counter.isChanged():
284                         config.plugins.seriesplugin.lookup_counter.save()
285                 self.saveXML()
286         
287         ################################################
288         # Identifier functions
289         def getIdentifier(self, future=False, today=False, elapsed=False):
290                 if elapsed:
291                         return self.identifier_elapsed and self.identifier_elapsed.getName()
292                 elif today:
293                         return self.identifier_today and self.identifier_today.getName()
294                 elif future:
295                         return self.identifier_future and self.identifier_future.getName()
296                 else:
297                         return None
298         
299         def getEpisode(self, callback, name, begin, end=None, service=None, future=False, today=False, elapsed=False):
300                 #available = False
301                 
302                 if config.plugins.seriesplugin.skip_during_records.value:
303                         try:
304                                 import NavigationInstance
305                                 if NavigationInstance.instance.RecordTimer.isRecording():
306                                         print("[SeriesPlugin]: Skip check during running records")
307                                         return
308                         except:
309                                 pass
310                 
311                 name = removeEpisodeInfo(name)
312                 name = removeEpisodeInfo(name)
313                 if name.startswith("The ") or name.startswith("Der ") or name.startswith("Die ")or name.startswith("Das "):
314                         name = name[4:]
315                 
316                 begin = datetime.fromtimestamp(begin)
317                 end = datetime.fromtimestamp(end)
318                 
319                 if elapsed:
320                         identifier = self.identifier_elapsed
321                 elif today:
322                         identifier = self.identifier_today
323                 elif future:
324                         identifier = self.identifier_future
325                 else:
326                         identifier = None
327                 
328                 # Reset title search depth
329                 identifier.search_depth = 0;
330                 
331                 if identifier:
332                         channels = lookupServiceAlternatives(service)
333
334                         seriespluginworker.Start(ThreadItem(identifier = identifier, callback = callback, name = name, begin = begin, end = end, service = service, channels = channels))
335                         '''
336                         try:
337                                 
338                                 
339                                 result = identifier.getEpisode(
340                                                                 name, begin, end, service, channels
341                                                         )
342                                 
343                                 if result and len(result) == 4:
344                                 
345                                         season, episode, title, series = result
346                                         season = int(CompiledRegexpNonDecimal.sub('', season))
347                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
348                                         #title = CompiledRegexpNonAlphanum.sub(' ', title)
349                                         title = title.strip()
350                                         splog("SeriesPlugin result callback")
351                                         callback( (season, episode, title, series) )
352                                         
353                                         config.plugins.seriesplugin.lookup_counter.value += 1
354                                         config.plugins.seriesplugin.lookup_counter.save()
355                                         
356                                 else:
357                                         splog("SeriesPlugin result failed")
358                                         callback( result )
359                                         
360                         except Exception as e:
361                                 splog("SeriesPlugin Callback Exception:", str(e))
362                                 callback( str(e) )
363                         '''
364                         
365 #TBD Because of E2 Update 05.2013
366                 #from threading import currentThread
367                 #if currentThread().getName() == 'MainThread':
368 #                       if (config.plugins.seriesplugin.lookup_counter.value == 10) \
369 #                               or (config.plugins.seriesplugin.lookup_counter.value == 100) \
370 #                               or (config.plugins.seriesplugin.lookup_counter.value % 1000 == 0):
371 #                               from plugin import ABOUT
372 #                               about = ABOUT.format( **{'lookups': config.plugins.seriesplugin.lookup_counter.value} )
373 #                               AddPopup(
374 #                                       about,
375 #                                       MessageBox.TYPE_INFO,
376 #                                       0,
377 #                                       'SP_PopUp_ID_About'
378 #                               )
379                         
380                         return identifier.getName()
381                         
382                 #if not available:
383                 else:
384                         callback( "No identifier available" )
385                         
386         def gotThreadMsg_seriespluginworker(self, msg):
387                 msg = seriespluginworker.Message.pop()
388                 print ('gotThreadMsg_seriespluginrenameservice]msg: %s' %str(msg))
389                 if msg[0] == 2:
390                         callback = msg[1]
391                         if callable(callback):
392                                 callback((msg[2],msg[3],msg[4],msg[5]))
393                 elif msg[0] == 1:
394                         callback = msg[1]
395                         if callable(callback):
396                                 callback(msg[2])
397                 self.renamerCallback()
398
399
400         def renamerCallback(self):
401                 if (config.plugins.seriesplugin.lookup_counter.value == 10) \
402                         or (config.plugins.seriesplugin.lookup_counter.value == 100) \
403                         or (config.plugins.seriesplugin.lookup_counter.value % 1000 == 0):
404                         from plugin import ABOUT
405                         about = ABOUT.format( **{'lookups': config.plugins.seriesplugin.lookup_counter.value} )
406                         AddPopup(
407                                 about,
408                                 MessageBox.TYPE_INFO,
409                                 0,
410                                 'SP_PopUp_ID_About'
411                         )
412
413         def cancel(self):
414                 seriespluginworker.MessagePump.recv_msg.get().remove(self.gotThreadMsg_seriespluginrenameservice) # interconnect to thread stop
415                 self.stop()