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