Pluginbrowser - menu button close pluginbrowser and whole menu
[openblackhole/openblackhole-enigma2.git] / lib / python / Screens / PluginBrowser.py
1 from Screen import Screen
2 from enigma import eConsoleAppContainer, eDVBDB
3
4 from Components.ActionMap import ActionMap
5 from Components.config import config, ConfigSubsection, ConfigText
6 from Components.PluginComponent import plugins
7 from Components.PluginList import *
8 from Components.Label import Label
9 from Components.Language import language
10 from Components.Harddisk import harddiskmanager
11 from Components.Sources.StaticText import StaticText
12 from Components import Ipkg
13 from Screens.MessageBox import MessageBox
14 from Screens.ChoiceBox import ChoiceBox
15 from Screens.Console import Console
16 from Plugins.Plugin import PluginDescriptor
17 from Tools.Directories import resolveFilename, SCOPE_PLUGINS, SCOPE_CURRENT_SKIN
18 from Tools.LoadPixmap import LoadPixmap
19
20 from time import time
21 import os
22
23 language.addCallback(plugins.reloadPlugins)
24
25 config.misc.pluginbrowser = ConfigSubsection()
26 config.misc.pluginbrowser.plugin_order = ConfigText(default="")
27
28 class PluginBrowserSummary(Screen):
29         def __init__(self, session, parent):
30                 Screen.__init__(self, session, parent = parent)
31                 self["entry"] = StaticText("")
32                 self["desc"] = StaticText("")
33                 self.onShow.append(self.addWatcher)
34                 self.onHide.append(self.removeWatcher)
35
36         def addWatcher(self):
37                 self.parent.onChangedEntry.append(self.selectionChanged)
38                 self.parent.selectionChanged()
39
40         def removeWatcher(self):
41                 self.parent.onChangedEntry.remove(self.selectionChanged)
42
43         def selectionChanged(self, name, desc):
44                 self["entry"].text = name
45                 self["desc"].text = desc
46
47
48 class PluginBrowser(Screen):
49         def __init__(self, session):
50                 Screen.__init__(self, session)
51
52                 self.firsttime = True
53
54                 self["key_red"] = self["red"] = Label(_("Remove plugins"))
55                 self["key_green"] = self["green"] = Label(_("Download plugins"))
56
57                 self.list = []
58                 self["list"] = PluginList(self.list)
59
60                 self["actions"] = ActionMap(["WizardActions","MenuActions"],
61                 {
62                         "ok": self.save,
63                         "back": self.close,
64                         "menu": self.exit,
65                 })
66                 self["PluginDownloadActions"] = ActionMap(["ColorActions"],
67                 {
68                         "red": self.delete,
69                         "green": self.download
70                 })
71                 self["DirectionActions"] = ActionMap(["DirectionActions"],
72                 {
73                         "moveUp": self.moveUp,
74                         "moveDown": self.moveDown
75                 })
76
77                 self.onFirstExecBegin.append(self.checkWarnings)
78                 self.onShown.append(self.updateList)
79                 self.onChangedEntry = []
80                 self["list"].onSelectionChanged.append(self.selectionChanged)
81                 self.onLayoutFinish.append(self.saveListsize)
82
83         def exit(self):
84                 self.close(True)
85
86         def saveListsize(self):
87                 listsize = self["list"].instance.size()
88                 self.listWidth = listsize.width()
89                 self.listHeight = listsize.height()
90
91         def createSummary(self):
92                 return PluginBrowserSummary
93
94         def selectionChanged(self):
95                 item = self["list"].getCurrent()
96                 if item:
97                         p = item[0]
98                         name = p.name
99                         desc = p.description
100                 else:
101                         name = "-"
102                         desc = ""
103                 for cb in self.onChangedEntry:
104                         cb(name, desc)
105
106         def checkWarnings(self):
107                 if len(plugins.warnings):
108                         text = _("Some plugins are not available:\n")
109                         for (pluginname, error) in plugins.warnings:
110                                 text += _("%s (%s)\n") % (pluginname, error)
111                         plugins.resetWarnings()
112                         self.session.open(MessageBox, text = text, type = MessageBox.TYPE_WARNING)
113
114         def save(self):
115                 self.run()
116
117         def run(self):
118                 plugin = self["list"].l.getCurrentSelection()[0]
119                 plugin(session=self.session)
120
121         def moveUp(self):
122                 self.move(-1)
123
124         def moveDown(self):
125                 self.move(1)
126
127         def move(self, direction):
128                 if len(self.list) > 1:
129                         currentIndex = self["list"].getSelectionIndex()
130                         swapIndex = (currentIndex + direction) % len(self.list)
131                         if currentIndex == 0 and swapIndex != 1:
132                                 self.list = self.list[1:] + [self.list[0]]
133                         elif swapIndex == 0 and currentIndex != 1:
134                                 self.list = [self.list[-1]] + self.list[:-1]
135                         else:
136                                 self.list[currentIndex], self.list[swapIndex] = self.list[swapIndex], self.list[currentIndex]
137                         self["list"].l.setList(self.list)
138                         if direction == 1:
139                                 self["list"].down()
140                         else:
141                                 self["list"].up()
142                         plugin_order = []
143                         for x in self.list:
144                                 plugin_order.append(x[0].path[24:])
145                         config.misc.pluginbrowser.plugin_order.value = ",".join(plugin_order)
146                         config.misc.pluginbrowser.plugin_order.save()
147
148         def updateList(self):
149                 self.list = []
150                 pluginlist = plugins.getPlugins(PluginDescriptor.WHERE_PLUGINMENU)[:]
151                 for x in config.misc.pluginbrowser.plugin_order.value.split(","):
152                         plugin = list(plugin for plugin in pluginlist if plugin.path[24:] == x)
153                         if plugin:
154                                 self.list.append(PluginEntryComponent(plugin[0], self.listWidth))
155                                 pluginlist.remove(plugin[0])
156                 self.list = self.list + [PluginEntryComponent(plugin, self.listWidth) for plugin in pluginlist]
157                 self["list"].l.setList(self.list)
158
159         def delete(self):
160                 self.session.openWithCallback(self.PluginDownloadBrowserClosed, PluginDownloadBrowser, PluginDownloadBrowser.REMOVE)
161
162         def download(self):
163                 self.session.openWithCallback(self.PluginDownloadBrowserClosed, PluginDownloadBrowser, PluginDownloadBrowser.DOWNLOAD, self.firsttime)
164                 self.firsttime = False
165
166         def PluginDownloadBrowserClosed(self):
167                 self.updateList()
168                 self.checkWarnings()
169
170         def openExtensionmanager(self):
171                 if fileExists(resolveFilename(SCOPE_PLUGINS, "SystemPlugins/SoftwareManager/plugin.py")):
172                         try:
173                                 from Plugins.SystemPlugins.SoftwareManager.plugin import PluginManager
174                         except ImportError:
175                                 self.session.open(MessageBox, _("The software management extension is not installed!\nPlease install it."), type = MessageBox.TYPE_INFO,timeout = 10 )
176                         else:
177                                 self.session.openWithCallback(self.PluginDownloadBrowserClosed, PluginManager)
178
179 class PluginDownloadBrowser(Screen):
180         DOWNLOAD = 0
181         REMOVE = 1
182         PLUGIN_PREFIX = 'enigma2-plugin-'
183         lastDownloadDate = None
184
185         def __init__(self, session, type = 0, needupdate = True):
186                 Screen.__init__(self, session)
187
188                 self.type = type
189                 self.needupdate = needupdate
190
191                 self.container = eConsoleAppContainer()
192                 self.container.appClosed.append(self.runFinished)
193                 self.container.dataAvail.append(self.dataAvail)
194                 self.onLayoutFinish.append(self.startRun)
195                 self.onShown.append(self.setWindowTitle)
196
197                 self.list = []
198                 self["list"] = PluginList(self.list)
199                 self.pluginlist = []
200                 self.expanded = []
201                 self.installedplugins = []
202                 self.plugins_changed = False
203                 self.reload_settings = False
204                 self.check_settings = False
205                 self.install_settings_name = ''
206                 self.remove_settings_name = ''
207
208                 if self.type == self.DOWNLOAD:
209                         self["text"] = Label(_("Downloading plugin information. Please wait..."))
210                 elif self.type == self.REMOVE:
211                         self["text"] = Label(_("Getting plugin information. Please wait..."))
212
213                 self.run = 0
214                 self.remainingdata = ""
215                 self["actions"] = ActionMap(["WizardActions"],
216                 {
217                         "ok": self.go,
218                         "back": self.requestClose,
219                 })
220                 if os.path.isfile('/usr/bin/opkg'):
221                         self.ipkg = '/usr/bin/opkg'
222                         self.ipkg_install = self.ipkg + ' install'
223                         self.ipkg_remove =  self.ipkg + ' remove --autoremove'
224                 else:
225                         self.ipkg = 'ipkg'
226                         self.ipkg_install = 'ipkg install -force-defaults'
227                         self.ipkg_remove =  self.ipkg + ' remove'
228
229         def go(self):
230                 sel = self["list"].l.getCurrentSelection()
231
232                 if sel is None:
233                         return
234
235                 sel = sel[0]
236                 if isinstance(sel, str): # category
237                         if sel in self.expanded:
238                                 self.expanded.remove(sel)
239                         else:
240                                 self.expanded.append(sel)
241                         self.updateList()
242                 else:
243                         if self.type == self.DOWNLOAD:
244                                 self.session.openWithCallback(self.runInstall, MessageBox, _("Do you really want to download\nthe plugin \"%s\"?") % sel.name)
245                         elif self.type == self.REMOVE:
246                                 self.session.openWithCallback(self.runInstall, MessageBox, _("Do you really want to remove\nthe plugin \"%s\"?") % sel.name)
247
248         def requestClose(self):
249                 if self.plugins_changed:
250                         plugins.readPluginList(resolveFilename(SCOPE_PLUGINS))
251                 if self.reload_settings:
252                         self["text"].setText(_("Reloading bouquets and services..."))
253                         eDVBDB.getInstance().reloadBouquets()
254                         eDVBDB.getInstance().reloadServicelist()
255                 plugins.readPluginList(resolveFilename(SCOPE_PLUGINS))
256                 self.container.appClosed.remove(self.runFinished)
257                 self.container.dataAvail.remove(self.dataAvail)
258                 self.close()
259
260         def resetPostInstall(self):
261                 try:
262                         del self.postInstallCall
263                 except:
264                         pass
265
266         def installDestinationCallback(self, result):
267                 if result is not None:
268                         dest = result[1]
269                         if dest.startswith('/'):
270                                 # Custom install path, add it to the list too
271                                 dest = os.path.normpath(dest)
272                                 extra = '--add-dest %s:%s -d %s' % (dest,dest,dest)
273                                 Ipkg.opkgAddDestination(dest)
274                         else:
275                                 extra = '-d ' + dest
276                         self.doInstall(self.installFinished, self["list"].l.getCurrentSelection()[0].name + ' ' + extra)
277                 else:
278                         self.resetPostInstall()
279
280         def runInstall(self, val):
281                 if val:
282                         if self.type == self.DOWNLOAD:
283                                 if self["list"].l.getCurrentSelection()[0].name.startswith("picons-"):
284                                         supported_filesystems = frozenset(('ext4', 'ext3', 'ext2', 'reiser', 'reiser4', 'jffs2', 'ubifs', 'rootfs'))
285                                         candidates = []
286                                         import Components.Harddisk
287                                         mounts = Components.Harddisk.getProcMounts()
288                                         for partition in harddiskmanager.getMountedPartitions(False, mounts):
289                                                 if partition.filesystem(mounts) in supported_filesystems:
290                                                         candidates.append((partition.description, partition.mountpoint))
291                                         if candidates:
292                                                 from Components.Renderer import Picon
293                                                 self.postInstallCall = Picon.initPiconPaths
294                                                 self.session.openWithCallback(self.installDestinationCallback, ChoiceBox, title=_("Install picons on"), list=candidates)
295                                         return
296                                 self.install_settings_name = self["list"].l.getCurrentSelection()[0].name
297                                 if self["list"].l.getCurrentSelection()[0].name.startswith('settings-'):
298                                         self.check_settings = True
299                                         self.startIpkgListInstalled(self.PLUGIN_PREFIX + 'settings-*')
300                                 else:
301                                         self.runSettingsInstall()
302                         elif self.type == self.REMOVE:
303                                 self.doRemove(self.installFinished, self["list"].l.getCurrentSelection()[0].name)
304
305         def doRemove(self, callback, pkgname):
306                 self.session.openWithCallback(callback, Console, cmdlist = [self.ipkg_remove + Ipkg.opkgExtraDestinations() + " " + self.PLUGIN_PREFIX + pkgname, "sync"], closeOnSuccess = True)
307
308         def doInstall(self, callback, pkgname):
309                 self.session.openWithCallback(callback, Console, cmdlist = [self.ipkg_install + " " + self.PLUGIN_PREFIX + pkgname, "sync"], closeOnSuccess = True)
310
311         def runSettingsRemove(self, val):
312                 if val:
313                         self.doRemove(self.runSettingsInstall, self.remove_settings_name)
314
315         def runSettingsInstall(self):
316                 self.doInstall(self.installFinished, self.install_settings_name)
317
318         def setWindowTitle(self):
319                 if self.type == self.DOWNLOAD:
320                         self.setTitle(_("Downloadable new plugins"))
321                 elif self.type == self.REMOVE:
322                         self.setTitle(_("Remove plugins"))
323
324         def startIpkgListInstalled(self, pkgname = PLUGIN_PREFIX + '*'):
325                 self.container.execute(self.ipkg + Ipkg.opkgExtraDestinations() + " list_installed '%s'" % pkgname)
326
327         def startIpkgListAvailable(self):
328                 self.container.execute(self.ipkg + Ipkg.opkgExtraDestinations() + " list '" + self.PLUGIN_PREFIX + "*'")
329
330         def startRun(self):
331                 listsize = self["list"].instance.size()
332                 self["list"].instance.hide()
333                 self.listWidth = listsize.width()
334                 self.listHeight = listsize.height()
335                 if self.type == self.DOWNLOAD:
336                         if self.needupdate and not PluginDownloadBrowser.lastDownloadDate or (time() - PluginDownloadBrowser.lastDownloadDate) > 3600:
337                                 # Only update from internet once per hour
338                                 self.container.execute(self.ipkg + " update")
339                                 PluginDownloadBrowser.lastDownloadDate = time()
340                         else:
341                                 self.run = 1
342                                 self.startIpkgListInstalled()
343                 elif self.type == self.REMOVE:
344                         self.run = 1
345                         self.startIpkgListInstalled()
346
347         def installFinished(self):
348                 if hasattr(self, 'postInstallCall'):
349                         try:
350                                 self.postInstallCall()
351                         except Exception, ex:
352                                 print "[PluginBrowser] postInstallCall failed:", ex
353                         self.resetPostInstall()
354                 try:
355                         os.unlink('/tmp/opkg.conf')
356                 except:
357                         pass
358                 for plugin in self.pluginlist:
359                         if plugin[3] == self["list"].l.getCurrentSelection()[0].name:
360                                 self.pluginlist.remove(plugin)
361                                 break
362                 self.plugins_changed = True
363                 if self["list"].l.getCurrentSelection()[0].name.startswith("settings-"):
364                         self.reload_settings = True
365                 self.expanded = []
366                 self.updateList()
367                 self["list"].moveToIndex(0)
368
369         def runFinished(self, retval):
370                 if self.check_settings:
371                         self.check_settings = False
372                         self.runSettingsInstall()
373                         return
374                 self.remainingdata = ""
375                 if self.run == 0:
376                         self.run = 1
377                         if self.type == self.DOWNLOAD:
378                                 self.startIpkgListInstalled()
379                 elif self.run == 1 and self.type == self.DOWNLOAD:
380                         self.run = 2
381                         from Components import opkg
382                         pluginlist = []
383                         self.pluginlist = pluginlist
384                         for plugin in opkg.enumPlugins(self.PLUGIN_PREFIX):
385                                 if plugin[0] not in self.installedplugins:
386                                         pluginlist.append(plugin + (plugin[0][15:],))
387                         if pluginlist:
388                                 pluginlist.sort()
389                                 self.updateList()
390                                 self["list"].instance.show()
391                         else:
392                                 self["text"].setText(_("No new plugins found"))
393                 else:
394                         if self.pluginlist:
395                                 self.updateList()
396                                 self["list"].instance.show()
397                         else:
398                                 self["text"].setText(_("No new plugins found"))
399
400         def dataAvail(self, str):
401                 #prepend any remaining data from the previous call
402                 str = self.remainingdata + str
403                 #split in lines
404                 lines = str.split('\n')
405                 #'str' should end with '\n', so when splitting, the last line should be empty. If this is not the case, we received an incomplete line
406                 if len(lines[-1]):
407                         #remember this data for next time
408                         self.remainingdata = lines[-1]
409                         lines = lines[0:-1]
410                 else:
411                         self.remainingdata = ""
412
413                 if self.check_settings:
414                         self.check_settings = False
415                         self.remove_settings_name = str.split(' - ')[0].replace(self.PLUGIN_PREFIX, '')
416                         self.session.openWithCallback(self.runSettingsRemove, MessageBox, _('You already have a channel list installed,\nwould you like to remove\n"%s"?') % self.remove_settings_name)
417                         return
418
419                 if self.run == 1:
420                         for x in lines:
421                                 plugin = x.split(" - ", 2)
422                                 # 'opkg list_installed' only returns name + version, no description field
423                                 if len(plugin) >= 2:
424                                         if not plugin[0].endswith('-dev') and not plugin[0].endswith('-staticdev') and not plugin[0].endswith('-dbg') and not plugin[0].endswith('-doc') and not plugin[0].endswith('-src'):
425                                                 if plugin[0] not in self.installedplugins:
426                                                         if self.type == self.DOWNLOAD:
427                                                                 self.installedplugins.append(plugin[0])
428                                                         else:
429                                                                 if len(plugin) == 2:
430                                                                         plugin.append('')
431                                                                 plugin.append(plugin[0][15:])
432                                                                 self.pluginlist.append(plugin)
433
434         def updateList(self):
435                 list = []
436                 expandableIcon = LoadPixmap(resolveFilename(SCOPE_CURRENT_SKIN, "skin_default/expandable-plugins.png"))
437                 expandedIcon = LoadPixmap(resolveFilename(SCOPE_CURRENT_SKIN, "skin_default/expanded-plugins.png"))
438                 verticallineIcon = LoadPixmap(resolveFilename(SCOPE_CURRENT_SKIN, "skin_default/verticalline-plugins.png"))
439
440                 self.plugins = {}
441                 for x in self.pluginlist:
442                         split = x[3].split('-', 1)
443                         if len(split) < 2:
444                                 continue
445                         if not self.plugins.has_key(split[0]):
446                                 self.plugins[split[0]] = []
447
448                         self.plugins[split[0]].append((PluginDescriptor(name = x[3], description = x[2], icon = verticallineIcon), split[1], x[1]))
449
450                 for x in self.plugins.keys():
451                         if x in self.expanded:
452                                 list.append(PluginCategoryComponent(x, expandedIcon, self.listWidth))
453                                 list.extend([PluginDownloadComponent(plugin[0], plugin[1], plugin[2], self.listWidth) for plugin in self.plugins[x]])
454                         else:
455                                 list.append(PluginCategoryComponent(x, expandableIcon, self.listWidth))
456                 self.list = list
457                 self["list"].l.setList(list)