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