SeriesPlugin 1.3.4: Fixed crash on deactivation of EPG integration
[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 = "1.3.4"
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.title_replace_chars       = ConfigYesNo(default = True)
91
92 config.plugins.seriesplugin.channel_file              = ConfigText(default = "/etc/enigma2/seriesplugin_channels.xml", fixed_size = False)
93 config.plugins.seriesplugin.channel_popups            = ConfigYesNo(default = False)
94
95 config.plugins.seriesplugin.rename_file               = ConfigYesNo(default = True)
96 config.plugins.seriesplugin.rename_tidy               = ConfigYesNo(default = False)
97 config.plugins.seriesplugin.rename_legacy             = ConfigYesNo(default = False)
98 config.plugins.seriesplugin.rename_existing_files     = ConfigYesNo(default = False)
99 config.plugins.seriesplugin.rename_popups             = ConfigYesNo(default = True)
100 config.plugins.seriesplugin.rename_popups_success     = ConfigYesNo(default = False)
101
102 config.plugins.seriesplugin.max_time_drift            = ConfigSelectionNumber(0, 600, 1, default = 15)
103 config.plugins.seriesplugin.search_depths             = ConfigSelectionNumber(0, 10, 1, default = 0)
104
105 config.plugins.seriesplugin.skip_during_records       = ConfigYesNo(default=False)
106
107 config.plugins.seriesplugin.autotimer_independent     = ConfigYesNo(default = False)
108 config.plugins.seriesplugin.independent_cycle         = ConfigSelectionNumber(5, 24*60, 5, default = 60)
109 config.plugins.seriesplugin.independent_retry         = ConfigYesNo(default = False)
110
111 config.plugins.seriesplugin.check_timer_list          = ConfigYesNo(default = False)
112
113 config.plugins.seriesplugin.timer_popups              = ConfigYesNo(default = True)
114 config.plugins.seriesplugin.timer_popups_success      = ConfigYesNo(default = False)
115
116 config.plugins.seriesplugin.caching                   = ConfigYesNo(default = True)
117
118 #config.plugins.seriesplugin.debug                     = ConfigYesNo(default = False)
119 config.plugins.seriesplugin.write_log                 = ConfigYesNo(default = False)
120 config.plugins.seriesplugin.log_file                  = ConfigText(default = "/tmp/seriesplugin.log", fixed_size = False)
121 config.plugins.seriesplugin.log_reply_user            = ConfigText(default = "Dreambox User", fixed_size = False)
122 config.plugins.seriesplugin.log_reply_mail            = ConfigText(default = "myemail@home.com", fixed_size = False)
123
124 config.plugins.seriesplugin.ganalytics                = ConfigYesNo(default = True)
125
126 # Internal
127 config.plugins.seriesplugin.lookup_counter            = ConfigNumber(default = 0)
128 #config.plugins.seriesplugin.uid                       = ConfigText(default = str(time()), fixed_size = False)
129
130
131 #######################################################
132 # Start
133 def start(reason, **kwargs):
134         if config.plugins.seriesplugin.enabled.value:
135                 # Startup
136                 if reason == 0:
137                         # Start on demand if it is requested
138                         if config.plugins.seriesplugin.autotimer_independent.value:
139                                 startIndependent()
140                         
141                 # Shutdown
142                 elif reason == 1:
143                         from SeriesPlugin import resetInstance
144                         resetInstance()
145
146
147 #######################################################
148 # Plugin configuration
149 def setup(session, *args, **kwargs):
150         try:
151                 session.open(SeriesPluginConfiguration)
152         except Exception as e:
153                 splog(_("SeriesPlugin setup exception ") + str(e))
154                 #exc_type, exc_value, exc_traceback = sys.exc_info()
155                 #splog( exc_type, exc_value, exc_traceback )
156
157
158 #######################################################
159 # Event Info
160 def info(session, service=None, event=None, *args, **kwargs):
161         if config.plugins.seriesplugin.enabled.value:
162                 try:
163                         session.open(SeriesPluginInfoScreen, service, event)
164                 except Exception as e:
165                         splog(_("SeriesPlugin info exception ") + str(e))
166                         #exc_type, exc_value, exc_traceback = sys.exc_info()
167                         #splog( exc_type, exc_value, exc_traceback )
168
169
170 #######################################################
171 # Extensions menu
172 def sp_extension(session, *args, **kwargs):
173         if config.plugins.seriesplugin.enabled.value:
174                 try:
175                         if session:
176                                 session.open(SeriesPluginInfoScreen)
177                 except Exception as e:
178                         splog(_("SeriesPlugin extension exception ") + str(e))
179
180
181 #######################################################
182 # Channel menu
183 def channel(session, service=None, *args, **kwargs):
184         if config.plugins.seriesplugin.enabled.value:
185                 try:
186                         from enigma import eServiceCenter
187                         info = eServiceCenter.getInstance().info(service)
188                         event = info.getEvent(service)
189                         session.open(SeriesPluginInfoScreen, service, event)
190                 except Exception as e:
191                         splog(_("SeriesPlugin extension exception ") + str(e))
192
193
194 #######################################################
195 # Timer
196 def checkTimers(session, *args, **kwargs):
197         runIndependent()
198
199
200 #######################################################
201 # Movielist menu rename
202 def movielist_rename(session, service, services=None, *args, **kwargs):
203         if config.plugins.seriesplugin.enabled.value:
204                 try:
205                         if services:
206                                 if not isinstance(services, list):
207                                         services = [services]   
208                         else:
209                                 services = [service]
210                         SeriesPluginRenamer(session, services)
211                 except Exception as e:
212                         splog(_("SeriesPlugin renamer exception ") + str(e))
213
214
215 #######################################################
216 # Movielist menu info
217 def movielist_info(session, service, *args, **kwargs):
218         if config.plugins.seriesplugin.enabled.value:
219                 try:
220                         session.open(SeriesPluginInfoScreen, service)
221                 except Exception as e:
222                         splog(_("SeriesPlugin extension exception ") + str(e))
223
224
225 #######################################################
226 # Timer renaming
227 def renameTimer(timer, name, begin, end, *args, **kwargs):
228         if config.plugins.seriesplugin.enabled.value:
229                 try:
230                         SeriesPluginTimer(timer, name, begin, end)
231                 except Exception as e:
232                         splog(_("SeriesPlugin label exception ") + str(e))
233
234
235 # For compatibility reasons
236 def modifyTimer(timer, name, *args, **kwargs):
237         if config.plugins.seriesplugin.enabled.value:
238                 splog("SeriesPlugin modifyTimer is deprecated - Update Your AutoTimer!")
239                 try:
240                         SeriesPluginTimer(timer, name or timer.name, timer.begin, timer.end)
241                 except Exception as e:
242                         splog(_("SeriesPlugin label exception ") + str(e))
243
244
245 # For compatibility reasons
246 def labelTimer(timer, begin=None, end=None, *args, **kwargs):
247         if config.plugins.seriesplugin.enabled.value:
248                 splog("SeriesPlugin labelTimer is deprecated - Update Your AutoTimer!")
249                 try:
250                         SeriesPluginTimer(timer, timer.name, timer.begin, timer.end)
251                 except Exception as e:
252                         splog(_("SeriesPlugin label exception ") + str(e))
253
254
255 #######################################################
256 # Plugin main function
257 def Plugins(**kwargs):
258         descriptors = []
259         
260         #TODO icon
261         descriptors.append( PluginDescriptor(
262                                                                                         name = NAME + " " + _("Setup"),
263                                                                                         description = NAME + " " + _("Setup"),
264                                                                                         where = PluginDescriptor.WHERE_PLUGINMENU,
265                                                                                         fnc = setup,
266                                                                                         needsRestart = False) )
267         
268         if config.plugins.seriesplugin.enabled.value:
269                 
270                 overwriteAutoTimer()
271                 
272                 descriptors.append( PluginDescriptor(
273                                                                                                         #where = PluginDescriptor.WHERE_SESSIONSTART,
274                                                                                                         where = PluginDescriptor.WHERE_AUTOSTART,
275                                                                                                         needsRestart = False,
276                                                                                                         fnc = start) )
277
278                 if config.plugins.seriesplugin.menu_info.value:
279                         descriptors.append( PluginDescriptor(
280                                                                                                         name = SHOWINFO,
281                                                                                                         description = SHOWINFO,
282                                                                                                         where = PluginDescriptor.WHERE_EVENTINFO,
283                                                                                                         needsRestart = False,
284                                                                                                         fnc = info) )
285
286                 if config.plugins.seriesplugin.menu_extensions.value:
287                         descriptors.append(PluginDescriptor(
288                                                                                                         name = SHOWINFO,
289                                                                                                         description = SHOWINFO,
290                                                                                                         where = PluginDescriptor.WHERE_EXTENSIONSMENU,
291                                                                                                         fnc = sp_extension,
292                                                                                                         needsRestart = False) )
293                         
294                 if config.plugins.seriesplugin.menu_channel.value:
295                         descriptors.append( PluginDescriptor(
296                                                                                                         name = SHOWINFO,
297                                                                                                         description = SHOWINFO,
298                                                                                                         where = PluginDescriptor.WHERE_CHANNEL_CONTEXT_MENU,
299                                                                                                         fnc = channel,
300                                                                                                         needsRestart = False) )
301                 
302                 if config.plugins.seriesplugin.check_timer_list.value:
303                         descriptors.append(PluginDescriptor(
304                                                                                                         name = CHECKTIMERS,
305                                                                                                         description = CHECKTIMERS,
306                                                                                                         where = PluginDescriptor.WHERE_EXTENSIONSMENU,
307                                                                                                         fnc = checkTimers,
308                                                                                                         needsRestart = False) )
309                 
310                 if config.plugins.seriesplugin.menu_movie_info.value:
311                         descriptors.append( PluginDescriptor(
312                                                                                                         name = SHOWINFO,
313                                                                                                         description = SHOWINFO,
314                                                                                                         where = PluginDescriptor.WHERE_MOVIELIST,
315                                                                                                         fnc = movielist_info,
316                                                                                                         needsRestart = False) )
317                 
318                 if config.plugins.seriesplugin.menu_movie_rename.value:
319                         descriptors.append( PluginDescriptor(
320                                                                                                         name = RENAMESERIES,
321                                                                                                         description = RENAMESERIES,
322                                                                                                         where = PluginDescriptor.WHERE_MOVIELIST,
323                                                                                                         fnc = movielist_rename,
324                                                                                                         needsRestart = False) )
325                 
326                 if config.plugins.seriesplugin.menu_epg.value:
327                         addSeriesPlugin(WHERE_EPGMENU, SHOWINFO)
328
329         return descriptors
330
331
332 #######################################################
333 # Override EPGSelection enterDateTime
334 EPGSelection_enterDateTime = None
335 #EPGSelection_openOutdatedEPGSelection = None
336 def SPEPGSelectionInit():
337         print "SeriesPlugin override EPGSelection"
338         global EPGSelection_enterDateTime #, EPGSelection_openOutdatedEPGSelection
339         if EPGSelection_enterDateTime is None: # and EPGSelection_openOutdatedEPGSelection is None:
340                 from Screens.EpgSelection import EPGSelection
341                 EPGSelection_enterDateTime = EPGSelection.enterDateTime
342                 EPGSelection.enterDateTime = enterDateTime
343                 #EPGSelection_openOutdatedEPGSelection = EPGSelection.openOutdatedEPGSelection
344                 #EPGSelection.openOutdatedEPGSelection = openOutdatedEPGSelection
345                 EPGSelection.SPcloseafterfinish = closeafterfinish
346
347 def SPEPGSelectionUndo():
348         print "SeriesPlugin undo override EPGSelection"
349         global EPGSelection_enterDateTime #, EPGSelection_openOutdatedEPGSelection
350         if EPGSelection_enterDateTime: # and EPGSelection_openOutdatedEPGSelection:
351                 from Screens.EpgSelection import EPGSelection
352                 EPGSelection.enterDateTime = EPGSelection_enterDateTime
353                 EPGSelection_enterDateTime = None
354                 #EPGSelection.openOutdatedEPGSelection = EPGSelection_openOutdatedEPGSelection
355                 #EPGSelection_openOutdatedEPGSelection = None
356
357 def enterDateTime(self):
358         from Screens.EpgSelection import EPG_TYPE_SINGLE,EPG_TYPE_MULTI,EPG_TYPE_SIMILAR
359         event = self["Event"].event
360         if self.type == EPG_TYPE_SINGLE:
361                 service = self.currentService
362         elif self.type == EPG_TYPE_MULTI:       
363                 service = self.services
364         elif self.type == EPG_TYPE_SIMILAR:
365                 service = self.currentService
366         if service and event:
367                 self.session.openWithCallback(self.SPcloseafterfinish, SeriesPluginInfoScreen, service, event) 
368                 return
369         EPGSelection_enterDateTime(self)
370
371 #def openOutdatedEPGSelection(self, reason=None):
372 #       if reason == 1:
373 #               EPGSelection_enterDateTime(self)
374
375
376 #######################################################
377 # Override ChannelContextMenu
378 ChannelContextMenu__init__ = None
379 def SPChannelContextMenuInit():
380         print "[SeriesPlugin] override ChannelContextMenu.__init__"
381         global ChannelContextMenu__init__
382         if ChannelContextMenu__init__ is None:
383                 from Screens.ChannelSelection import ChannelContextMenu
384                 ChannelContextMenu__init__ = ChannelContextMenu.__init__
385                 ChannelContextMenu.__init__ = SPChannelContextMenu__init__
386                 ChannelContextMenu.SPchannelShowSeriesInfo = channelShowSeriesInfo
387                 ChannelContextMenu.SPcloseafterfinish = closeafterfinish
388
389 def SPChannelContextMenuUndo():
390         print "[SeriesPlugin] override ChannelContextMenu.__init__"
391         global ChannelContextMenu__init__
392         if ChannelContextMenu__init__:
393                 from Screens.ChannelSelection import ChannelContextMenu
394                 ChannelContextMenu.__init__ = ChannelContextMenu__init__
395                 ChannelContextMenu__init__ = None
396
397 def SPChannelContextMenu__init__(self, session, csel):
398         from Components.ChoiceList import ChoiceEntryComponent
399         from Screens.ChannelSelection import MODE_TV
400         from Tools.BoundFunction import boundFunction
401         from enigma import eServiceReference
402         ChannelContextMenu__init__(self, session, csel)
403         current = csel.getCurrentSelection()
404         current_sel_path = current.getPath()
405         current_sel_flags = current.flags
406         if csel.mode == MODE_TV and not (current_sel_path or current_sel_flags & (eServiceReference.isDirectory|eServiceReference.isMarker)):
407                 self["menu"].list.insert(0, ChoiceEntryComponent(text=(SHOWINFO, boundFunction(self.SPchannelShowSeriesInfo))))
408
409 def channelShowSeriesInfo(self):
410         splog( "[SeriesPlugin] channelShowSeriesInfo ")
411         if config.plugins.seriesplugin.enabled.value:
412                 try:
413                         from enigma import eServiceCenter
414                         service = self.csel.servicelist.getCurrent()
415                         info = eServiceCenter.getInstance().info(service)
416                         event = info.getEvent(service)
417                         self.session.openWithCallback(self.SPcloseafterfinish, SeriesPluginInfoScreen, service, event)
418                 except Exception as e:
419                         splog(_("SeriesPlugin info exception ") + str(e))
420
421 def closeafterfinish(self, retval=None):
422         self.close()
423
424
425 #######################################################
426 # Add / Remove menu functions
427 def addSeriesPlugin(menu, title, fnc=None):
428         # Add to menu
429         if( menu == WHERE_EPGMENU ):
430                 SPEPGSelectionInit()
431         elif( menu == WHERE_CHANNELMENU ):
432                 SPChannelContextMenuInit()
433         else:
434                 from Components.PluginComponent import plugins
435                 if plugins:
436                         for p in plugins.getPlugins( where = menu ):
437                                 if p.name == title:
438                                         # Plugin is already in menu
439                                         break
440                         else:
441                                 # Plugin not in menu - add it
442                                 plugin = PluginDescriptor(
443                                                                                                                                 name = title,
444                                                                                                                                 description = title,
445                                                                                                                                 where = menu,
446                                                                                                                                 needsRestart = False,
447                                                                                                                                 fnc = fnc)
448                                 if menu in plugins.plugins:
449                                         plugins.plugins[ menu ].append(plugin)
450
451
452 def removeSeriesPlugin(menu, title):
453         # Remove from menu
454         if( menu == WHERE_EPGMENU ):
455                 SPEPGSelectionUndo()
456         elif( menu == WHERE_CHANNELMENU ):
457                 SPChannelContextMenuUndo()
458         else:
459                 from Components.PluginComponent import plugins
460                 if plugins:
461                         for p in plugins.getPlugins( where = menu ):
462                                 if p.name == title:
463                                         plugins.plugins[ menu ].remove(p)
464                                         break
465
466
467 #######################################################
468 # Overwrite AutoTimer support functions
469
470 try:
471         from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer
472         #from Plugins.Extensions.AutoTimer.plugin import autotimer as AutoTimer
473 except:
474         AutoTimer = None
475
476 ATmodifyTimer = None
477 ATcheckSimilarity = None
478
479
480 def overwriteAutoTimer():
481         try:
482                 global ATmodifyTimer, ATcheckSimilarity
483                 if AutoTimer:
484                         if ATmodifyTimer is None:
485                                 # Backup original function
486                                 ATmodifyTimer = AutoTimer.modifyTimer
487                                 # Overwrite function
488                                 AutoTimer.modifyTimer = SPmodifyTimer
489                         if ATcheckSimilarity is None:
490                                 # Backup original function
491                                 ATcheckSimilarity = AutoTimer.checkSimilarity
492                                 # Overwrite function
493                                 AutoTimer.checkSimilarity = SPcheckSimilarity
494         except:
495                 splog("SeriesPlugin found old AutoTimer")
496
497
498 def recoverAutoTimer():
499         try:
500                 global ATmodifyTimer, ATcheckSimilarity
501                 if AutoTimer:
502                         if ATmodifyTimer:
503                                 AutoTimer.modifyTimer = ATmodifyTimer
504                                 ATmodifyTimer = None
505                         if ATcheckSimilarity:
506                                 AutoTimer.checkSimilarity = ATcheckSimilarity
507                                 ATcheckSimilarity = None
508         except:
509                 splog("SeriesPlugin found old AutoTimer")
510
511
512 #######################################################
513 # Customized support functions
514
515 from difflib import SequenceMatcher
516 from ServiceReference import ServiceReference
517
518 def SPmodifyTimer(self, timer, name, shortdesc, begin, end, serviceref, eit=None):
519         # Never overwrite existing names, You will lose Your series informations
520         #timer.name = name
521         # Only overwrite non existing descriptions
522         timer.description = timer.description or shortdesc
523         timer.begin = int(begin)
524         timer.end = int(end)
525         timer.service_ref = ServiceReference(serviceref)
526         if eit:
527                 timer.eit = eit
528
529 def SPcheckSimilarity(self, timer, name1, name2, shortdesc1, shortdesc2, extdesc1, extdesc2, force=False):
530         # Check if the new title is part of the existing one
531         foundTitle = name1 in name2 or name2 in name1
532         
533         if timer.searchForDuplicateDescription > 0 or force:
534                 foundShort = (shortdesc1 in shortdesc2 or shortdesc2 in shortdesc1) if (timer.searchForDuplicateDescription > 0 or force) else True
535                 
536                 # NOTE: only check extended if short description already is a match because otherwise
537                 # it won't evaluate to True anyway
538                 if (timer.searchForDuplicateDescription > 0 or force) and foundShort and extdesc1 != extdesc2:
539                         # Some channels indicate replays in the extended descriptions
540                         # If the similarity percent is higher then 0.8 it is a very close match
541                         #if timer.series_labeling and (extdesc1 == "" or extdesc2 == ""):
542                         #       foundExt = True
543                         #else:
544                         foundExt = ( 0.8 < SequenceMatcher(lambda x: x == " ",extdesc1, extdesc2).ratio() )
545                 else:
546                         foundExt = True
547         else:
548                 foundShort = True
549                 foundExt = True
550         
551         return foundTitle and foundShort and foundExt