SeriesPlugin 1.2.8: Fixed multiple timer / movie handling
[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.__list = []
187
188         def __getMessagePump(self):
189                 return self.__messagePump
190         MessagePump = property(__getMessagePump)
191
192         def __getMessageQueue(self):
193                 return self.__messages
194         Message = property(__getMessageQueue)
195
196         def __getRunning(self):
197                 return self.__running
198         isRunning = property(__getRunning)
199
200         def Start(self, item):
201                 if not self.__running:
202                         self.__running = True
203                         splog("SP: Worker: Start")
204                         #self.__item = item
205                         self.__list = [item]
206                         self.start() # Start blocking code in Thread
207                 else:
208                         self.__list.append(item)
209         
210         def run(self):
211         
212                 while self.__list:
213                         item = self.__list.pop(0)
214                         splog('SP: Worker is processing: ', item.identifier)
215                         # do processing stuff here
216                         result = None
217                         
218                         try:
219                                 result = item.identifier.getEpisode(
220                                         item.name, item.begin, item.end, item.service, item.channels
221                                 )
222                         except Exception, e:
223                                 splog("SP: Worker: Exception:", str(e))
224                                 
225                                 # Exception finish job with error
226                                 result = str(e)
227                         
228                         try:
229                                 splog("SP: Worker: result")
230                                 if result and len(result) == 4:
231                                         season, episode, title, series = result
232                                         season = int(CompiledRegexpNonDecimal.sub('', season))
233                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
234                                         title = title.strip()
235                                         splog("SP: Worker: result callback")
236                                         self.__messages.push((2, item.callback, season, episode, title, series,))
237                                         self.__messagePump.send(0)
238                                 else:
239                                         splog("SP: Worker: result failed")
240                                         self.__messages.push((1, item.callback, result))
241                                         self.__messagePump.send(0)
242                         except Exception, e:
243                                 splog("SP: Worker: Callback Exception:", str(e))
244                         
245                         config.plugins.seriesplugin.lookup_counter.value += 1
246                         config.plugins.seriesplugin.lookup_counter.save()
247                         
248                 self.__messages.push((0, result,))
249                 self.__messagePump.send(0)
250                 self.__running = False
251                 Thread.__init__(self)
252                 splog('SP: Worker: list is emty, done')
253
254 seriespluginworker = SeriesPluginWorker()
255
256 class SeriesPlugin(Modules, ChannelsBase):
257
258         def __init__(self):
259                 splog("SP: Main: Init")
260                 Modules.__init__(self)
261                 ChannelsBase.__init__(self)
262                 
263                 self.serviceHandler = eServiceCenter.getInstance()
264                 self.__pump_recv_msg_conn = seriespluginworker.MessagePump.recv_msg.connect(self.gotThreadMsg_seriespluginworker)
265                 
266                 #http://bugs.python.org/issue7980
267                 datetime.strptime('2012-01-01', '%Y-%m-%d')
268                         
269                 self.identifier_elapsed = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_elapsed.value )
270                 #splog(self.identifier_elapsed)
271                 
272                 self.identifier_today = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_today.value )
273                 #splog(self.identifier_today)
274                 
275                 self.identifier_future = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_future.value )
276                 #splog(self.identifier_future)
277
278         def stop(self):
279                 splog("SP: Main: stop")
280                 if config.plugins.seriesplugin.lookup_counter.isChanged():
281                         config.plugins.seriesplugin.lookup_counter.save()
282                 self.saveXML()
283         
284         ################################################
285         # Identifier functions
286         def getIdentifier(self, future=False, today=False, elapsed=False):
287                 if elapsed:
288                         return self.identifier_elapsed and self.identifier_elapsed.getName()
289                 elif today:
290                         return self.identifier_today and self.identifier_today.getName()
291                 elif future:
292                         return self.identifier_future and self.identifier_future.getName()
293                 else:
294                         return None
295         
296         def getEpisode(self, callback, name, begin, end=None, service=None, future=False, today=False, elapsed=False):
297                 #available = False
298                 
299                 if config.plugins.seriesplugin.skip_during_records.value:
300                         try:
301                                 import NavigationInstance
302                                 if NavigationInstance.instance.RecordTimer.isRecording():
303                                         splog("SP: Main: Skip check during running records")
304                                         return
305                         except:
306                                 pass
307                 
308                 name = removeEpisodeInfo(name)
309                 #name = removeEpisodeInfo(name)
310                 if name.startswith("The ") or name.startswith("Der ") or name.startswith("Die ")or name.startswith("Das "):
311                         name = name[4:]
312                 
313                 begin = datetime.fromtimestamp(begin)
314                 splog("SP: Main: begin:", begin.strftime('%Y-%m-%d %H:%M:%S'))
315                 end = datetime.fromtimestamp(end)
316                 splog("SP: Main: end:", end.strftime('%Y-%m-%d %H:%M:%S'))
317                 
318                 if elapsed:
319                         identifier = self.identifier_elapsed
320                 elif today:
321                         identifier = self.identifier_today
322                 elif future:
323                         identifier = self.identifier_future
324                 else:
325                         identifier = None
326                 
327                 if identifier:
328                 
329                         # Reset title search depth on every new request
330                         identifier.search_depth = 0;
331                         
332                         # Reset the knownids on every new request
333                         identifier.knownids = []
334                         
335                         channels = lookupServiceAlternatives(service)
336
337                         seriespluginworker.Start(ThreadItem(identifier = identifier, callback = callback, name = name, begin = begin, end = end, service = service, channels = channels))
338                         
339                         '''
340                         try:
341                                 result = identifier.getEpisode(
342                                                                 name, begin, end, service, channels
343                                                         )
344                                 
345                                 if result and len(result) == 4:
346                                 
347                                         season, episode, title, series = result
348                                         season = int(CompiledRegexpNonDecimal.sub('', season))
349                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
350                                         #title = CompiledRegexpNonAlphanum.sub(' ', title)
351                                         title = title.strip()
352                                         splog("SeriesPlugin result callback")
353                                         callback( (season, episode, title, series) )
354                                         
355                                         config.plugins.seriesplugin.lookup_counter.value += 1
356                                         config.plugins.seriesplugin.lookup_counter.save()
357                                         
358                                 else:
359                                         splog("SeriesPlugin result failed")
360                                         callback( result )
361                                         
362                         except Exception as e:
363                                 splog("SeriesPlugin Callback Exception:", str(e))
364                                 callback( str(e) )
365                         '''
366                         
367                         return identifier.getName()
368                         
369                 #if not available:
370                 else:
371                         callback( "No identifier available" )
372                         
373         def gotThreadMsg_seriespluginworker(self, msg):
374                 msg = seriespluginworker.Message.pop()
375                 splog("SP: Main: gotThreadMsg:", msg)
376                 if msg[0] == 2:
377                         callback = msg[1]
378                         if callable(callback):
379                                 callback((msg[2],msg[3],msg[4],msg[5]))
380                 elif msg[0] == 1:
381                         callback = msg[1]
382                         if callable(callback):
383                                 callback(msg[2])
384
385                 if (config.plugins.seriesplugin.lookup_counter.value == 10) \
386                         or (config.plugins.seriesplugin.lookup_counter.value == 100) \
387                         or (config.plugins.seriesplugin.lookup_counter.value % 1000 == 0):
388                         from plugin import ABOUT
389                         about = ABOUT.format( **{'lookups': config.plugins.seriesplugin.lookup_counter.value} )
390                         AddPopup(
391                                 about,
392                                 MessageBox.TYPE_INFO,
393                                 0,
394                                 'SP_PopUp_ID_About'
395                         )
396
397         def cancel(self):
398                 splog("SP: Main: cancel")
399                 self.__pump_recv_msg_conn = None
400                 self.stop()