autotimer,pluginsort: allow mphelp localization
[enigma2-plugins.git] / pluginsort / src / plugin.py
1 from __future__ import print_function
2
3 from . import _
4
5 # Plugin definition
6 from Plugins.Plugin import PluginDescriptor
7
8 from Screens import PluginBrowser
9 from Screens.MessageBox import MessageBox
10 from Screens.ChoiceBox import ChoiceBox
11 from Components.PluginComponent import PluginComponent, plugins
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 from Components.config import config, ConfigSubsection, ConfigYesNo
16 from Components.PluginList import PluginList
17 from Components.Converter.TemplatedMultiContent import TemplatedMultiContent
18 from Components.Renderer.Listbox import Listbox as ListboxRenderer
19
20 from Components.ActionMap import ActionMap, NumberActionMap
21 from operator import attrgetter # python 2.5+
22
23 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
24
25 from enigma import eListboxPythonMultiContent, gFont
26 from Tools.LoadPixmap import LoadPixmap
27
28 from xml.etree.cElementTree import parse as cet_parse
29 try:
30         from xml.etree.cElementTree import ParseError
31 except ImportError as ie:
32         ParseError = SyntaxError
33 from Tools.XMLTools import stringToXML
34
35 from shutil import copyfile, Error
36
37 XML_CONFIG = "/etc/enigma2/pluginsort.xml"
38 DEBUG = False
39
40 config.plugins.pluginsort = ConfigSubsection()
41 config.plugins.pluginsort.show_help = ConfigYesNo(default=True)
42
43 def MyPluginEntryComponent(plugin, backcolor_sel=None):
44         if plugin.icon is None:
45                 png = LoadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/icons/plugin.png"))
46         else:
47                 png = plugin.icon
48
49         return [
50                 plugin, plugin.name, plugin.description, png,
51                 #plugin, backcolor_sel, plugin.name, plugin.description, png,
52         ]
53
54 # TODO: make selected color themable
55 SelectedPluginEntryComponent = lambda plugin: MyPluginEntryComponent(plugin, backcolor_sel=8388608)
56
57 class MyPluginList(PluginList):
58         def __init__(self, *args, **kwargs):
59                 PluginList.__init__(self, *args, **kwargs)
60                 self.__inst = None
61
62         def __instance(self):
63                 if self.__inst is not None:
64                         return self.__inst
65                 for x in self.downstream_elements:
66                         if isinstance(x, TemplatedMultiContent):
67                                 for y in x.downstream_elements:
68                                         if isinstance(y, ListboxRenderer):
69                                                 self.__inst = y.instance
70                                                 return self.__inst
71                 return None
72
73         def up(self):
74                 instance = self.__instance()
75                 if instance: instance.moveSelection(instance.moveUp)
76         def down(self):
77                 instance = self.__instance()
78                 if instance: instance.moveSelection(instance.moveDown)
79         def pageUp(self):
80                 instance = self.__instance()
81                 if instance: instance.moveSelection(instance.pageUp)
82         def pageDown(self):
83                 instance = self.__instance()
84                 if instance: instance.moveSelection(instance.pageDown)
85
86 WHEREMAP = {}
87 pdict = PluginDescriptor.__dict__
88 for where in pdict:
89         if where.startswith('WHERE_'):
90                 WHEREMAP[where] = pdict[where]
91 del pdict
92
93 try:
94         dict.iteritems
95         iteritems = lambda d: d.iteritems()
96 except AttributeError:
97         iteritems = lambda d: d.items()
98 reverse = lambda map: dict((v,k) for k,v in iteritems(map))
99
100 class PluginWeights:
101         def __init__(self):
102                 self.plugins = {}
103                 self.load()
104
105         def load(self):
106                 if not fileExists(XML_CONFIG):
107                         return
108
109                 try:
110                         config = cet_parse(XML_CONFIG).getroot()
111                 except ParseError as pe:
112                         from time import time
113                         print("[PluginSort] Parse Error occured in configuration, backing it up and starting from scratch!")
114                         try:
115                                 copyfile(XML_CONFIG, "/etc/enigma2/pluginsort.xml.%d" % (int(time()),))
116                         except Error as she:
117                                 print("[PluginSort] Uh oh, failed to create the backup... I hope you have one anyway :D")
118                         return
119
120                 for wheresection in config.findall('where'):
121                         where = wheresection.get('type')
122                         whereid = WHEREMAP.get(where, None)
123                         whereplugins = wheresection.findall('plugin')
124                         if whereid is None or not whereplugins:
125                                 print("[PluginSort] Ignoring section %s because of invalid id (%s) or no plugins (%s)" % (where, repr(whereid), repr(whereplugins)))
126                                 continue
127
128                         for plugin in whereplugins:
129                                 name = plugin.get('name')
130                                 try:
131                                         weight = int(plugin.get('weight'))
132                                 except ValueError as ve:
133                                         print("[PluginSort] Invalid weight of %s received for plugin %s, ignoring" % (repr(plugin.get('weight')), repr(name)))
134                                 else:
135                                         self.plugins.setdefault(whereid, {})[name] = weight
136
137         def save(self):
138                 lst = ['<?xml version="1.0" ?>\n<pluginsort>\n\n']
139                 append = lst.append
140                 extend = lst.extend
141
142                 idmap = reverse(WHEREMAP)
143                 for key in self.plugins.keys():
144                         whereplugins = self.plugins.get(key, None)
145                         if not whereplugins:
146                                 continue
147
148                         where = idmap[key]
149                         extend((' <where type="', str(where), '">\n'))
150                         for key, value in iteritems(whereplugins):
151                                 extend(('  <plugin name="', stringToXML(str(key)), '" weight="', str(value), '" />\n'))
152                         append((' </where>\n'))
153                 append('\n</pluginsort>\n')
154                 
155                 with open(XML_CONFIG, 'w') as config:
156                         config.writelines(lst)
157
158         def get(self, plugin):
159                 for x in plugin.where:
160                         whereplugins = self.plugins.get(x, None)
161                         weight = whereplugins and whereplugins.get(plugin.name, None)
162                         if weight is not None:
163                                 return weight
164                 return plugin.weight
165
166         def set(self, plugin):
167                 for x in plugin.where:
168                         whereplugins = self.plugins.get(x, None)
169                         if whereplugins:
170                                 whereplugins[plugin.name] = plugin.weight
171                         else:
172                                 self.plugins[x] = {plugin.name: plugin.weight}
173
174 pluginWeights = PluginWeights()
175
176 def PluginComponent_addPlugin(self, plugin, *args, **kwargs):
177         if len(plugin.where) > 1:
178                 print("[PluginSort] Splitting %s up in individual entries (%s)" % (plugin.name, repr(plugin.where)))
179                 for x in plugin.where:
180                         pd = PluginDescriptor(name=plugin.name, where=[x], description=plugin.description, icon=plugin.icon, fnc=plugin.__call__, wakeupfnc=plugin.wakeupfnc, needsRestart=plugin.needsRestart, internal=plugin.internal, weight=plugin.weight)
181
182                         newWeight = pluginWeights.get(pd)
183                         if DEBUG: print("[PluginSort] Setting weight of %s from %d to %d" % (pd.name, pd.weight, newWeight))
184                         pd.weight = newWeight
185                         PluginComponent.pluginSort_baseAddPlugin(self, pd, *args, **kwargs)
186
187                 # installedPluginList is a list of original descriptors, but we changed it to be a copy, not a reference. so keep it up to date
188                 if self.firstRun:
189                         self.installedPluginList.append(plugin)
190                         if DEBUG: print("[PluginSort] Adding %s to list of installed plugins (%s, %s)." % (plugin.name, plugin.path, repr(plugin.where)))
191                 return
192
193         newWeight = pluginWeights.get(plugin)
194         if DEBUG: print("[PluginSort] Setting weight of %s from %d to %d" % (plugin.name, plugin.weight, newWeight))
195         plugin.weight = newWeight
196         PluginComponent.pluginSort_baseAddPlugin(self, plugin, *args, **kwargs)
197
198         if self.firstRun:
199                 if DEBUG: print("[PluginSort] Adding %s to list of installed plugins (%s, %s)." % (plugin.name, plugin.path, repr(plugin.where)))
200                 self.installedPluginList.append(plugin)
201
202 if DEBUG:
203         def PluginComponent_removePlugin(self, plugin, *args, **kwargs):
204                 print("[PluginSort] Supposed to remove plugin: %s (%s, %s)." % (plugin.name, plugin.path, repr(plugin.where)))
205                 try:
206                         PluginComponent.pluginSort_baseRemovePlugin(self, plugin, *args, **kwargs)
207                 except ValueError as ve:
208                         revMap = reverse(WHEREMAP)
209                         print("-"*40)
210                         print("-"*40)
211                         print("-"*40)
212                         print("[PluginSort] pluginList: %s" % (repr([(x.name, x.path, repr([revMap[y] for y in x.where])) for x in self.pluginList]),))
213                         for w in plugin.where:
214                                 print("[PluginSort] plugins[%s]: %s" % (revMap[w], repr([(x.name, x.path, repr([revMap[y] for y in x.where])) for x in self.plugins[w]])))
215         PluginComponent.pluginSort_baseRemovePlugin = PluginComponent.removePlugin
216         PluginComponent.removePlugin = PluginComponent_removePlugin
217
218 OriginalPluginBrowser = PluginBrowser.PluginBrowser
219 class SortingPluginBrowser(OriginalPluginBrowser):
220         def __init__(self, *args, **kwargs):
221                 self.movemode = False
222                 self.selected = -1
223                 if 'where' in kwargs:
224                         self.where = kwargs['where']
225                         del kwargs['where']
226                 else:
227                         self.where = PluginDescriptor.WHERE_PLUGINMENU
228
229                 OriginalPluginBrowser.__init__(self, *args, **kwargs)
230                 self.skinName = ["SortingPluginBrowser", "PluginBrowser"] # XXX: fallback is evil because it makes moving more confusing :P
231
232                 self["pluginlist"] = MyPluginList(self.list)
233
234                 self["ColorActions"] = ActionMap(["ColorActions"],
235                         {
236                                 "green": self.toggleMoveMode,
237                         }, -2
238                 )
239
240                 self["WizardActions"] = ActionMap(["WizardActions"],
241                         {
242                                 "left": boundFunction(self.doMove, self["pluginlist"].pageUp),
243                                 "right": boundFunction(self.doMove, self["pluginlist"].pageDown),
244                                 "up": boundFunction(self.doMove, self["pluginlist"].up),
245                                 "down": boundFunction(self.doMove, self["pluginlist"].down),
246                         }, -2
247                 )
248
249                 if self.where != PluginDescriptor.WHERE_PLUGINMENU:
250                         self.toggleMoveMode()
251                         self.onShow.append(self.setCustomTitle)
252                 else:
253                         self["NumberActions"] = NumberActionMap(["NumberActions"],
254                                 {
255                                         "1": self.keyNumberGlobal,
256                                         "2": self.keyNumberGlobal,
257                                         "3": self.keyNumberGlobal,
258                                         "4": self.keyNumberGlobal,
259                                         "5": self.keyNumberGlobal,
260                                         "6": self.keyNumberGlobal,
261                                         "7": self.keyNumberGlobal,
262                                         "8": self.keyNumberGlobal,
263                                         "9": self.keyNumberGlobal,
264                                         "0": self.keyNumberGlobal,
265                                 }, -2
266                         )
267
268                         self["MenuActions"] = ActionMap(["MenuActions"],
269                                 {
270                                         "menu": self.openMenu,
271                                 }, -1
272                         )
273                 self.onFirstExecBegin.append(self.firstExec)
274
275         def firstExec(self):
276                 if config.plugins.pluginsort.show_help.value and pluginSortHelp:
277                         config.plugins.pluginsort.show_help.value = False
278                         config.plugins.pluginsort.show_help.save()
279                         pluginSortHelp.open(self.session)
280
281         def setCustomTitle(self):
282                 titleMap = {
283                         PluginDescriptor.WHERE_EXTENSIONSMENU: _("Sort Extensions"),
284                         PluginDescriptor.WHERE_MOVIELIST: _("Sort MovieList Extensions"),
285                         PluginDescriptor.WHERE_EVENTINFO: _("Sort EventInfo Extensions"),
286                 }
287                 title = titleMap.get(self.where, None)
288                 if title:
289                         self.setTitle(title)
290
291         def keyNumberGlobal(self, number):
292                 if not self.movemode:
293                         realnumber = (number - 1) % 10
294                         if realnumber < len(self.list):
295                                 self["pluginlist"].index = realnumber
296                                 self.save()
297
298         def close(self, *args, **kwargs):
299                 if self.movemode:
300                         self.toggleMoveMode()
301                 OriginalPluginBrowser.close(self, *args, **kwargs)
302
303         # copied from PluginBrowser because we redo pretty much anything :-)
304         def updateList(self):
305                 self.pluginlist = plugins.getPlugins(self.where)
306                 if self.where in (PluginDescriptor.WHERE_PLUGINMENU, PluginDescriptor.WHERE_EXTENSIONSMENU):
307                         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
308                 else: #if self.where in (PluginDescriptor.WHERE_EVENTINFO, PluginDescriptor.WHERE_MOVIELIST):
309                         self.pluginlist.sort(key=attrgetter('weight'))
310                 self.list = [MyPluginEntryComponent(plugin) for plugin in self.pluginlist]
311                 self["pluginlist"].list = self.list
312                 if self.where == PluginDescriptor.WHERE_PLUGINMENU:
313                         # TRANSLATORS: leaving this empty is encouraged to not cause any confusion (this string was taken directly from the standard PluginBrowser)
314                         self["red"].setText(_("Manage extensions"))
315                         self["green"].setText(_("Sort") if not self.movemode else _("End Sort"))
316                 else:
317                         self["red"].setText("")
318                         self["green"].setText(_("Sort") if not self.movemode else _("End Sort"))
319                         self["SoftwareActions"].setEnabled(False)
320
321         def doMove(self, func):
322                 if self.selected != -1:
323                         oldpos = self["pluginlist"].index
324                         func()
325                         entry = self.list.pop(oldpos)
326                         newpos = self["pluginlist"].index
327                         self.list.insert(newpos, entry)
328                         # XXX: modifyEntry is broken - I'd say a job well done :P
329                         #self["pluginlist"].modifyEntry(oldpos, self.list[oldpos])
330                         #self["pluginlist"].modifyEntry(newpos, self.list[newpos])
331                         self["pluginlist"].updateList(self.list)
332                 else:
333                         func()
334
335         def save(self):
336                 selected = self.selected
337                 if not self.movemode:
338                         OriginalPluginBrowser.save(self)
339                 elif selected != -1:
340                         Len = len(self.pluginlist)
341                         newpos = self["pluginlist"].index
342                         entry = self.pluginlist[selected]
343                         self.pluginlist.remove(entry)
344                         self.pluginlist.insert(newpos, entry)
345
346                         # we moved up, increase weight of plugins after us
347                         if newpos < selected:
348                                 print("[PluginSort]", entry.name, "moved up")
349                                 i = newpos + 1
350                                 # since we moved up, there has to be an entry after this one
351                                 diff = abs(self.pluginlist[i].weight - self.pluginlist[newpos].weight) + 1
352                                 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))
353                                 while i < Len:
354                                         if DEBUG: print("[PluginSort] INCREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff)
355                                         self.pluginlist[i].weight += diff
356                                         i += 1
357                         # we moved down, decrease weight of plugins before us
358                         elif newpos > selected:
359                                 print("[PluginSort]", entry.name, "moved down")
360                                 i = newpos - 1
361                                 # since we moved up, there has to be an entry before this one
362                                 diff = abs(self.pluginlist[newpos].weight - self.pluginlist[i].weight) + 1
363                                 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))
364                                 while i > -1:
365                                         if DEBUG: print("[PluginSort] DECREASE WEIGHT OF", self.pluginlist[i].name, "BY", diff)
366                                         self.pluginlist[i].weight -= diff
367                                         i -= 1
368                         else:
369                                 if DEBUG: print("[PluginSort]", entry.name, "did not move (%d to %d)?" % (selected, newpos))
370
371                         self.list = [MyPluginEntryComponent(plugin) for plugin in self.pluginlist]
372                         if DEBUG: print("[PluginSort] NEW LIST:", [(plugin.name, plugin.weight) for plugin in self.pluginlist])
373                         # XXX: modifyEntry is broken - I'd say a job well done :P
374                         #self["pluginlist"].modifyEntry(newpos, self.list[newpos])
375                         self["pluginlist"].updateList(self.list)
376                         self.selected = -1
377                 else:
378                         self.selected = self["pluginlist"].index
379                         self.list[self.selected] = SelectedPluginEntryComponent(self.pluginlist[self.selected])
380                         # XXX: modifyEntry is broken - I'd say a job well done :P
381                         #self["pluginlist"].modifyEntry(self.selected, self.list[self.selected])
382                         self["pluginlist"].updateList(self.list)
383         
384         def openMenu(self):
385                 if self.movemode:
386                         # TRANSLATORS: there is no need to translate this string, as it was reused from e2 core
387                         moveString = _("disable move mode")
388                 else:
389                         # TRANSLATORS: there is no need to translate this string, as it was reused from e2 core
390                         moveString = _("enable move mode")
391
392                 list = [
393                         (moveString, self.toggleMoveMode),
394                         (_("move extensions"), boundFunction(self.openMover, PluginDescriptor.WHERE_EXTENSIONSMENU)),
395                         (_("move movie extensions"), boundFunction(self.openMover, PluginDescriptor.WHERE_MOVIELIST)),
396                         (_("move event extensions"), boundFunction(self.openMover, PluginDescriptor.WHERE_EVENTINFO)),
397                 ]
398
399                 if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/PluginHider/plugin.py")):
400                         list.insert(0, (_("hide selected plugin"), self.hidePlugin))
401
402                 if pluginSortHelp:
403                         list.insert(0, (_("Help"), boundFunction(pluginSortHelp.open, self.session)))
404
405                 self.session.openWithCallback(
406                         self.menuCallback,
407                         ChoiceBox,
408                         list = list,
409                 )
410
411         def menuCallback(self, ret):
412                 ret and ret[1]()
413
414         def openMover(self, where):
415                 self.session.open(SortingPluginBrowser, where=where)
416
417         def hidePlugin(self):
418                 try:
419                         from Plugins.Extensions.PluginHider.plugin import hidePlugin
420                 except Exception as e:
421                         self.session.open(MessageBox, _("Unable to load PluginHider"), MessageBox.TYPE_ERROR)
422                 else:
423                         hidePlugin(self["pluginlist"].current[0])
424
425                         # we were actually in move mode, so save the current position
426                         if self.selected != -1:
427                                 self.save()
428                         self.updateList()
429
430         def toggleMoveMode(self):
431                 if self.movemode:
432                         if self.selected != -1:
433                                 self.save()
434                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
435                                 self["green"].setText(_("Sort"))
436
437                         for plugin in self.pluginlist:
438                                 pluginWeights.set(plugin)
439                         pluginWeights.save()
440
441                         # auto-close if not "PluginBrowser"
442                         if self.where != PluginDescriptor.WHERE_PLUGINMENU:
443                                 self.movemode = False
444                                 return self.close()
445                 else:
446                         if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
447                                 self["green"].setText(_("End Sort"))
448                 self.movemode = not self.movemode
449
450 def autostart(reason, *args, **kwargs):
451         if reason == 0:
452                 if hasattr(PluginComponent, 'pluginSort_baseAddPlugin'):
453                         print("[PluginSort] Something went wrong as our autostart handler was called multiple times for startup, printing traceback and ignoring.")
454                         import traceback, sys
455                         traceback.print_stack(limit=5, file=sys.stdout)
456                 else:
457                         PluginComponent.pluginSort_baseAddPlugin = PluginComponent.addPlugin
458                         PluginComponent.addPlugin = PluginComponent_addPlugin
459
460                         # we use a copy for installed plugins because we might change the 'where'-lists
461                         plugins.installedPluginList = plugins.pluginList[:]
462                         def PluginComponent__setattr__(self, key, value):
463                                 if key == 'installedPluginList': return
464                                 else: self.__dict__[key] = value
465                         PluginComponent.__setattr__ = PluginComponent__setattr__
466
467                         if hasattr(plugins, 'pluginHider_baseGetPlugins'):
468                                 pluginlist = plugins.pluginHider_baseGetPlugins([PluginDescriptor.WHERE_PLUGINMENU, PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_MOVIELIST, PluginDescriptor.WHERE_EVENTINFO])
469                         else:
470                                 pluginlist = plugins.getPlugins([PluginDescriptor.WHERE_PLUGINMENU, PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_MOVIELIST, PluginDescriptor.WHERE_EVENTINFO])
471
472                         # "fix" weight of plugins already added to list, future ones will be fixed automatically
473                         fixed = []
474                         for plugin in pluginlist:
475                                 if plugin in fixed: continue # skip double entries
476
477                                 # create individual entries for multiple wheres, this is potentially harmful!
478                                 if len(plugin.where) > 1:
479                                         # remove all entries except for a potential autostart one (highly unlikely to mix autostart with one of the above, but you never know :D)
480                                         if PluginDescriptor.WHERE_AUTOSTART in plugin.where:
481                                                 plugin.where.remove(PluginDescriptor.WHERE_AUTOSTART)
482                                                 hadAutostart = True
483                                         else:
484                                                 hadAutostart = False
485                                         plugins.removePlugin(plugin)
486                                         plugins.addPlugin(plugin) # this is our own addPlugin now, which automatically creates copies
487
488                                         # HACK: re-add autostart entry to internal list inside PluginComponent
489                                         if hadAutostart:
490                                                 plugin.where = [ PluginDescriptor.WHERE_AUTOSTART ]
491                                                 plugins.pluginList.append(plugin)
492
493                                 # we're keeping the entry, just fix the weight
494                                 else:
495                                         newWeight = pluginWeights.get(plugin)
496                                         print("[PluginSort] Fixing weight for %s (was %d, now %d)" % (plugin.name, plugin.weight, newWeight))
497                                         plugin.weight = newWeight
498
499                                 fixed.append(plugin)
500
501                         # let movieepg fix extensions list sorting if installed, else do this ourselves
502                         if not fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/MovieEPG/plugin.py")):
503                                 def InfoBarPlugins_getPluginList(self, *args, **kwargs):
504                                         l = InfoBarPlugins.pluginSort_baseGetPluginList(self, *args, **kwargs)
505                                         try:
506                                                 l.sort(key=lambda e: (e[0][1].args[0].weight, e[2]))
507                                         except Exception as e:
508                                                 print("[PluginSort] Failed to sort extensions", e)
509                                         return l
510
511                                 InfoBarPlugins.pluginSort_baseGetPluginList = InfoBarPlugins.getPluginList
512                                 InfoBarPlugins.getPluginList = InfoBarPlugins_getPluginList
513
514
515                         PluginBrowser.PluginBrowser = SortingPluginBrowser
516         else:
517                 if hasattr(PluginComponent, 'pluginSort_baseAddPlugin'):
518                         PluginComponent.addPlugin = PluginComponent.pluginSort_baseAddPlugin
519                         del PluginComponent.pluginSort_baseAddPlugin
520                 if hasattr(InfoBarPlugins, 'pluginSort_baseGetPluginList'):
521                         InfoBarPlugins.getPluginList = InfoBarPlugins.pluginSort_baseGetPluginList
522                         del InfoBarPlugins.pluginSort_baseGetPluginList
523                 PluginBrowser.PluginBrowser = OriginalPluginBrowser
524
525 #pragma mark - Help
526 try:
527         from Plugins.SystemPlugins.MPHelp import registerHelp, showHelp, XMLHelpReader
528         reader = XMLHelpReader(resolveFilename(SCOPE_PLUGINS, "Extensions/PluginSort/mphelp.xml"), translate=_)
529         pluginSortHelp = registerHelp(*reader)
530 except Exception as e:
531         print("[PluginSort] Unable to initialize MPHelp:", e,"- Help not available!")
532         pluginSortHelp = None
533 #pragma mark -
534
535 def Plugins(**kwargs):
536         return [
537                 PluginDescriptor(
538                         where=PluginDescriptor.WHERE_AUTOSTART,
539                         fnc=autostart,
540                         needsRestart=True,
541                 ),
542         ]