SeriesPlugin 1.2.5: Remove special chars during title renaming
[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( "config.plugins.seriesplugin.%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("SERIESPLUGIN NEW INSTANCE " + VERSION)
72                 
73                 try:
74
75
76                         from Tools.HardwareInfo import HardwareInfo
77                         splog( "DeviceName " + HardwareInfo().get_device_name().strip() )
78                 except:
79                         pass
80                 
81                 try:
82                         from Components.About import about
83                         splog( "EnigmaVersion " + about.getEnigmaVersionString().strip() )
84                         splog( "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( "dreamboxmodel " + open("/proc/stb/info/model").readline().strip() )
91                         splog( "imageversion " + open("/etc/image-version").readline().strip() )
92                         splog( "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( "config.plugins.seriesplugin.%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( 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( 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( 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("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("SeriesPluginRenamer refactorTitle")
140                         splog("SeriesPluginRenamer refactorTitle org", org)
141                         org = CompiledRegexpNonAlphanum.sub('', org)
142                         splog("SeriesPluginRenamer refactorTitle org", org)
143                         splog("SeriesPluginRenamer refactorTitle title", title)
144                         title = CompiledRegexpNonAlphanum.sub('', title)
145                         splog("SeriesPluginRenamer refactorTitle title", title)
146                         splog("SeriesPluginRenamer refactorTitle series", series)
147                         series = CompiledRegexpNonAlphanum.sub('', series)
148                         splog("SeriesPluginRenamer refactorTitle 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.__beginn = None
188                 self.__end = None
189
190         def __getMessagePump(self):
191                 return self.__messagePump
192         MessagePump = property(__getMessagePump)
193
194         def __getMessageQueue(self):
195                 return self.__messages
196         Message = property(__getMessageQueue)
197
198         def __getRunning(self):
199                 return self.__running
200         isRunning = property(__getRunning)
201
202         def Start(self, item):
203                 if not self.__running:
204                         self.__running = True
205                         splog("SeriesPluginRenamer")
206                         #self.__item = item
207                         self.__list = [item]
208                         self.start() # Start blocking code in Thread
209                 else:
210                         self.__list.append(item)
211         
212         def run(self):
213         
214                 while self.__list:
215                         item = self.__list.pop(0)
216                         splog('SeriesPluginWorker is processing: ', item.identifier)
217                         # do processing stuff here
218                         result = None
219                         
220                         try:
221                                 result = item.identifier.getEpisode(
222                                         item.name, item.begin, item.end, item.service, item.channels
223                                 )
224                         except Exception, e:
225                                 splog("SeriesPluginWorker Identifier Exception:", str(e))
226                                 
227                                 # Exception finish job with error
228                                 result = str(e)
229                         
230                         try:
231                                 splog("SeriesPluginWorker result")
232                                 if result and len(result) == 4:
233                                         season, episode, title, series = result
234                                         season = int(CompiledRegexpNonDecimal.sub('', season))
235                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
236                                         title = title.strip()
237                                         splog("SeriesPluginWorker result callback")
238                                         self.__messages.push((2, item.callback, season, episode, title, series,))
239                                         self.__messagePump.send(0)
240                                 else:
241                                         splog("SeriesPluginWorker result failed")
242                                         self.__messages.push((1, item.callback, result))
243                                         self.__messagePump.send(0)
244                         except Exception, e:
245                                 splog("SeriesPluginWorker Callback Exception:", str(e))
246                         
247                         config.plugins.seriesplugin.lookup_counter.value += 1
248                         config.plugins.seriesplugin.lookup_counter.save()
249                         
250                 self.__messages.push((0, result,))
251                 self.__messagePump.send(0)
252                 self.__running = False
253                 Thread.__init__(self)
254                 splog('SeriesPluginWorker] list is emty, done')
255
256 #TBD Because of E2 Update 05.2013
257 #                       if (config.plugins.seriesplugin.lookup_counter.value == 10) \
258 #                               or (config.plugins.seriesplugin.lookup_counter.value == 100) \
259 #                               or (config.plugins.seriesplugin.lookup_counter.value % 1000 == 0):
260 #                               from plugin import ABOUT
261 #                               about = ABOUT.format( **{'lookups': config.plugins.seriesplugin.lookup_counter.value} )
262 #                               AddPopup(
263 #                                       about,
264 #                                       MessageBox.TYPE_INFO,
265 #                                       0,
266 #                                       'SP_PopUp_ID_About'
267 #                               )
268
269 seriespluginworker = SeriesPluginWorker()
270
271 class SeriesPlugin(Modules, ChannelsBase):
272
273         def __init__(self):
274                 splog("SeriesPlugin")
275                 Modules.__init__(self)
276                 ChannelsBase.__init__(self)
277                 
278                 self.serviceHandler = eServiceCenter.getInstance()
279                 self.__pump_recv_msg_conn = seriespluginworker.MessagePump.recv_msg.connect(self.gotThreadMsg_seriespluginworker)
280                 
281                 #http://bugs.python.org/issue7980
282                 datetime.strptime('2012-01-01', '%Y-%m-%d')
283                         
284                 self.identifier_elapsed = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_elapsed.value )
285                 splog(self.identifier_elapsed)
286                 
287                 self.identifier_today = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_today.value )
288                 splog(self.identifier_today)
289                 
290                 self.identifier_future = self.instantiateModuleWithName( config.plugins.seriesplugin.identifier_future.value )
291                 splog(self.identifier_future)
292
293         def stop(self):
294                 splog("SeriesPlugin stop")
295                 if config.plugins.seriesplugin.lookup_counter.isChanged():
296                         config.plugins.seriesplugin.lookup_counter.save()
297                 self.saveXML()
298         
299         ################################################
300         # Identifier functions
301         def getIdentifier(self, future=False, today=False, elapsed=False):
302                 if elapsed:
303                         return self.identifier_elapsed and self.identifier_elapsed.getName()
304                 elif today:
305                         return self.identifier_today and self.identifier_today.getName()
306                 elif future:
307                         return self.identifier_future and self.identifier_future.getName()
308                 else:
309                         return None
310         
311         def getEpisode(self, callback, name, begin, end=None, service=None, future=False, today=False, elapsed=False):
312                 #available = False
313                 
314                 if config.plugins.seriesplugin.skip_during_records.value:
315                         try:
316                                 import NavigationInstance
317                                 if NavigationInstance.instance.RecordTimer.isRecording():
318                                         print("[SeriesPlugin]: Skip check during running records")
319                                         return
320                         except:
321                                 pass
322                 
323                 name = removeEpisodeInfo(name)
324                 #name = removeEpisodeInfo(name)
325                 if name.startswith("The ") or name.startswith("Der ") or name.startswith("Die ")or name.startswith("Das "):
326                         name = name[4:]
327                 
328                 begin = datetime.fromtimestamp(begin)
329                 end = datetime.fromtimestamp(end)
330                 
331                 if elapsed:
332                         identifier = self.identifier_elapsed
333                 elif today:
334                         identifier = self.identifier_today
335                 elif future:
336                         identifier = self.identifier_future
337                 else:
338                         identifier = None
339                 
340                 if identifier:
341                 
342                         # Reset title search depth on every new request
343                         identifier.search_depth = 0;
344                         
345                         # Reset the knownids on every new request
346                         identifier.knownids = []
347                         
348                         channels = lookupServiceAlternatives(service)
349
350                         seriespluginworker.Start(ThreadItem(identifier = identifier, callback = callback, name = name, begin = begin, end = end, service = service, channels = channels))
351                         
352                         '''
353                         try:
354                                 result = identifier.getEpisode(
355                                                                 name, begin, end, service, channels
356                                                         )
357                                 
358                                 if result and len(result) == 4:
359                                 
360                                         season, episode, title, series = result
361                                         season = int(CompiledRegexpNonDecimal.sub('', season))
362                                         episode = int(CompiledRegexpNonDecimal.sub('', episode))
363                                         #title = CompiledRegexpNonAlphanum.sub(' ', title)
364                                         title = title.strip()
365                                         splog("SeriesPlugin result callback")
366                                         callback( (season, episode, title, series) )
367                                         
368                                         config.plugins.seriesplugin.lookup_counter.value += 1
369                                         config.plugins.seriesplugin.lookup_counter.save()
370                                         
371                                 else:
372                                         splog("SeriesPlugin result failed")
373                                         callback( result )
374                                         
375                         except Exception as e:
376                                 splog("SeriesPlugin Callback Exception:", str(e))
377                                 callback( str(e) )
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                 self.__pump_recv_msg_conn = None
415                 self.stop()