SeriesPlugin 1.3.2: Fixed missing spaces in filename
[enigma2-plugins.git] / seriesplugin / src / SeriesPlugin.py
1 # -*- coding: utf-8 -*-
2 # by betonme @2012
3
4 import re
5 import os, sys, traceback
6 from time import localtime, strftime
7 from datetime import datetime
8
9 # Localization
10 from . import _
11
12 from datetime import datetime
13
14 from Components.config import config
15
16 from enigma import eServiceReference, iServiceInformation, eServiceCenter, ePythonMessagePump
17 from ServiceReference import ServiceReference
18
19 # Plugin framework
20 from Modules import Modules
21
22 # Tools
23 from Tools.BoundFunction import boundFunction
24 from Tools.Directories import resolveFilename, SCOPE_PLUGINS
25 from Tools.Notifications import AddPopup
26 from Screens.MessageBox import MessageBox
27
28 # Plugin internal
29 from IdentifierBase import IdentifierBase
30 from ManagerBase import ManagerBase
31 from GuideBase import GuideBase
32 from Channels import ChannelsBase, removeEpisodeInfo, lookupServiceAlternatives
33 from Logger import splog
34 from CancelableThread import QueueWithTimeOut, CancelableThread, synchronized, myLock
35
36 from ThreadQueue import ThreadQueue
37 from threading import Thread
38 from enigma import ePythonMessagePump
39
40
41 try:
42         if(config.plugins.autotimer.timeout.value == 1):
43                 config.plugins.autotimer.timeout.value = 5
44                 config.plugins.autotimer.save()
45 except Exception as e:
46         pass
47
48
49 # Constants
50 AUTOTIMER_PATH  = os.path.join( resolveFilename(SCOPE_PLUGINS), "Extensions/AutoTimer/" )
51 SERIESPLUGIN_PATH  = os.path.join( resolveFilename(SCOPE_PLUGINS), "Extensions/SeriesPlugin/" )
52
53
54 # Globals
55 instance = None
56
57 CompiledRegexpNonDecimal = re.compile(r'[^\d]+')
58 CompiledRegexpNonAlphanum = re.compile(r'[^-_\.()abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ]+')
59
60 def dump(obj):
61         for attr in dir(obj):
62                 splog( "SP: %s = %s" % (attr, getattr(obj, attr)) )
63
64
65 def getInstance():
66         global instance
67         
68         if instance is None:
69                 
70                 from plugin import VERSION
71                 
72                 splog("SP: SERIESPLUGIN NEW INSTANCE " + VERSION)
73                 
74                 try:
75
76
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
136 def refactorTitle(org, data):
137         if data:
138                 season, episode, title, series = data
139                 if config.plugins.seriesplugin.pattern_title.value and not config.plugins.seriesplugin.pattern_title.value == "Off":
140                         splog("SP: refactor org", org)
141                         org = CompiledRegexpNonAlphanum.sub('', org)
142                         splog("SP: refactor org", org)
143                         splog("SP: refactor title", title)
144                         title = CompiledRegexpNonAlphanum.sub('', title)
145                         splog("SP: refactor title", title)
146                         splog("SP: refactor series", series)
147                         series = CompiledRegexpNonAlphanum.sub('', 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 self.__list and True
203
204         def Start(self, item):
205                 if not self.__running:
206                         self.__running = True
207                         splog("SP: Worker: Start")
208                         #self.__item = item
209                         self.__list = [item]
210                         self.start() # Start blocking code in Thread
211                 else:
212                         self.__list.append(item)
213         
214         def run(self):
215         
216                 while self.__list:
217                         item = self.__list.pop(0)
218                         splog('SP: Worker is processing: ', item.identifier)
219                         # do processing stuff here
220                         result = None
221                         
222                         try:
223                                 result = item.identifier.getEpisode(
224                                         item.name, item.begin, item.end, item.service, item.channels
225                                 )
226                         except Exception, e:
227                                 splog("SP: Worker: Exception:", str(e))
228                                 
229                                 # Exception finish job with error
230                                 result = str(e)
231                         
232                         try:
233                                 splog("SP: Worker: result")
234                                 if result and len(result) == 4:
235                                         season, episode, title, series = result
236                                         season = int(CompiledRegexpNonDecimal.sub('', season))
237                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
238                                         title = title.strip()
239                                         splog("SP: Worker: result callback")
240                                         self.__messages.push((2, item.callback, season, episode, title, series,))
241                                         self.__messagePump.send(0)
242                                 else:
243                                         splog("SP: Worker: result failed")
244                                         self.__messages.push((1, item.callback, result))
245                                         self.__messagePump.send(0)
246                         except Exception, e:
247                                 splog("SP: Worker: Callback Exception:", str(e))
248                         
249                         config.plugins.seriesplugin.lookup_counter.value += 1
250                         config.plugins.seriesplugin.lookup_counter.save()
251                         
252                 self.__messages.push((0, result,))
253                 self.__messagePump.send(0)
254                 self.__running = False
255                 Thread.__init__(self)
256                 splog('SP: Worker: list is emty, done')
257
258 seriespluginworker = SeriesPluginWorker()
259
260 class SeriesPlugin(Modules, ChannelsBase):
261
262         def __init__(self):
263                 splog("SP: Main: Init")
264                 Modules.__init__(self)
265                 ChannelsBase.__init__(self)
266                 
267                 self.serviceHandler = eServiceCenter.getInstance()
268                 self.__pump_recv_msg_conn = seriespluginworker.MessagePump.recv_msg.connect(self.gotThreadMsg_seriespluginworker)
269                 
270                 #http://bugs.python.org/issue7980
271                 datetime.strptime('2012-01-01', '%Y-%m-%d')
272                         
273                 self.identifier_elapsed = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_elapsed.value )
274                 #splog(self.identifier_elapsed)
275                 
276                 self.identifier_today = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_today.value )
277                 #splog(self.identifier_today)
278                 
279                 self.identifier_future = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_future.value )
280                 #splog(self.identifier_future)
281
282         def stop(self):
283                 splog("SP: Main: stop")
284                 if config.plugins.seriesplugin.lookup_counter.isChanged():
285                         config.plugins.seriesplugin.lookup_counter.save()
286                 self.saveXML()
287         
288         ################################################
289         # Identifier functions
290         def getIdentifier(self, future=False, today=False, elapsed=False):
291                 if elapsed:
292                         return self.identifier_elapsed and self.identifier_elapsed.getName()
293                 elif today:
294                         return self.identifier_today and self.identifier_today.getName()
295                 elif future:
296                         return self.identifier_future and self.identifier_future.getName()
297                 else:
298                         return None
299         
300         def getEpisode(self, callback, name, begin, end=None, service=None, future=False, today=False, elapsed=False):
301                 #available = False
302                 
303                 if config.plugins.seriesplugin.skip_during_records.value:
304                         try:
305                                 import NavigationInstance
306                                 if NavigationInstance.instance.RecordTimer.isRecording():
307                                         splog("SP: Main: Skip check during running records")
308                                         return
309                         except:
310                                 pass
311                 
312                 name = removeEpisodeInfo(name)
313                 #name = removeEpisodeInfo(name)
314                 if name.startswith("The ") or name.startswith("Der ") or name.startswith("Die ")or name.startswith("Das "):
315                         name = name[4:]
316                 
317                 begin = datetime.fromtimestamp(begin)
318                 splog("SP: Main: begin:", begin.strftime('%Y-%m-%d %H:%M:%S'))
319                 end = datetime.fromtimestamp(end)
320                 splog("SP: Main: end:", end.strftime('%Y-%m-%d %H:%M:%S'))
321                 
322                 if elapsed:
323                         identifier = self.identifier_elapsed
324                 elif today:
325                         identifier = self.identifier_today
326                 elif future:
327                         identifier = self.identifier_future
328                 else:
329                         identifier = None
330                 
331                 if identifier:
332                 
333                         # Reset title search depth on every new request
334                         identifier.search_depth = 0;
335                         
336                         # Reset the knownids on every new request
337                         identifier.knownids = []
338                         
339                         channels = lookupServiceAlternatives(service)
340
341                         seriespluginworker.Start(ThreadItem(identifier = identifier, callback = callback, name = name, begin = begin, end = end, service = service, channels = channels))
342                         
343                         '''
344                         try:
345                                 result = identifier.getEpisode(
346                                                                 name, begin, end, service, channels
347                                                         )
348                                 
349                                 if result and len(result) == 4:
350                                 
351                                         season, episode, title, series = result
352                                         season = int(CompiledRegexpNonDecimal.sub('', season))
353                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
354                                         #title = CompiledRegexpNonAlphanum.sub(' ', title)
355                                         title = title.strip()
356                                         splog("SeriesPlugin result callback")
357                                         callback( (season, episode, title, series) )
358                                         
359                                         config.plugins.seriesplugin.lookup_counter.value += 1
360                                         config.plugins.seriesplugin.lookup_counter.save()
361                                         
362                                 else:
363                                         splog("SeriesPlugin result failed")
364                                         callback( result )
365                                         
366                         except Exception as e:
367                                 splog("SeriesPlugin Callback Exception:", str(e))
368                                 callback( str(e) )
369                         '''
370                         
371                         return identifier.getName()
372                         
373                 #if not available:
374                 else:
375                         callback( "No identifier available" )
376                         
377         def gotThreadMsg_seriespluginworker(self, msg):
378                 msg = seriespluginworker.Message.pop()
379                 splog("SP: Main: gotThreadMsg:", msg)
380                 if msg[0] == 2:
381                         callback = msg[1]
382                         if callable(callback):
383                                 callback((msg[2],msg[3],msg[4],msg[5]))
384                 elif msg[0] == 1:
385                         callback = msg[1]
386                         if callable(callback):
387                                 callback(msg[2])
388
389                 if (config.plugins.seriesplugin.lookup_counter.value == 10) \
390                         or (config.plugins.seriesplugin.lookup_counter.value == 100) \
391                         or (config.plugins.seriesplugin.lookup_counter.value % 1000 == 0):
392                         from plugin import ABOUT
393                         about = ABOUT.format( **{'lookups': config.plugins.seriesplugin.lookup_counter.value} )
394                         AddPopup(
395                                 about,
396                                 MessageBox.TYPE_INFO,
397                                 0,
398                                 'SP_PopUp_ID_About'
399                         )
400
401         def cancel(self):
402                 splog("SP: Main: cancel")
403                 self.__pump_recv_msg_conn = None
404                 self.stop()