SeriesPlugin 2.4.3: Changed handling of special char encoding
[enigma2-plugins.git] / seriesplugin / src / plugin.py
1 # -*- coding: utf-8 -*-
2 import os, sys, traceback
3
4 # Localization
5 from . import _
6
7 from time import time
8
9 # GUI (Screens)
10 from Screens.MessageBox import MessageBox
11
12 # Config
13 from Components.config import config, ConfigSubsection, ConfigEnableDisable, ConfigNumber, ConfigSelection, ConfigYesNo, ConfigText, ConfigSelectionNumber
14
15 # Plugin
16 from Components.PluginComponent import plugins
17 from Plugins.Plugin import PluginDescriptor
18
19 # Plugin internal
20 from SeriesPluginTimer import SeriesPluginTimer
21 from SeriesPluginInfoScreen import SeriesPluginInfoScreen
22 from SeriesPluginRenamer import SeriesPluginRenamer
23 from SeriesPluginIndependent import startIndependent, runIndependent
24 from SeriesPluginConfiguration import SeriesPluginConfiguration
25 from Logger import splog
26
27
28 #######################################################
29 # Constants
30 NAME = "SeriesPlugin"
31 VERSION = "2.4.3"
32 DESCRIPTION = _("SeriesPlugin")
33 SHOWINFO = _("Show series info (SP)")
34 RENAMESERIES = _("Rename serie(s) (SP)")
35 CHECKTIMERS = _("Check timer list for series (SP)")
36 SUPPORT = "http://bit.ly/seriespluginihad"
37 DONATE = "http://bit.ly/seriespluginpaypal"
38 ABOUT = "\n  " + NAME + " " + VERSION + "\n\n" \
39                                 + _("  (C) 2012 by betonme @ IHAD \n\n") \
40                                 + _("  {lookups:d} successful lookups.\n") \
41                                 + _("  How much time have You saved?\n\n") \
42                                 + _("  Support: ") + SUPPORT + "\n" \
43                                 + _("  Feel free to donate. \n") \
44                                 + _("  PayPal: ") + DONATE
45 try:
46         from Tools.HardwareInfo import HardwareInfo
47         DEVICE = HardwareInfo().get_device_name().strip()
48         
49         # Get Box Info
50         #from Components.Network import iNetwork
51         #self.BoxID = iNetwork.getAdapterAttribute("eth0", "mac")
52         #self.DeviceName = HardwareInfo().get_device_name()
53         #from Components.About import about
54         #self.EnigmaVersion = about.getEnigmaVersionString()
55         #self.ImageVersion = about.getVersionString()
56 except:
57         DEVICE = ''
58
59 WHERE_EPGMENU     = 'WHERE_EPGMENU'
60 WHERE_CHANNELMENU = 'WHERE_CHANNELMENU'
61
62
63 #######################################################
64 # Initialize Configuration
65 config.plugins.seriesplugin = ConfigSubsection()
66
67 config.plugins.seriesplugin.enabled                   = ConfigEnableDisable(default = False)
68
69 config.plugins.seriesplugin.menu_info                 = ConfigYesNo(default = True)
70 config.plugins.seriesplugin.menu_extensions           = ConfigYesNo(default = False)
71 config.plugins.seriesplugin.menu_epg                  = ConfigYesNo(default = False)
72 config.plugins.seriesplugin.menu_channel              = ConfigYesNo(default = True)
73 config.plugins.seriesplugin.menu_movie_info           = ConfigYesNo(default = True)
74 config.plugins.seriesplugin.menu_movie_rename         = ConfigYesNo(default = True)
75
76 #TODO config.plugins.seriesplugin.open MessageBox or TheTVDB  ConfigSelection if hasTheTVDB
77
78 config.plugins.seriesplugin.identifier_elapsed        = ConfigText(default = "", fixed_size = False)
79 config.plugins.seriesplugin.identifier_today          = ConfigText(default = "", fixed_size = False)
80 config.plugins.seriesplugin.identifier_future         = ConfigText(default = "", fixed_size = False)
81
82 #config.plugins.seriesplugin.manager                   = ConfigSelection(choices = [("", "")], default = "")
83 #config.plugins.seriesplugin.guide                     = ConfigSelection(choices = [("", "")], default = "")
84
85 config.plugins.seriesplugin.pattern_file              = ConfigText(default = "/etc/enigma2/seriesplugin_patterns.json", fixed_size = False)
86 config.plugins.seriesplugin.pattern_title             = ConfigText(default = "{org:s} S{season:02d}E{episode:02d} {title:s}", fixed_size = False)
87 config.plugins.seriesplugin.pattern_description       = ConfigText(default = "S{season:02d}E{episode:02d} {title:s} {org:s}", fixed_size = False)
88 #config.plugins.seriesplugin.pattern_record            = ConfigText(default = "{org:s} S{season:02d}E{episode:02d} {title:s}", fixed_size = False)
89
90 config.plugins.seriesplugin.replace_chars             = ConfigText(default = ":\!/\\,\(\)'\?", fixed_size = False)
91
92 config.plugins.seriesplugin.channel_file              = ConfigText(default = "/etc/enigma2/seriesplugin_channels.xml", fixed_size = False)
93
94 config.plugins.seriesplugin.bouquet_main              = ConfigText(default = "", fixed_size = False)
95
96 config.plugins.seriesplugin.rename_file               = ConfigYesNo(default = True)
97 config.plugins.seriesplugin.rename_tidy               = ConfigYesNo(default = False)
98 config.plugins.seriesplugin.rename_legacy             = ConfigYesNo(default = False)
99 config.plugins.seriesplugin.rename_existing_files     = ConfigYesNo(default = False)
100 config.plugins.seriesplugin.rename_popups             = ConfigYesNo(default = True)
101 config.plugins.seriesplugin.rename_popups_success     = ConfigYesNo(default = False)
102 config.plugins.seriesplugin.rename_popups_timeout     = ConfigSelectionNumber(-1, 20, 1, default = 3)
103
104 config.plugins.seriesplugin.max_time_drift            = ConfigSelectionNumber(0, 600, 1, default = 15)
105 config.plugins.seriesplugin.search_depths             = ConfigSelectionNumber(0, 10, 1, default = 0)
106
107 config.plugins.seriesplugin.skip_during_records       = ConfigYesNo(default=False)
108 config.plugins.seriesplugin.skip_pattern_match        = ConfigYesNo(default=True)
109
110 config.plugins.seriesplugin.autotimer_independent     = ConfigYesNo(default = False)
111 config.plugins.seriesplugin.independent_cycle         = ConfigSelectionNumber(5, 24*60, 5, default = 60)
112 config.plugins.seriesplugin.independent_retry         = ConfigYesNo(default = False)
113
114 config.plugins.seriesplugin.check_timer_list          = ConfigYesNo(default = False)
115
116 config.plugins.seriesplugin.timer_popups              = ConfigYesNo(default = True)
117 config.plugins.seriesplugin.timer_popups_success      = ConfigYesNo(default = False)
118 config.plugins.seriesplugin.timer_popups_timeout     = ConfigSelectionNumber(-1, 20, 1, default = 3)
119
120 config.plugins.seriesplugin.caching                   = ConfigYesNo(default = True)
121
122 config.plugins.seriesplugin.debug_prints              = ConfigYesNo(default = False)
123 config.plugins.seriesplugin.write_log                 = ConfigYesNo(default = False)
124 config.plugins.seriesplugin.log_file                  = ConfigText(default = "/tmp/seriesplugin.log", fixed_size = False)
125 config.plugins.seriesplugin.log_reply_user            = ConfigText(default = "Dreambox User", fixed_size = False)
126 config.plugins.seriesplugin.log_reply_mail            = ConfigText(default = "myemail@home.com", fixed_size = False)
127
128 config.plugins.seriesplugin.ganalytics                = ConfigYesNo(default = True)
129
130 # Internal
131 config.plugins.seriesplugin.lookup_counter            = ConfigNumber(default = 0)
132 #config.plugins.seriesplugin.uid                       = ConfigText(default = str(time()), fixed_size = False)
133
134
135 #######################################################
136 # Start
137 def start(reason, **kwargs):
138         if config.plugins.seriesplugin.enabled.value:
139                 # Startup
140                 if reason == 0:
141                         # Start on demand if it is requested
142                         if config.plugins.seriesplugin.autotimer_independent.value:
143                                 startIndependent()
144                         
145                 # Shutdown
146                 elif reason == 1:
147                         from SeriesPlugin import resetInstance
148                         resetInstance()
149
150
151 #######################################################
152 # Plugin configuration
153 def setup(session, *args, **kwargs):
154         try:
155                 session.open(SeriesPluginConfiguration)
156         except Exception as e:
157                 splog(_("SeriesPlugin setup exception ") + str(e))
158                 #exc_type, exc_value, exc_traceback = sys.exc_info()
159                 #splog( exc_type, exc_value, exc_traceback )
160
161
162 #######################################################
163 # Event Info
164 def info(session, service=None, event=None, *args, **kwargs):
165         if config.plugins.seriesplugin.enabled.value:
166                 try:
167                         session.open(SeriesPluginInfoScreen, service, event)
168                 except Exception as e:
169                         splog(_("SeriesPlugin info exception ") + str(e))
170                         #exc_type, exc_value, exc_traceback = sys.exc_info()
171                         #splog( exc_type, exc_value, exc_traceback )
172
173
174 #######################################################
175 # Extensions menu
176 def sp_extension(session, *args, **kwargs):
177         if config.plugins.seriesplugin.enabled.value:
178                 try:
179                         if session:
180                                 session.open(SeriesPluginInfoScreen)
181                 except Exception as e:
182                         splog(_("SeriesPlugin extension exception ") + str(e))
183
184
185 #######################################################
186 # Channel menu
187 def channel(session, service=None, *args, **kwargs):
188         if config.plugins.seriesplugin.enabled.value:
189                 try:
190                         from enigma import eServiceCenter
191                         info = eServiceCenter.getInstance().info(service)
192                         event = info.getEvent(service)
193                         session.open(SeriesPluginInfoScreen, service, event)
194                 except Exception as e:
195                         splog(_("SeriesPlugin extension exception ") + str(e))
196
197
198 #######################################################
199 # Timer
200 def checkTimers(session, *args, **kwargs):
201         runIndependent()
202
203
204 #######################################################
205 # Movielist menu rename
206 def movielist_rename(session, service, services=None, *args, **kwargs):
207         if config.plugins.seriesplugin.enabled.value:
208                 try:
209                         if services:
210                                 if not isinstance(services, list):
211                                         services = [services]   
212                         else:
213                                 services = [service]
214                         SeriesPluginRenamer(session, services)
215                 except Exception as e:
216                         splog(_("SeriesPlugin renamer exception ") + str(e))
217
218
219 #######################################################
220 # Movielist menu info
221 def movielist_info(session, service, *args, **kwargs):
222         if config.plugins.seriesplugin.enabled.value:
223                 try:
224                         session.open(SeriesPluginInfoScreen, service)
225                 except Exception as e:
226                         splog(_("SeriesPlugin extension exception ") + str(e))
227
228
229 #######################################################
230 # Timer renaming
231
232 # Synchronous call, blocks until we have the information
233 def getSeasonAndEpisode(timer, name, begin, end, *args, **kwargs):
234         result = None
235         if config.plugins.seriesplugin.enabled.value:
236                 try:
237                         result = SeriesPluginTimer(timer, name, begin, end, True)
238                 except Exception as e:
239                         splog(_("SeriesPlugin label exception ") + str(e))
240         return result
241
242 # Call asynchronous
243 def renameTimer(timer, name, begin, end, *args, **kwargs):
244         if config.plugins.seriesplugin.enabled.value:
245                 try:
246                         SeriesPluginTimer(timer, name, begin, end)
247                 except Exception as e:
248                         splog(_("SeriesPlugin label exception ") + str(e))
249
250
251 # For compatibility reasons
252 def modifyTimer(timer, name, *args, **kwargs):
253         if config.plugins.seriesplugin.enabled.value:
254                 splog("SeriesPlugin modifyTimer is deprecated - Update Your AutoTimer!")
255                 try:
256                         SeriesPluginTimer(timer, name or timer.name, timer.begin, timer.end)
257                 except Exception as e:
258                         splog(_("SeriesPlugin label exception ") + str(e))
259
260
261 # For compatibility reasons
262 def labelTimer(timer, begin=None, end=None, *args, **kwargs):
263         if config.plugins.seriesplugin.enabled.value:
264                 splog("SeriesPlugin labelTimer is deprecated - Update Your AutoTimer!")
265                 try:
266                         SeriesPluginTimer(timer, timer.name, timer.begin, timer.end)
267                 except Exception as e:
268                         splog(_("SeriesPlugin label exception ") + str(e))
269
270
271 #######################################################
272 # Plugin main function
273 def Plugins(**kwargs):
274         descriptors = []
275         
276         #TODO icon
277         descriptors.append( PluginDescriptor(
278                                                                                         name = NAME + " " + _("Setup"),
279                                                                                         description = NAME + " " + _("Setup"),
280                                                                                         where = PluginDescriptor.WHERE_PLUGINMENU,
281                                                                                         fnc = setup,
282                                                                                         needsRestart = False) )
283         
284         if config.plugins.seriesplugin.enabled.value:
285                 
286                 overwriteAutoTimer()
287                 
288                 descriptors.append( PluginDescriptor(
289                                                                                                         #where = PluginDescriptor.WHERE_SESSIONSTART,
290                                                                                                         where = PluginDescriptor.WHERE_AUTOSTART,
291                                                                                                         needsRestart = False,
292                                                                                                         fnc = start) )
293
294                 if config.plugins.seriesplugin.menu_info.value:
295                         descriptors.append( PluginDescriptor(
296                                                                                                         name = SHOWINFO,
297                                                                                                         description = SHOWINFO,
298                                                                                                         where = PluginDescriptor.WHERE_EVENTINFO,
299                                                                                                         needsRestart = False,
300                                                                                                         fnc = info) )
301
302                 if config.plugins.seriesplugin.menu_extensions.value:
303                         descriptors.append(PluginDescriptor(
304                                                                                                         name = SHOWINFO,
305                                                                                                         description = SHOWINFO,
306                                                                                                         where = PluginDescriptor.WHERE_EXTENSIONSMENU,
307                                                                                                         fnc = sp_extension,
308                                                                                                         needsRestart = False) )
309                 
310                 if config.plugins.seriesplugin.check_timer_list.value:
311                         descriptors.append(PluginDescriptor(
312                                                                                                         name = CHECKTIMERS,
313                                                                                                         description = CHECKTIMERS,
314                                                                                                         where = PluginDescriptor.WHERE_EXTENSIONSMENU,
315                                                                                                         fnc = checkTimers,
316                                                                                                         needsRestart = False) )
317                 
318                 if config.plugins.seriesplugin.menu_movie_info.value:
319                         descriptors.append( PluginDescriptor(
320                                                                                                         name = SHOWINFO,
321                                                                                                         description = SHOWINFO,
322                                                                                                         where = PluginDescriptor.WHERE_MOVIELIST,
323                                                                                                         fnc = movielist_info,
324                                                                                                         needsRestart = False) )
325                 
326                 if config.plugins.seriesplugin.menu_movie_rename.value:
327                         descriptors.append( PluginDescriptor(
328                                                                                                         name = RENAMESERIES,
329                                                                                                         description = RENAMESERIES,
330                                                                                                         where = PluginDescriptor.WHERE_MOVIELIST,
331                                                                                                         fnc = movielist_rename,
332                                                                                                         needsRestart = False) )
333                 
334                 if config.plugins.seriesplugin.menu_channel.value:
335                         try:
336                                 descriptors.append( PluginDescriptor(
337                                                                                                         name = SHOWINFO,
338                                                                                                         description = SHOWINFO,
339                                                                                                         where = PluginDescriptor.WHERE_CHANNEL_CONTEXT_MENU,
340                                                                                                         fnc = channel,
341                                                                                                         needsRestart = False) )
342                         except:
343                                 addSeriesPlugin(WHERE_CHANNELMENU, SHOWINFO)
344                 
345                 if config.plugins.seriesplugin.menu_epg.value:
346                         addSeriesPlugin(WHERE_EPGMENU, SHOWINFO)
347
348         return descriptors
349
350
351 #######################################################
352 # Override EPGSelection enterDateTime
353 EPGSelection_enterDateTime = None
354 #EPGSelection_openOutdatedEPGSelection = None
355 def SPEPGSelectionInit():
356         print "SeriesPlugin override EPGSelection"
357         global EPGSelection_enterDateTime #, EPGSelection_openOutdatedEPGSelection
358         if EPGSelection_enterDateTime is None: # and EPGSelection_openOutdatedEPGSelection is None:
359                 from Screens.EpgSelection import EPGSelection
360                 EPGSelection_enterDateTime = EPGSelection.enterDateTime
361                 EPGSelection.enterDateTime = enterDateTime
362                 #EPGSelection_openOutdatedEPGSelection = EPGSelection.openOutdatedEPGSelection
363                 #EPGSelection.openOutdatedEPGSelection = openOutdatedEPGSelection
364                 EPGSelection.SPcloseafterfinish = closeafterfinish
365
366 def SPEPGSelectionUndo():
367         print "SeriesPlugin undo override EPGSelection"
368         global EPGSelection_enterDateTime #, EPGSelection_openOutdatedEPGSelection
369         if EPGSelection_enterDateTime: # and EPGSelection_openOutdatedEPGSelection:
370                 from Screens.EpgSelection import EPGSelection
371                 EPGSelection.enterDateTime = EPGSelection_enterDateTime
372                 EPGSelection_enterDateTime = None
373                 #EPGSelection.openOutdatedEPGSelection = EPGSelection_openOutdatedEPGSelection
374                 #EPGSelection_openOutdatedEPGSelection = None
375
376 def enterDateTime(self):
377         from Screens.EpgSelection import EPG_TYPE_SINGLE,EPG_TYPE_MULTI,EPG_TYPE_SIMILAR
378         event = self["Event"].event
379         if self.type == EPG_TYPE_SINGLE:
380                 service = self.currentService
381         elif self.type == EPG_TYPE_MULTI:       
382                 service = self.services
383         elif self.type == EPG_TYPE_SIMILAR:
384                 service = self.currentService
385         if service and event:
386                 self.session.openWithCallback(self.SPcloseafterfinish, SeriesPluginInfoScreen, service, event) 
387                 return
388         EPGSelection_enterDateTime(self)
389
390 #def openOutdatedEPGSelection(self, reason=None):
391 #       if reason == 1:
392 #               EPGSelection_enterDateTime(self)
393
394
395 #######################################################
396 # Override ChannelContextMenu
397 ChannelContextMenu__init__ = None
398 def SPChannelContextMenuInit():
399         print "[SeriesPlugin] override ChannelContextMenu.__init__"
400         global ChannelContextMenu__init__
401         if ChannelContextMenu__init__ is None:
402                 from Screens.ChannelSelection import ChannelContextMenu
403                 ChannelContextMenu__init__ = ChannelContextMenu.__init__
404                 ChannelContextMenu.__init__ = SPChannelContextMenu__init__
405                 ChannelContextMenu.SPchannelShowSeriesInfo = channelShowSeriesInfo
406                 ChannelContextMenu.SPcloseafterfinish = closeafterfinish
407
408 def SPChannelContextMenuUndo():
409         print "[SeriesPlugin] override ChannelContextMenu.__init__"
410         global ChannelContextMenu__init__
411         if ChannelContextMenu__init__:
412                 from Screens.ChannelSelection import ChannelContextMenu
413                 ChannelContextMenu.__init__ = ChannelContextMenu__init__
414                 ChannelContextMenu__init__ = None
415
416 def SPChannelContextMenu__init__(self, session, csel):
417         from Components.ChoiceList import ChoiceEntryComponent
418         from Screens.ChannelSelection import MODE_TV
419         from Tools.BoundFunction import boundFunction
420         from enigma import eServiceReference
421         ChannelContextMenu__init__(self, session, csel)
422         current = csel.getCurrentSelection()
423         current_sel_path = current.getPath()
424         current_sel_flags = current.flags
425         if csel.mode == MODE_TV and not (current_sel_path or current_sel_flags & (eServiceReference.isDirectory|eServiceReference.isMarker)):
426                 self["menu"].list.insert(0, ChoiceEntryComponent(text=(SHOWINFO, boundFunction(self.SPchannelShowSeriesInfo))))
427
428 def channelShowSeriesInfo(self):
429         splog( "[SeriesPlugin] channelShowSeriesInfo ")
430         if config.plugins.seriesplugin.enabled.value:
431                 try:
432                         from enigma import eServiceCenter
433                         service = self.csel.servicelist.getCurrent()
434                         info = eServiceCenter.getInstance().info(service)
435                         event = info.getEvent(service)
436                         self.session.openWithCallback(self.SPcloseafterfinish, SeriesPluginInfoScreen, service, event)
437                 except Exception as e:
438                         splog(_("SeriesPlugin info exception ") + str(e))
439
440 def closeafterfinish(self, retval=None):
441         self.close()
442
443
444 #######################################################
445 # Add / Remove menu functions
446 def addSeriesPlugin(menu, title, fnc=None):
447         # Add to menu
448         if( menu == WHERE_EPGMENU ):
449                 SPEPGSelectionInit()
450         elif( menu == WHERE_CHANNELMENU ):
451                 try:
452                         addSeriesPlugin(PluginDescriptor.WHERE_CHANNEL_CONTEXT_MENU, SHOWINFO, fnc)
453                 except:
454                         SPChannelContextMenuInit()
455         else:
456                 from Components.PluginComponent import plugins
457                 if plugins:
458                         for p in plugins.getPlugins( where = menu ):
459                                 if p.name == title:
460                                         # Plugin is already in menu
461                                         break
462                         else:
463                                 # Plugin not in menu - add it
464                                 plugin = PluginDescriptor(
465                                                                                                                                 name = title,
466                                                                                                                                 description = title,
467                                                                                                                                 where = menu,
468                                                                                                                                 needsRestart = False,
469                                                                                                                                 fnc = fnc)
470                                 if menu in plugins.plugins:
471                                         plugins.plugins[ menu ].append(plugin)
472
473
474 def removeSeriesPlugin(menu, title):
475         # Remove from menu
476         if( menu == WHERE_EPGMENU ):
477                 SPEPGSelectionUndo()
478         elif( menu == WHERE_CHANNELMENU ):
479                 try:
480                         removeSeriesPlugin(PluginDescriptor.WHERE_CHANNEL_CONTEXT_MENU, SHOWINFO)
481                 except:
482                         SPChannelContextMenuUndo()
483         else:
484                 from Components.PluginComponent import plugins
485                 if plugins:
486                         for p in plugins.getPlugins( where = menu ):
487                                 if p.name == title:
488                                         plugins.plugins[ menu ].remove(p)
489                                         break
490
491
492 #######################################################
493 # Overwrite AutoTimer support functions
494
495 try:
496         from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer
497         #from Plugins.Extensions.AutoTimer.plugin import autotimer as AutoTimer
498 except:
499         AutoTimer = None
500
501 ATmodifyTimer = None
502
503
504 def overwriteAutoTimer():
505         try:
506                 global ATmodifyTimer
507                 if AutoTimer:
508                         if ATmodifyTimer is None:
509                                 # Backup original function
510                                 ATmodifyTimer = AutoTimer.modifyTimer
511                                 # Overwrite function
512                                 AutoTimer.modifyTimer = SPmodifyTimer
513         except:
514                 splog("SeriesPlugin found old AutoTimer")
515
516
517 def recoverAutoTimer():
518         try:
519                 global ATmodifyTimer
520                 if AutoTimer:
521                         if ATmodifyTimer:
522                                 AutoTimer.modifyTimer = ATmodifyTimer
523                                 ATmodifyTimer = None
524         except:
525                 splog("SeriesPlugin found old AutoTimer")
526
527
528 #######################################################
529 # Customized support functions
530
531 from difflib import SequenceMatcher
532 from ServiceReference import ServiceReference
533
534 def SPmodifyTimer(self, timer, name, shortdesc, begin, end, serviceref, eit=None):
535         # Never overwrite existing names, You will lose Your series informations
536         #timer.name = name
537         # Only overwrite non existing descriptions
538         timer.description = timer.description or shortdesc
539         timer.begin = int(begin)
540         timer.end = int(end)
541         timer.service_ref = ServiceReference(serviceref)
542         if eit:
543                 timer.eit = eit