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