pluginsort: pluginhider integration, selection by numbers
[enigma2-plugins.git] / pluginsort / src / 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                         self["red"].setText(_("Manage extensions"))
199                         self["green"].setText(_("Sort") if not self.movemode else _("End Sort"))
200                         self["SoftwareActions"].setEnabled(True)
201                         self["PluginDownloadActions"].setEnabled(False)
202                         self["ColorActions"].setEnabled(True)
203                 else:
204                         self["red"].setText(_("Remove Plugins"))
205                         self["green"].setText(_("Download Plugins"))
206                         self["SoftwareActions"].setEnabled(False)
207                         self["PluginDownloadActions"].setEnabled(True)
208                         self["ColorActions"].setEnabled(False)
209
210         def doMove(self, func):
211                 if self.selected != -1:
212                         oldpos = self["list"].getSelectedIndex()
213                         func()
214                         entry = self.list.pop(oldpos)
215                         newpos = self["list"].getSelectedIndex()
216                         self.list.insert(newpos, entry)
217                         self["list"].l.setList(self.list)
218                 else:
219                         func()
220
221         def left(self):
222                 self.doMove(self["list"].pageUp)
223
224         def right(self):
225                 self.doMove(self["list"].pageDown)
226
227         def up(self):
228                 self.doMove(self["list"].up)
229
230         def down(self):
231                 self.doMove(self["list"].down)
232
233         def save(self):
234                 selected = self.selected
235                 if not self.movemode:
236                         OriginalPluginBrowser.save(self)
237                 elif selected != -1:
238                         Len = len(self.pluginlist)
239                         newpos = self["list"].getSelectedIndex()
240                         entry = self.pluginlist[selected]
241                         self.pluginlist.remove(entry)
242                         self.pluginlist.insert(newpos, entry)
243
244                         # we moved up, increase weight of plugins after us
245                         if newpos < selected:
246                                 print "[PluginSort]", entry.name, "moved up"
247                                 i = newpos + 1
248                                 # since we moved up, there has to be an entry after this one
249                                 diff = abs(self.pluginlist[i].weight - self.pluginlist[newpos].weight) + 1
250                                 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)
251                                 while i < Len:
252                                         print "[PluginSort] INCREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff
253                                         self.pluginlist[i].weight += diff
254                                         i += 1
255                         # we moved down, decrease weight of plugins before us
256                         elif newpos > selected:
257                                 print "[PluginSort]", entry.name, "moved down"
258                                 i = newpos - 1
259                                 # since we moved up, there has to be an entry before this one
260                                 diff = abs(self.pluginlist[newpos].weight - self.pluginlist[i].weight) + 1
261                                 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)
262                                 while i > -1:
263                                         print "[PluginSort] DECREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff
264                                         self.pluginlist[i].weight -= diff
265                                         i -= 1
266                         else:
267                                 print "[PluginSort]", entry.name, "did not move (%d to %d)?" % (selected, newpos)
268
269                         self.list = [PluginEntryComponent(plugin) for plugin in self.pluginlist]
270                         print "[PluginSort] NEW LIST:", [(plugin.name, plugin.weight) for plugin in self.pluginlist]
271                         self["list"].l.setList(self.list)
272                         self.selected = -1
273                 else:
274                         self.selected = self["list"].getSelectedIndex()
275                         self.list[self.selected] = SelectedPluginEntryComponent(self.pluginlist[self.selected])
276                         self["list"].l.setList(self.list)
277         
278         def openMenu(self):
279                 if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/PluginHider/plugin.py")):
280                         list = [
281                                 (_("hide selected plugin"), self.hidePlugin),
282                                 (_("disable move mode") if self.movemode else _("enable move mode"), self.toggleMoveMode),
283                         ]
284                         self.session.openWithCallback(
285                                 self.menuCallback,
286                                 ChoiceBox,
287                                 list = list,
288                         )
289                 else:
290                         self.toggleMoveMode()
291
292         def menuCallback(self, ret):
293                 ret and ret[1]()
294
295         def hidePlugin(self):
296                 try:
297                         from Plugins.Extensions.PluginHider.plugin import hidePlugin
298                 except Exception, e:
299                         self.session.open(MessageBox, _("Unable to load PluginHider"), MessageBox.TYPE_ERROR)
300                 else:
301                         hidePlugin(self["list"].l.getCurrentSelection()[0])
302
303                         # we were actually in move mode, so save the current position
304                         if self.selected != -1:
305                                 self.save()
306                         self.updateList()
307
308         def toggleMoveMode(self):
309                 if self.movemode:
310                         if self.selected != -1:
311                                 self.save()
312                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
313                                 self["green"].setText(_("Sort"))
314
315                         for plugin in self.pluginlist:
316                                 pluginWeights.set(plugin)
317                         pluginWeights.save()
318                 else:
319                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
320                                 self["green"].setText(_("End Sort"))
321                 self.movemode = not self.movemode
322
323 def autostart(reason, *args, **kwargs):
324         if reason == 0:
325                 PluginComponent.pluginSorter_baseAddPlugin = PluginComponent.addPlugin
326                 PluginComponent.addPlugin = PluginComponent_addPlugin
327
328                 # "fix" weight of plugins already added to list, future ones will be fixed automatically
329                 for plugin in plugins.getPlugins(PluginDescriptor.WHERE_PLUGINMENU):
330                         newWeight = pluginWeights.get(plugin)
331                         print "[PluginSort] Fixing weight for %s (was %d, now %d)" % (plugin.name, plugin.weight, newWeight)
332                         plugin.weight = newWeight
333
334                 PluginBrowser.PluginBrowser = SortingPluginBrowser
335         else:
336                 PluginComponent.addPlugin = PluginComponent.pluginSorter_baseAddPlugin
337                 PluginBrowser.PluginBrowser = OriginalPluginBrowser
338
339 def Plugins(**kwargs):
340         return [
341                 PluginDescriptor(
342                         where=PluginDescriptor.WHERE_AUTOSTART,
343                         fnc=autostart,
344                         needsRestart=False, # TODO: check this!
345                 ),
346         ]