6aa5cf7148f77b40731a5ff81141a5068c7b20b7
[enigma2-plugins.git] / plugin.py
1 # Plugin definition
2 from Plugins.Plugin import PluginDescriptor
3
4 from Components.config import config, ConfigSubsection, ConfigSet
5 from Screens import PluginBrowser
6 from Screens.MessageBox import MessageBox
7 from Screens.ChoiceBox import ChoiceBox
8 from Components.PluginComponent import PluginComponent, plugins
9 from Components.PluginList import PluginEntryComponent
10 from Tools.Directories import resolveFilename, fileExists, SCOPE_SKIN_IMAGE, SCOPE_PLUGINS
11
12 from Components.ActionMap import ActionMap, NumberActionMap
13 from operator import attrgetter # python 2.5+
14
15 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
16
17 from enigma import eListboxPythonMultiContent, gFont
18 from Tools.LoadPixmap import LoadPixmap
19
20 from xml.etree.cElementTree import parse as cet_parse
21 try:
22         from xml.etree.cElementTree import ParseError
23 except ImportError, ie:
24         ParseError = SyntaxError
25
26 from shutil import copyfile, Error
27
28 XML_CONFIG = "/etc/enigma2/pluginsort.xml"
29
30 def SelectedPluginEntryComponent(plugin):
31         if plugin.icon is None:
32                 png = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/plugin.png"))
33         else:
34                 png = plugin.icon
35
36         return [
37                 plugin,
38                 MultiContentEntryText(pos=(120, 5), size=(320, 25), font=0, text=plugin.name, backcolor_sel=1234566),
39                 MultiContentEntryText(pos=(120, 26), size=(320, 17), font=1, text=plugin.description, backcolor_sel=1234566),
40                 MultiContentEntryPixmapAlphaTest(pos=(10, 5), size=(100, 40), png = png, backcolor_sel=1234566)
41         ]
42
43 WHEREMAP = {}
44 pdict = PluginDescriptor.__dict__
45 for where in pdict:
46         if where.startswith('WHERE_'):
47                 WHEREMAP[where] = pdict[where]
48 del pdict
49 reverse = lambda map: dict(zip(map.values(), map.keys()))
50
51 class PluginWeights:
52         def __init__(self):
53                 self.plugins = {}
54                 self.load()
55
56         def load(self):
57                 if not fileExists(XML_CONFIG):
58                         return
59
60                 try:
61                         config = cet_parse(XML_CONFIG).getroot()
62                 except ParseError, pe:
63                         from time import time
64                         print "[PluginSort] Parse Error occured in configuration, backing it up and starting from scratch!"
65                         try:
66                                 copyfile(XML_CONFIG, "/etc/enigma2/pluginsort.xml.%d" % (int(time()),))
67                         except Error, she:
68                                 print "[PluginSort] Uh oh, failed to create the backup... I hope you have one anyway :D"
69                         return
70
71                 for wheresection in config.findall('where'):
72                         where = wheresection.get('type')
73                         whereid = WHEREMAP.get(where, None)
74                         whereplugins = wheresection.findall('plugin')
75                         if not whereid or not whereplugins:
76                                 print "[PluginSort] Ignoring section %s because of invalid id (%s) or no plugins" % (where, repr(whereid))
77                                 continue
78
79                         for plugin in whereplugins:
80                                 name = plugin.get('name')
81                                 try:
82                                         weight = int(plugin.get('weight'))
83                                 except ValueError, ve:
84                                         print "[PluginSort] Invalid weight of %s received for plugin %s, ignoring" % (repr(plugin.get('weight')), repr(name))
85                                 else:
86                                         self.plugins.setdefault(whereid, {})[name] = weight
87
88         def save(self):
89                 list = ['<?xml version="1.0" ?>\n<pluginsort>\n\n']
90                 append = list.append
91                 extend = list.extend
92
93                 idmap = reverse(WHEREMAP)
94                 for key in self.plugins.keys():
95                         whereplugins = self.plugins.get(key, None)
96                         if not whereplugins:
97                                 continue
98
99                         where = idmap[key]
100                         extend((' <where type="', str(where), '">\n'))
101                         for key, value in whereplugins.iteritems():
102                                 extend(('  <plugin name="', str(key), '" weight="', str(value), '" />\n'))
103                         append((' </where>\n'))
104                 append('\n</pluginsort>\n')
105                 
106                 file = open(XML_CONFIG, 'w')
107                 file.writelines(list)
108                 file.close()
109
110         def get(self, plugin):
111                 for x in plugin.where:
112                         whereplugins = self.plugins.get(x, None)
113                         weight = whereplugins and whereplugins.get(plugin.name, None)
114                         if weight is not None:
115                                 return weight
116                 return plugin.weight
117
118         def set(self, plugin):
119                 for x in plugin.where:
120                         whereplugins = self.plugins.get(x, None)
121                         if whereplugins:
122                                 whereplugins[plugin.name] = plugin.weight
123                         else:
124                                 self.plugins[x] = {plugin.name: plugin.weight}
125
126 pluginWeights = PluginWeights()
127
128 def PluginComponent_addPlugin(self, plugin, *args, **kwargs):
129         newWeight = pluginWeights.get(plugin)
130         print "[PluginSort] Setting weight of %s from %d to %d" % (plugin.name, plugin.weight, newWeight)
131         plugin.weight = newWeight
132         PluginComponent.pluginSorter_baseAddPlugin(self, plugin, *args, **kwargs)
133
134 OriginalPluginBrowser = PluginBrowser.PluginBrowser
135 class SortingPluginBrowser(OriginalPluginBrowser):
136         def __init__(self, *args, **kwargs):
137                 OriginalPluginBrowser.__init__(self, *args, **kwargs)
138                 self.skinName = ["SortingPluginBrowser", "PluginBrowser"]
139
140                 self.movemode = False
141                 self.selected = -1
142
143                 self["MenuActions"] = ActionMap(["MenuActions"],
144                         {
145                                 "menu": self.openMenu,
146                         }, -1
147                 )
148                 self["ColorActions"] = ActionMap(["ColorActions"],
149                         {
150                                 "green": self.toggleMoveMode,
151                         }, -1
152                 )
153                 self["ColorActions"].setEnabled(False)
154
155                 self["WizardActions"] = ActionMap(["WizardActions"],
156                         {
157                                 "left": self.left,
158                                 "right": self.right,
159                                 "up": self.up,
160                                 "down": self.down,
161                         }, -2
162                 )
163                 # TODO: allow to select first 10 plugins by number (1-9, 0)
164                 self["NumberActions"] = NumberActionMap(["NumberActions"],
165                         {
166                                 "1": self.keyNumberGlobal,
167                                 "2": self.keyNumberGlobal,
168                                 "3": self.keyNumberGlobal,
169                                 "4": self.keyNumberGlobal,
170                                 "5": self.keyNumberGlobal,
171                                 "6": self.keyNumberGlobal,
172                                 "7": self.keyNumberGlobal,
173                                 "8": self.keyNumberGlobal,
174                                 "9": self.keyNumberGlobal,
175                                 "0": self.keyNumberGlobal,
176                         }, -1
177                 )
178
179         def keyNumberGlobal(self, number):
180                 if not self.movemode:
181                         realnumber = (number - 1) % 10
182                         if realnumber < len(self.list):
183                                 self["list"].moveToIndex(realnumber)
184                                 self.save()
185
186         def close(self, *args, **kwargs):
187                 if self.movemode:
188                         self.toggleMoveMode()
189                 OriginalPluginBrowser.close(self, *args, **kwargs)
190
191         # copied from PluginBrowser because we redo pretty much anything :-)
192         def updateList(self):
193                 self.pluginlist = plugins.getPlugins(PluginDescriptor.WHERE_PLUGINMENU)
194                 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
195                 self.list = [PluginEntryComponent(plugin) for plugin in self.pluginlist]
196                 self["list"].l.setList(self.list)
197                 if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
198                         # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
199                         self["red"].setText(_("Manage extensions"))
200                         self["green"].setText(_("Sort") if not self.movemode else _("End Sort"))
201                         self["SoftwareActions"].setEnabled(True)
202                         self["PluginDownloadActions"].setEnabled(False)
203                         self["ColorActions"].setEnabled(True)
204                 else:
205                         # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
206                         self["red"].setText(_("Remove Plugins"))
207                         # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
208                         self["green"].setText(_("Download Plugins"))
209                         self["SoftwareActions"].setEnabled(False)
210                         self["PluginDownloadActions"].setEnabled(True)
211                         self["ColorActions"].setEnabled(False)
212
213         def doMove(self, func):
214                 if self.selected != -1:
215                         oldpos = self["list"].getSelectedIndex()
216                         func()
217                         entry = self.list.pop(oldpos)
218                         newpos = self["list"].getSelectedIndex()
219                         self.list.insert(newpos, entry)
220                         self["list"].l.setList(self.list)
221                 else:
222                         func()
223
224         def left(self):
225                 self.doMove(self["list"].pageUp)
226
227         def right(self):
228                 self.doMove(self["list"].pageDown)
229
230         def up(self):
231                 self.doMove(self["list"].up)
232
233         def down(self):
234                 self.doMove(self["list"].down)
235
236         def save(self):
237                 selected = self.selected
238                 if not self.movemode:
239                         OriginalPluginBrowser.save(self)
240                 elif selected != -1:
241                         Len = len(self.pluginlist)
242                         newpos = self["list"].getSelectedIndex()
243                         entry = self.pluginlist[selected]
244                         self.pluginlist.remove(entry)
245                         self.pluginlist.insert(newpos, entry)
246
247                         # we moved up, increase weight of plugins after us
248                         if newpos < selected:
249                                 print "[PluginSort]", entry.name, "moved up"
250                                 i = newpos + 1
251                                 # since we moved up, there has to be an entry after this one
252                                 diff = abs(self.pluginlist[i].weight - self.pluginlist[newpos].weight) + 1
253                                 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)
254                                 while i < Len:
255                                         print "[PluginSort] INCREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff
256                                         self.pluginlist[i].weight += diff
257                                         i += 1
258                         # we moved down, decrease weight of plugins before us
259                         elif newpos > selected:
260                                 print "[PluginSort]", entry.name, "moved down"
261                                 i = newpos - 1
262                                 # since we moved up, there has to be an entry before this one
263                                 diff = abs(self.pluginlist[newpos].weight - self.pluginlist[i].weight) + 1
264                                 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)
265                                 while i > -1:
266                                         print "[PluginSort] DECREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff
267                                         self.pluginlist[i].weight -= diff
268                                         i -= 1
269                         else:
270                                 print "[PluginSort]", entry.name, "did not move (%d to %d)?" % (selected, newpos)
271
272                         self.list = [PluginEntryComponent(plugin) for plugin in self.pluginlist]
273                         print "[PluginSort] NEW LIST:", [(plugin.name, plugin.weight) for plugin in self.pluginlist]
274                         self["list"].l.setList(self.list)
275                         self.selected = -1
276                 else:
277                         self.selected = self["list"].getSelectedIndex()
278                         self.list[self.selected] = SelectedPluginEntryComponent(self.pluginlist[self.selected])
279                         self["list"].l.setList(self.list)
280         
281         def openMenu(self):
282                 if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/PluginHider/plugin.py")):
283                         list = [
284                                 (_("hide selected plugin"), self.hidePlugin),
285                                 (_("disable move mode") if self.movemode else _("enable move mode"), self.toggleMoveMode),
286                         ]
287                         self.session.openWithCallback(
288                                 self.menuCallback,
289                                 ChoiceBox,
290                                 list = list,
291                         )
292                 else:
293                         self.toggleMoveMode()
294
295         def menuCallback(self, ret):
296                 ret and ret[1]()
297
298         def hidePlugin(self):
299                 try:
300                         from Plugins.Extensions.PluginHider.plugin import hidePlugin
301                 except Exception, e:
302                         self.session.open(MessageBox, _("Unable to load PluginHider"), MessageBox.TYPE_ERROR)
303                 else:
304                         hidePlugin(self["list"].l.getCurrentSelection()[0])
305
306                         # we were actually in move mode, so save the current position
307                         if self.selected != -1:
308                                 self.save()
309                         self.updateList()
310
311         def toggleMoveMode(self):
312                 if self.movemode:
313                         if self.selected != -1:
314                                 self.save()
315                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
316                                 self["green"].setText(_("Sort"))
317
318                         for plugin in self.pluginlist:
319                                 pluginWeights.set(plugin)
320                         pluginWeights.save()
321                 else:
322                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
323                                 self["green"].setText(_("End Sort"))
324                 self.movemode = not self.movemode
325
326 def autostart(reason, *args, **kwargs):
327         if reason == 0:
328                 PluginComponent.pluginSorter_baseAddPlugin = PluginComponent.addPlugin
329                 PluginComponent.addPlugin = PluginComponent_addPlugin
330
331                 # "fix" weight of plugins already added to list, future ones will be fixed automatically
332                 for plugin in plugins.getPlugins(PluginDescriptor.WHERE_PLUGINMENU):
333                         # enigma2 older than 3.3.2011 does not know plugin weights, so default them to 0 manually
334                         try:
335                                 newWeight = pluginWeights.get(plugin)
336                         except AttributeError, ae:
337                                 plugin.weight = 0
338                                 newWeight = 0
339                                 PluginDescriptor.weight = 0
340                                 print "[PluginSort] Introduced weight attribute to PluginDescriptor for old enigma2 (this message may show multiple times)"
341
342                         print "[PluginSort] Fixing weight for %s (was %d, now %d)" % (plugin.name, plugin.weight, newWeight)
343                         plugin.weight = newWeight
344
345                 PluginBrowser.PluginBrowser = SortingPluginBrowser
346         else:
347                 PluginComponent.addPlugin = PluginComponent.pluginSorter_baseAddPlugin
348                 PluginBrowser.PluginBrowser = OriginalPluginBrowser
349
350 def Plugins(**kwargs):
351         return [
352                 PluginDescriptor(
353                         where=PluginDescriptor.WHERE_AUTOSTART,
354                         fnc=autostart,
355                         needsRestart=False, # TODO: check this!
356                 ),
357         ]