SeriesPlugin 2.2.2: Bulk update
[enigma2-plugins.git] / seriesplugin / src / Channels.py
1 # -*- coding: utf-8 -*-
2 #######################################################################
3 #
4 #    Series Plugin for Enigma-2
5 #    Coded by betonme (c) 2012 <glaserfrank(at)gmail.com>
6 #    Support: http://www.i-have-a-dreambox.com/wbb2/thread.php?threadid=TBD
7 #
8 #    This program is free software; you can redistribute it and/or
9 #    modify it under the terms of the GNU General Public License
10 #    as published by the Free Software Foundation; either version 2
11 #    of the License, or (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #######################################################################
19
20 import os
21 import re
22
23
24 # Config
25 from Components.config import config
26
27 from enigma import eServiceReference, eServiceCenter
28 from ServiceReference import ServiceReference
29
30 from Screens.MessageBox import MessageBox
31 from Tools.BoundFunction import boundFunction
32
33 # XML
34 from xml.etree.cElementTree import ElementTree, tostring, parse, Element, SubElement, Comment
35 from Tools.XMLTools import stringToXML
36
37 # Plugin internal
38 from . import _
39 from Logger import splog
40
41 try:
42         #Python >= 2.7
43         from collections import OrderedDict
44 except:
45         from OrderedDict import OrderedDict
46
47
48 ChannelReplaceDict = OrderedDict([
49         ('\(S\)', ''),
50         (' HD', ''),
51         (' TV', ''),
52         (' Television', ''),
53         (' Channel', ''),
54         ('III', 'drei'),
55         ('II',  'zwei'),
56         #('I',   'eins'),
57         ('ARD', 'daserste'),
58         ('\+', 'plus'),
59         ('0', 'null'),
60         ('1', 'eins'),
61         ('2', 'zwei'),
62         ('3', 'drei'),
63         ('4', 'vier'),
64         ('5', 'fuenf'),
65         ('6', 'sechs'),
66         ('7', 'sieben'),
67         ('8', 'acht'),
68         ('9', 'neun'),
69         ('\xc3\xa4', 'ae'),
70         ('\xc3\xb6', 'oe'),
71         ('\xc3\xbc', 'ue'),
72         ('\xc3\x84', 'ae'),
73         ('\xc3\x96', 'oe'),
74         ('\xc3\x9c', 'ue'),
75         ('\xc3\x9f', 'ss'),
76 ])
77 CompiledRegexpChannelUnify = re.compile('|'.join(ChannelReplaceDict))
78 CompiledRegexpChannelRemoveSpecialChars = re.compile('[^a-zA-Z0-9]')
79 def unifyChannel(text):
80         def translate(match):
81                 m = match.group(0)
82                 return ChannelReplaceDict.get(m, m)
83         
84         text = CompiledRegexpChannelUnify.sub(translate, text)
85         try:
86                 text = text.decode("utf-8").encode("latin1")
87         except:
88                 pass
89         text = CompiledRegexpChannelRemoveSpecialChars.sub('', text)
90         return text.strip().lower()
91
92
93 def getServiceList(ref):
94         root = eServiceReference(str(ref))
95         serviceHandler = eServiceCenter.getInstance()
96         return serviceHandler.list(root).getContent("SN", True)
97
98 def getTVBouquets():
99         from Screens.ChannelSelection import service_types_tv
100         return getServiceList(service_types_tv + ' FROM BOUQUET "bouquets.tv" ORDER BY bouquet')
101
102 def buildSTBchannellist(BouquetName = None):
103         chlist = None
104         chlist = []
105         mask = (eServiceReference.isMarker | eServiceReference.isDirectory)
106         splog("SPC: read STB Channellist..")
107         tvbouquets = getTVBouquets()
108         splog("SPC: found %s bouquet: %s" % (len(tvbouquets), tvbouquets) )
109
110         if not BouquetName:
111                 for bouquet in tvbouquets:
112                         bouquetlist = []
113                         bouquetlist = getServiceList(bouquet[0])
114                         for (serviceref, servicename) in bouquetlist:
115                                 playable = not (eServiceReference(serviceref).flags & mask)
116                                 if playable:
117                                         chlist.append((servicename, serviceref, unifyChannel(servicename)))
118         else:
119                 for bouquet in tvbouquets:
120                         if bouquet[1] == BouquetName:
121                                 bouquetlist = []
122                                 bouquetlist = getServiceList(bouquet[0])
123                                 for (serviceref, servicename) in bouquetlist:
124                                         playable = not (eServiceReference(serviceref).flags & mask)
125                                         if playable:
126                                                 chlist.append((servicename, serviceref, unifyChannel(servicename)))
127                                 break
128         return chlist
129
130 def getChannelByRef(stb_chlist,serviceref):
131         for (channelname,channelref) in stb_chlist:
132                 if channelref == serviceref:
133                         return channelname
134
135         
136
137 class ChannelsFile(object):
138
139         cache = ""
140         mtime = -1
141         
142         def __init__(self):
143                 pass
144
145         def readXML(self):
146                 path = config.plugins.seriesplugin.channel_file.value
147                 
148                 # Abort if no config found
149                 if not os.path.exists(path):
150                         splog("No configuration file present")
151                         return None
152                 
153                 # Parse if mtime differs from whats saved
154                 mtime = os.path.getmtime(path)
155                 if mtime == ChannelsFile.mtime:
156                         # No changes in configuration, won't read again
157                         return ChannelsFile.cache
158                 
159                 # Parse XML
160                 try:
161                         etree = parse(path).getroot()
162                 except Exception as e:
163                         splog("Exception in readXML: " + str(e))
164                         etree = None
165                         mtime = -1
166                 
167                 # Save time and cache file content
168                 ChannelsFile.mtime = mtime
169                 ChannelsFile.cache = etree
170                 return ChannelsFile.cache
171
172         def writeXML(self, etree):
173                 path = config.plugins.seriesplugin.channel_file.value
174                 
175                 def indent(elem, level=0):
176                         i = "\n" + level*"  "
177                         if len(elem):
178                                 if not elem.text or not elem.text.strip():
179                                         elem.text = i + "  "
180                                 if not elem.tail or not elem.tail.strip():
181                                         elem.tail = i
182                                 for elem in elem:
183                                         indent(elem, level+1)
184                                 if not elem.tail or not elem.tail.strip():
185                                         elem.tail = i
186                         else:
187                                 if level and (not elem.tail or not elem.tail.strip()):
188                                         elem.tail = i
189                 
190                 indent(etree)
191                 data = tostring(etree, 'utf-8')
192                 
193                 f = None
194                 try:
195                         f = open(path, 'w')
196                         if data:
197                                 f.writelines(data)
198                 except Exception as e:
199                         splog("Exception in writeXML: " + str(e))
200                 finally:
201                         if f is not None:
202                                 f.close()
203                 
204                 # Save time and cache file content
205                 self.mtime = os.path.getmtime( path )
206                 self.cache = etree
207
208
209 class ChannelsBase(ChannelsFile):
210
211         channels = {}  # channels[reference] = ( name, [ name1, name2, ... ] )
212         channels_changed = False
213         
214         def __init__(self):
215                 ChannelsFile.__init__(self)
216                 if not ChannelsBase.channels:
217                         self.resetChannels()
218         
219         def channelsEmpty(self):
220                 return not ChannelsBase.channels
221         
222         def resetChannels(self):
223                 ChannelsBase.channels = {}
224                 ChannelsBase.channels_changed = False
225                 
226                 self.loadXML()
227                 
228         #
229         # Channel handling
230         #
231         def compareChannels(self, ref, remote):
232                 splog("SP compareChannels", ref, remote)
233                 if ref in ChannelsBase.channels:
234                         ( name, alternatives ) = ChannelsBase.channels[ref]
235                         for altname in alternatives:
236                                 if altname in remote or remote in altname:
237                                         return True
238                         
239                 return False
240                 
241         def lookupChannelByReference(self, ref):
242                 if ref in ChannelsBase.channels:
243                         ( name, alternatives ) = ChannelsBase.channels[ref]
244                         altnames = []
245                         for altname in alternatives:
246                                 if altname:
247                                         splog("SP lookupChannelByReference", altname)
248                                         altnames.append(altname)
249                         return ' / '.join(altnames)
250                         
251                 return False
252         
253         def addChannel(self, ref, name, remote):
254                 splog("SP addChannel name remote", name, remote)
255                 
256                 if ref in ChannelsBase.channels:
257                         ( name, alternatives ) = ChannelsBase.channels[ref]
258                         if remote not in alternatives:
259                                 alternatives.append(remote)
260                                 ChannelsBase.channels[ref] = ( name, alternatives )
261                 else:
262                         ChannelsBase.channels[ref] = ( name, [remote] )
263                 ChannelsBase.channels_changed = True
264         
265         def replaceChannel(self, ref, name, remote):
266                 splog("SP addChannel name remote", name, remote)
267                 
268                 ChannelsBase.channels[ref] = ( name, [remote] )
269                 ChannelsBase.channels_changed = True
270
271         def removeChannel(self, ref):
272                 if ref in ChannelsBase.channels:
273                         del ChannelsBase.channels[ref]
274                         ChannelsBase.channels_changed = True
275
276         #
277         # I/O Functions
278         #
279         def loadXML(self):
280                 # Read xml config file
281                 root = self.readXML()
282                 if root:
283                         channels = {}
284                         
285                         # Parse Config
286                         def parse(root):
287                                 channels = {}
288                                 version = root.get("version", "1")
289                                 if version.startswith("2"):
290                                         if root:
291                                                 for element in root.findall("Channel"):
292                                                         name = element.get("name", "")
293                                                         reference = element.get("reference", "")
294                                                         if name and reference:
295                                                                 alternatives = []
296                                                                 for alternative in element.findall("Alternative"):
297                                                                         alternatives.append( alternative.text )
298                                                                 channels[reference] = (name, list(set(alternatives)))
299                                 elif version.startswith("1"):
300                                         splog("loadXML channels - Skip old file")
301                                 return channels
302                         
303                         channels = parse( root )
304                         #splog("loadXML channels", channels)
305                         splog("SP loadXML channels", len(channels))
306                 else:
307                         channels = {}
308                 ChannelsBase.channels = channels
309
310         def saveXML(self):
311                 if ChannelsBase.channels_changed:
312                         
313                         channels = ChannelsBase.channels
314                         
315                         # Generate List in RAM
316                         root = None
317                         #splog("saveXML channels", channels)
318                         splog("SP saveXML channels", len(channels))
319                         
320                         # Build Header
321                         from plugin import NAME, VERSION
322                         root = Element(NAME)
323                         root.set('version', VERSION)
324                         root.append(Comment(_("Don't edit this manually unless you really know what you are doing")))
325                         
326                         # Build Body
327                         def build(root, channels):
328                                 if channels:
329                                         for reference, namealternatives in channels.iteritems():
330                                                 name, alternatives = namealternatives
331                                                 # Add channel
332                                                 element = SubElement( root, "Channel", name = stringToXML(name), reference = stringToXML(reference) )
333                                                 # Add alternatives
334                                                 if alternatives:
335                                                         for name in alternatives:
336                                                                 SubElement( element, "Alternative" ).text = stringToXML(name)
337                                 return root
338                         
339                         root = build( root, channels )
340                         
341                         self.writeXML( root )