Merge branch 'master' of git+ssh://scm.schwerkraft.elitedvb.net/scmrepos/git/enigma2...
[enigma2-plugins.git] / pluginsort / src / plugin.py
1 from . import _
2
3 # Plugin definition
4 from Plugins.Plugin import PluginDescriptor
5
6 from Components.config import config, ConfigSubsection, ConfigSet
7 from Screens import PluginBrowser
8 from Screens.MessageBox import MessageBox
9 from Screens.ChoiceBox import ChoiceBox
10 from Components.PluginComponent import PluginComponent, plugins
11 from Components.PluginList import PluginEntryComponent
12 from Tools.Directories import resolveFilename, fileExists, SCOPE_SKIN_IMAGE, SCOPE_PLUGINS
13 from Tools.BoundFunction import boundFunction
14 from Screens.InfoBarGenerics import InfoBarPlugins
15
16 from Components.ActionMap import ActionMap, NumberActionMap
17 from operator import attrgetter # python 2.5+
18
19 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
20
21 from enigma import eListboxPythonMultiContent, gFont
22 from Tools.LoadPixmap import LoadPixmap
23
24 from xml.etree.cElementTree import parse as cet_parse
25 try:
26         from xml.etree.cElementTree import ParseError
27 except ImportError, ie:
28         ParseError = SyntaxError
29
30 from shutil import copyfile, Error
31
32 XML_CONFIG = "/etc/enigma2/pluginsort.xml"
33
34 def SelectedPluginEntryComponent(plugin):
35         if plugin.icon is None:
36                 png = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/plugin.png"))
37         else:
38                 png = plugin.icon
39
40         return [
41                 plugin,
42                 MultiContentEntryText(pos=(0,0), size=(440, 50), backcolor_sel=8388608),
43                 MultiContentEntryText(pos=(120, 5), size=(320, 25), font=0, text=plugin.name),
44                 MultiContentEntryText(pos=(120, 26), size=(320, 17), font=1, text=plugin.description),
45                 MultiContentEntryPixmapAlphaTest(pos=(10, 5), size=(100, 40), png = png),
46         ]
47
48 WHEREMAP = {}
49 pdict = PluginDescriptor.__dict__
50 for where in pdict:
51         if where.startswith('WHERE_'):
52                 WHEREMAP[where] = pdict[where]
53 del pdict
54 reverse = lambda map: dict(zip(map.values(), map.keys()))
55
56 class PluginWeights:
57         def __init__(self):
58                 self.plugins = {}
59                 self.load()
60
61         def load(self):
62                 if not fileExists(XML_CONFIG):
63                         return
64
65                 try:
66                         config = cet_parse(XML_CONFIG).getroot()
67                 except ParseError, pe:
68                         from time import time
69                         print "[PluginSort] Parse Error occured in configuration, backing it up and starting from scratch!"
70                         try:
71                                 copyfile(XML_CONFIG, "/etc/enigma2/pluginsort.xml.%d" % (int(time()),))
72                         except Error, she:
73                                 print "[PluginSort] Uh oh, failed to create the backup... I hope you have one anyway :D"
74                         return
75
76                 for wheresection in config.findall('where'):
77                         where = wheresection.get('type')
78                         whereid = WHEREMAP.get(where, None)
79                         whereplugins = wheresection.findall('plugin')
80                         if whereid is None or not whereplugins:
81                                 print "[PluginSort] Ignoring section %s because of invalid id (%s) or no plugins (%s)" % (where, repr(whereid), repr(whereplugins))
82                                 continue
83
84                         for plugin in whereplugins:
85                                 name = plugin.get('name')
86                                 try:
87                                         weight = int(plugin.get('weight'))
88                                 except ValueError, ve:
89                                         print "[PluginSort] Invalid weight of %s received for plugin %s, ignoring" % (repr(plugin.get('weight')), repr(name))
90                                 else:
91                                         self.plugins.setdefault(whereid, {})[name] = weight
92
93         def save(self):
94                 list = ['<?xml version="1.0" ?>\n<pluginsort>\n\n']
95                 append = list.append
96                 extend = list.extend
97
98                 idmap = reverse(WHEREMAP)
99                 for key in self.plugins.keys():
100                         whereplugins = self.plugins.get(key, None)
101                         if not whereplugins:
102                                 continue
103
104                         where = idmap[key]
105                         extend((' <where type="', str(where), '">\n'))
106                         for key, value in whereplugins.iteritems():
107                                 extend(('  <plugin name="', str(key), '" weight="', str(value), '" />\n'))
108                         append((' </where>\n'))
109                 append('\n</pluginsort>\n')
110                 
111                 file = open(XML_CONFIG, 'w')
112                 file.writelines(list)
113                 file.close()
114
115         def get(self, plugin):
116                 for x in plugin.where:
117                         whereplugins = self.plugins.get(x, None)
118                         weight = whereplugins and whereplugins.get(plugin.name, None)
119                         if weight is not None:
120                                 return weight
121                 return plugin.weight
122
123         def set(self, plugin):
124                 for x in plugin.where:
125                         whereplugins = self.plugins.get(x, None)
126                         if whereplugins:
127                                 whereplugins[plugin.name] = plugin.weight
128                         else:
129                                 self.plugins[x] = {plugin.name: plugin.weight}
130
131 pluginWeights = PluginWeights()
132
133 def PluginComponent_addPlugin(self, plugin, *args, **kwargs):
134         if len(plugin.where) > 1:
135                 print "[PluginSort] Splitting %s up in individual entries" % (plugin.name,)
136                 for x in plugin.where:
137                         if hasattr(plugin, 'iconstr'):
138                                 icon = plugin.iconstr
139                         else:
140                                 icon = plugin.icon
141                         pd = PluginDescriptor(name=plugin.name, where=[x], description=plugin.description, icon=icon, fnc=plugin.__call__, wakeupfnc=plugin.wakeupfnc, needsRestart=plugin.needsRestart, internal=plugin.internal, weight=plugin.weight)
142                         PluginComponent_addPlugin(self, pd)
143                 return
144
145         newWeight = pluginWeights.get(plugin)
146         print "[PluginSort] Setting weight of %s from %d to %d" % (plugin.name, plugin.weight, newWeight)
147         plugin.weight = newWeight
148         PluginComponent.pluginSorter_baseAddPlugin(self, plugin, *args, **kwargs)
149
150 OriginalPluginBrowser = PluginBrowser.PluginBrowser
151 class SortingPluginBrowser(OriginalPluginBrowser):
152         def __init__(self, *args, **kwargs):
153                 self.movemode = False
154                 self.selected = -1
155                 if 'where' in kwargs:
156                         self.where = kwargs['where']
157                         del kwargs['where']
158                 else:
159                         self.where = PluginDescriptor.WHERE_PLUGINMENU
160
161                 OriginalPluginBrowser.__init__(self, *args, **kwargs)
162                 self.skinName = ["SortingPluginBrowser", "PluginBrowser"]
163
164                 self["ColorActions"] = ActionMap(["ColorActions"],
165                         {
166                                 "green": self.toggleMoveMode,
167                         }, -2
168                 )
169                 self["ColorActions"].setEnabled(False)
170
171                 self["WizardActions"] = ActionMap(["WizardActions"],
172                         {
173                                 "left": boundFunction(self.doMove, self["list"].pageUp),
174                                 "right": boundFunction(self.doMove, self["list"].pageDown),
175                                 "up": boundFunction(self.doMove, self["list"].up),
176                                 "down": boundFunction(self.doMove, self["list"].down),
177                         }, -2
178                 )
179
180                 if self.where != PluginDescriptor.WHERE_PLUGINMENU:
181                         self.toggleMoveMode()
182                         self.onShow.append(self.setCustomTitle)
183                 else:
184                         self["NumberActions"] = NumberActionMap(["NumberActions"],
185                                 {
186                                         "1": self.keyNumberGlobal,
187                                         "2": self.keyNumberGlobal,
188                                         "3": self.keyNumberGlobal,
189                                         "4": self.keyNumberGlobal,
190                                         "5": self.keyNumberGlobal,
191                                         "6": self.keyNumberGlobal,
192                                         "7": self.keyNumberGlobal,
193                                         "8": self.keyNumberGlobal,
194                                         "9": self.keyNumberGlobal,
195                                         "0": self.keyNumberGlobal,
196                                 }, -2
197                         )
198
199                         self["MenuActions"] = ActionMap(["MenuActions"],
200                                 {
201                                         "menu": self.openMenu,
202                                 }, -1
203                         )
204
205         def setCustomTitle(self):
206                 titleMap = {
207                         PluginDescriptor.WHERE_EXTENSIONSMENU: _("Sort Extensions"),
208                         PluginDescriptor.WHERE_MOVIELIST: _("Sort MovieList Extensions"),
209                         PluginDescriptor.WHERE_EVENTINFO: _("Sort EventInfo Extensions"),
210                 }
211                 title = titleMap.get(self.where, None)
212                 if title:
213                         self.setTitle(title)
214
215         def keyNumberGlobal(self, number):
216                 if not self.movemode:
217                         realnumber = (number - 1) % 10
218                         if realnumber < len(self.list):
219                                 self["list"].moveToIndex(realnumber)
220                                 self.save()
221
222         def close(self, *args, **kwargs):
223                 if self.movemode:
224                         self.toggleMoveMode()
225                 OriginalPluginBrowser.close(self, *args, **kwargs)
226
227         # copied from PluginBrowser because we redo pretty much anything :-)
228         def updateList(self):
229                 self.pluginlist = plugins.getPlugins(self.where)
230                 if self.where in (PluginDescriptor.WHERE_PLUGINMENU, PluginDescriptor.WHERE_EXTENSIONSMENU):
231                         self.pluginlist.sort(key=attrgetter('weight', 'name')) # sort first by weight, then by name; we get pretty much a weight sorted but otherwise random list
232                 else: #if self.where in (PluginDescriptor.WHERE_EVENTINFO, PluginDescriptor.WHERE_MOVIELIST):
233                         self.pluginlist.sort(key=attrgetter('weight'))
234                 self.list = [PluginEntryComponent(plugin) for plugin in self.pluginlist]
235                 self["list"].l.setList(self.list)
236                 if self.where == PluginDescriptor.WHERE_PLUGINMENU:
237                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
238                                 # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
239                                 self["red"].setText(_("Manage extensions"))
240                                 self["green"].setText(_("Sort") if not self.movemode else _("End Sort"))
241                                 self["SoftwareActions"].setEnabled(True)
242                                 self["PluginDownloadActions"].setEnabled(False)
243                                 self["ColorActions"].setEnabled(True)
244                         else:
245                                 # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
246                                 self["red"].setText(_("Remove Plugins"))
247                                 # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
248                                 self["green"].setText(_("Download Plugins"))
249                                 self["SoftwareActions"].setEnabled(False)
250                                 self["PluginDownloadActions"].setEnabled(True)
251                                 self["ColorActions"].setEnabled(False)
252                 else:
253                         self["red"].setText("")
254                         self["green"].setText(_("Sort") if not self.movemode else _("End Sort"))
255                         self["SoftwareActions"].setEnabled(False)
256                         self["PluginDownloadActions"].setEnabled(False)
257                         self["ColorActions"].setEnabled(True)
258
259         def doMove(self, func):
260                 if self.selected != -1:
261                         oldpos = self["list"].getSelectedIndex()
262                         func()
263                         entry = self.list.pop(oldpos)
264                         newpos = self["list"].getSelectedIndex()
265                         self.list.insert(newpos, entry)
266                         self["list"].l.setList(self.list)
267                 else:
268                         func()
269
270         def save(self):
271                 selected = self.selected
272                 if not self.movemode:
273                         OriginalPluginBrowser.save(self)
274                 elif selected != -1:
275                         Len = len(self.pluginlist)
276                         newpos = self["list"].getSelectedIndex()
277                         entry = self.pluginlist[selected]
278                         self.pluginlist.remove(entry)
279                         self.pluginlist.insert(newpos, entry)
280
281                         # we moved up, increase weight of plugins after us
282                         if newpos < selected:
283                                 print "[PluginSort]", entry.name, "moved up"
284                                 i = newpos + 1
285                                 # since we moved up, there has to be an entry after this one
286                                 diff = abs(self.pluginlist[i].weight - self.pluginlist[newpos].weight) + 1
287                                 print "[PluginSort] Using weight from %d (%d) and %d (%d) to calculate diff (%d)" % (i, self.pluginlist[i].weight, newpos, self.pluginlist[newpos].weight, diff)
288                                 while i < Len:
289                                         print "[PluginSort] INCREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff
290                                         self.pluginlist[i].weight += diff
291                                         i += 1
292                         # we moved down, decrease weight of plugins before us
293                         elif newpos > selected:
294                                 print "[PluginSort]", entry.name, "moved down"
295                                 i = newpos - 1
296                                 # since we moved up, there has to be an entry before this one
297                                 diff = abs(self.pluginlist[newpos].weight - self.pluginlist[i].weight) + 1
298                                 print "[PluginSort] Using weight from %d (%d) and %d (%d) to calculate diff (%d)" % (newpos, self.pluginlist[newpos].weight, i, self.pluginlist[i].weight, diff)
299                                 while i > -1:
300                                         print "[PluginSort] DECREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff
301                                         self.pluginlist[i].weight -= diff
302                                         i -= 1
303                         else:
304                                 print "[PluginSort]", entry.name, "did not move (%d to %d)?" % (selected, newpos)
305
306                         self.list = [PluginEntryComponent(plugin) for plugin in self.pluginlist]
307                         print "[PluginSort] NEW LIST:", [(plugin.name, plugin.weight) for plugin in self.pluginlist]
308                         self["list"].l.setList(self.list)
309                         self.selected = -1
310                 else:
311                         self.selected = self["list"].getSelectedIndex()
312                         self.list[self.selected] = SelectedPluginEntryComponent(self.pluginlist[self.selected])
313                         self["list"].l.setList(self.list)
314         
315         def openMenu(self):
316                 if self.movemode:
317                         # TRANSLATORS: there is no need to translate this string, as it was reused from e2 core
318                         moveString = _("disable move mode")
319                 else:
320                         # TRANSLATORS: there is no need to translate this string, as it was reused from e2 core
321                         moveString = _("enable move mode")
322
323                 list = [
324                         (moveString, self.toggleMoveMode),
325                         (_("move extensions"), boundFunction(self.openMover, PluginDescriptor.WHERE_EXTENSIONSMENU)),
326                         (_("move movie extensions"), boundFunction(self.openMover, PluginDescriptor.WHERE_MOVIELIST)),
327                         (_("move event extensions"), boundFunction(self.openMover, PluginDescriptor.WHERE_EVENTINFO)),
328                 ]
329
330                 if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/PluginHider/plugin.py")):
331                         list.insert(0, (_("hide selected plugin"), self.hidePlugin))
332                         self.session.openWithCallback(
333                                 self.menuCallback,
334                                 ChoiceBox,
335                                 list = list,
336                         )
337
338         def menuCallback(self, ret):
339                 ret and ret[1]()
340
341         def openMover(self, where):
342                 self.session.open(SortingPluginBrowser, where=where)
343
344         def hidePlugin(self):
345                 try:
346                         from Plugins.Extensions.PluginHider.plugin import hidePlugin
347                 except Exception, e:
348                         self.session.open(MessageBox, _("Unable to load PluginHider"), MessageBox.TYPE_ERROR)
349                 else:
350                         hidePlugin(self["list"].l.getCurrentSelection()[0])
351
352                         # we were actually in move mode, so save the current position
353                         if self.selected != -1:
354                                 self.save()
355                         self.updateList()
356
357         def toggleMoveMode(self):
358                 if self.movemode:
359                         if self.selected != -1:
360                                 self.save()
361                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
362                                 self["green"].setText(_("Sort"))
363
364                         for plugin in self.pluginlist:
365                                 pluginWeights.set(plugin)
366                         pluginWeights.save()
367
368                         # auto-close if not "PluginBrowser"
369                         if self.where != PluginDescriptor.WHERE_PLUGINMENU:
370                                 self.movemode = False
371                                 return self.close()
372                 else:
373                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
374                                 self["green"].setText(_("End Sort"))
375                 self.movemode = not self.movemode
376
377 def autostart(reason, *args, **kwargs):
378         if reason == 0:
379                 PluginComponent.pluginSorter_baseAddPlugin = PluginComponent.addPlugin
380                 PluginComponent.addPlugin = PluginComponent_addPlugin
381
382                 # "fix" weight of plugins already added to list, future ones will be fixed automatically
383                 for plugin in plugins.getPlugins([PluginDescriptor.WHERE_PLUGINMENU, PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_MOVIELIST, PluginDescriptor.WHERE_EVENTINFO]):
384                         # create individual entries for multiple wheres, this is potentially harmful!
385                         if len(plugin.where) > 1:
386                                 # remove all entries except for a potential autostart one (highly unlikely to mix autostart with one of the above, but you never know :D)
387                                 if PluginDescriptor.WHERE_AUTOSTART in plugin.where:
388                                         plugin.where.remove(WHERE_AUTOSTART)
389                                         hadAutostart = True
390                                 else:
391                                         hadAutostart = False
392                                 plugins.removePlugin(plugin)
393                                 plugins.addPlugin(plugin) # this is our own addPlugin now, which automatically creates copies
394
395                                 # HACK: re-add autostart entry to internal list inside PluginComponent
396                                 if hadAutostart:
397                                         plugin.where = [ PluginDescriptor.WHERE_AUTOSTART ]
398                                         plugins.pluginList.append(plugin)
399
400                         # we're keeping the entry, just fix the weight
401                         else:
402                                 newWeight = pluginWeights.get(plugin)
403                                 print "[PluginSort] Fixing weight for %s (was %d, now %d)" % (plugin.name, plugin.weight, newWeight)
404                                 plugin.weight = newWeight
405
406                 PluginBrowser.PluginBrowser = SortingPluginBrowser
407
408                 # let movieepg fix extensions list sorting if installed, else do this ourselves
409                 if not fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/MovieEPG/plugin.py")):
410                         def InfoBarPlugins_getPluginList(self, *args, **kwargs):
411                                 l = InfoBarPlugins.pluginSort_baseGetPluginList(self, *args, **kwargs)
412                                 try:
413                                         l.sort(key=lambda e: (e[0][1].args[0].weight, e[2]))
414                                 except Exception, e:
415                                         print "[PluginSort] Failed to sort extensions", e
416                                 return l
417
418                         InfoBarPlugins.pluginSort_baseGetPluginList = InfoBarPlugins.getPluginList
419                         InfoBarPlugins.getPluginList = InfoBarPlugins_getPluginList
420
421         else:
422                 PluginComponent.addPlugin = PluginComponent.pluginSorter_baseAddPlugin
423                 PluginBrowser.PluginBrowser = OriginalPluginBrowser
424                 if hasattr(InfoBarPlugins, 'pluginSort_baseGetPluginList'):
425                         InfoBarPlugins.getPluginList = InfoBarPlugins.pluginSort_baseGetPluginList
426
427 def Plugins(**kwargs):
428         return [
429                 PluginDescriptor(
430                         where=PluginDescriptor.WHERE_AUTOSTART,
431                         fnc=autostart,
432                         needsRestart=False, # TODO: check this!
433                 ),
434         ]