NFIFlash: change display of feed list details
[enigma2.git] / lib / python / Plugins / SystemPlugins / NFIFlash / downloader.py
1 # -*- coding: utf-8 -*-
2 from Plugins.SystemPlugins.Hotplug.plugin import hotplugNotifier
3 from Screens.Screen import Screen
4 from Screens.MessageBox import MessageBox
5 from Screens.ChoiceBox import ChoiceBox
6 from Screens.HelpMenu import HelpableScreen
7 from Screens.TaskView import JobView
8 from Components.About import about
9 from Components.ActionMap import ActionMap
10 from Components.Sources.StaticText import StaticText
11 from Components.Sources.List import List
12 from Components.Label import Label
13 from Components.FileList import FileList
14 from Components.MenuList import MenuList
15 from Components.MultiContent import MultiContentEntryText
16 from Components.ScrollLabel import ScrollLabel
17 from Components.Harddisk import harddiskmanager
18 from Components.Task import Task, Job, job_manager, Condition
19 from Tools.Directories import fileExists, isMount
20 from Tools.HardwareInfo import HardwareInfo
21 from Tools.Downloader import downloadWithProgress
22 from enigma import eConsoleAppContainer, gFont, RT_HALIGN_LEFT, RT_HALIGN_CENTER, RT_VALIGN_CENTER, RT_WRAP, eTimer
23 from os import system, path, access, stat, remove, W_OK, R_OK
24 from twisted.web import client
25 from twisted.internet import reactor, defer
26 from twisted.python import failure
27 import re
28
29 class ImageDownloadJob(Job):
30         def __init__(self, url, filename, device=None, mountpoint="/"):
31                 Job.__init__(self, _("Download .NFI-Files for USB-Flasher"))
32                 if device:
33                         if isMount(mountpoint):
34                                 UmountTask(self, mountpoint)
35                         MountTask(self, device, mountpoint)
36                 ImageDownloadTask(self, url, mountpoint+filename)
37                 ImageDownloadTask(self, url[:-4]+".nfo", mountpoint+filename[:-4]+".nfo")
38                 if device:
39                         UmountTask(self, mountpoint)
40
41         def retry(self):
42                 self.tasks[0].args += self.tasks[0].retryargs
43                 Job.retry(self)
44
45 class MountTask(Task):
46         def __init__(self, job, device, mountpoint):
47                 Task.__init__(self, job, ("mount"))
48                 self.setTool("mount")
49                 options = "rw,sync"
50                 self.mountpoint = mountpoint
51                 self.args += [ device, mountpoint, "-o"+options ]
52                 self.weighting = 1
53
54         def processOutput(self, data):
55                 print "[MountTask] output:", data
56
57 class UmountTask(Task):
58         def __init__(self, job, mountpoint):
59                 Task.__init__(self, job, ("mount"))
60                 self.setTool("umount")
61                 self.args += [mountpoint]
62                 self.weighting = 1
63
64 class DownloaderPostcondition(Condition):
65         def check(self, task):
66                 return task.returncode == 0
67
68         def getErrorMessage(self, task):
69                 return self.error_message
70                 
71 class ImageDownloadTask(Task):
72         def __init__(self, job, url, path):
73                 Task.__init__(self, job, _("Downloading"))
74                 self.postconditions.append(DownloaderPostcondition())
75                 self.job = job
76                 self.url = url
77                 self.path = path
78                 self.error_message = ""
79                 self.last_recvbytes = 0
80                 self.error_message = None
81                 
82         def run(self, callback):
83                 self.callback = callback
84                 self.download = downloadWithProgress(self.url,self.path)
85                 self.download.addProgress(self.download_progress)
86                 self.download.start().addCallback(self.download_finished).addErrback(self.download_failed)
87                 print "[ImageDownloadTask] downloading", self.url, "to", self.path
88
89         def download_progress(self, recvbytes, totalbytes):
90                 #print "[update_progress] recvbytes=%d, totalbytes=%d" % (recvbytes, totalbytes)
91                 if ( recvbytes - self.last_recvbytes  ) > 10000: # anti-flicker
92                         self.progress = int(100*(float(recvbytes)/float(totalbytes)))
93                         self.name = _("Downloading") + ' ' + "%d of %d kBytes" % (recvbytes/1024, totalbytes/1024)
94                         self.last_recvbytes = recvbytes
95                 
96         def download_failed(self, failure_instance=None, error_message=""):
97                 self.error_message = error_message
98                 if error_message == "" and failure_instance is not None:
99                         self.error_message = failure_instance.getErrorMessage()
100                 print "[download_failed]", self.error_message
101                 Task.processFinished(self, 1)
102                 
103         def download_finished(self, string=""):
104                 print "[download_finished]", string
105                 Task.processFinished(self, 0)
106
107 class StickWizardJob(Job):
108         def __init__(self, path):
109                 Job.__init__(self, _("USB stick wizard"))
110                 self.path = path
111                 self.device = path
112                 while self.device[-1:] == "/" or self.device[-1:].isdigit():
113                         self.device = self.device[:-1]
114                                 
115                 box = HardwareInfo().get_device_name()
116                 url = "http://www.dreamboxupdate.com/download/opendreambox/dreambox-nfiflasher-%s.tar.bz2" % box
117                 self.downloadfilename = "/tmp/dreambox-nfiflasher-%s.tar.bz2" % box
118                 self.imagefilename = "/tmp/nfiflash_%s.img" % box
119                 #UmountTask(self, device)
120                 PartitionTask(self)
121                 ImageDownloadTask(self, url, self.downloadfilename)
122                 UnpackTask(self)
123                 CopyTask(self)
124
125 class PartitionTaskPostcondition(Condition):
126         def check(self, task):
127                 return task.returncode == 0
128
129         def getErrorMessage(self, task):
130                 return {
131                         task.ERROR_BLKRRPART: ("Device or resource busy"),
132                         task.ERROR_UNKNOWN: (task.errormsg)
133                 }[task.error]
134                 
135 class PartitionTask(Task):
136         ERROR_UNKNOWN, ERROR_BLKRRPART = range(2)
137         def __init__(self, job):
138                 Task.__init__(self, job, ("partitioning"))
139                 self.postconditions.append(PartitionTaskPostcondition())
140                 self.job = job          
141                 self.setTool("sfdisk")
142                 self.args += [self.job.device]
143                 self.weighting = 10
144                 self.initial_input = "0 - 0x6 *\n;\n;\n;\ny"
145                 self.errormsg = ""
146         
147         def run(self, callback):
148                 Task.run(self, callback)
149         
150         def processOutput(self, data):
151                 print "[PartitionTask] output:", data
152                 if data.startswith("BLKRRPART:"):
153                         self.error = self.ERROR_BLKRRPART
154                 else:
155                         self.error = self.ERROR_UNKNOWN
156                         self.errormsg = data
157
158 class UnpackTask(Task):
159         def __init__(self, job):
160                 Task.__init__(self, job, ("Unpacking USB flasher image..."))
161                 self.job = job
162                 self.setTool("tar")
163                 self.args += ["-xjvf", self.job.downloadfilename]
164                 self.weighting = 80
165                 self.end = 80
166                 self.delayTimer = eTimer()
167                 self.delayTimer.callback.append(self.progress_increment)
168         
169         def run(self, callback):
170                 Task.run(self, callback)
171                 self.delayTimer.start(950, False)
172                 
173         def progress_increment(self):
174                 self.progress += 1
175
176         def processOutput(self, data):
177                 print "[UnpackTask] output: \'%s\'" % data
178                 self.job.imagefilename = data
179         
180         def afterRun(self):
181                 self.delayTimer.callback.remove(self.progress_increment)
182
183 class CopyTask(Task):
184         def __init__(self, job):
185                 Task.__init__(self, job, ("Copying USB flasher boot image to stick..."))
186                 self.job = job
187                 self.setTool("dd")
188                 self.args += ["if=%s" % self.job.imagefilename, "of=%s1" % self.job.device]
189                 self.weighting = 20
190                 self.end = 20
191                 self.delayTimer = eTimer()
192                 self.delayTimer.callback.append(self.progress_increment)
193
194         def run(self, callback):
195                 Task.run(self, callback)
196                 self.delayTimer.start(100, False)
197                 
198         def progress_increment(self):
199                 self.progress += 1
200
201         def processOutput(self, data):
202                 print "[CopyTask] output:", data
203
204         def afterRun(self):
205                 self.delayTimer.callback.remove(self.progress_increment)
206
207 class NFOViewer(Screen):
208         skin = """
209                 <screen name="NFOViewer" position="center,center" size="610,410" title="Changelog viewer" >
210                         <widget name="changelog" position="10,10" size="590,380" font="Regular;16" />
211                 </screen>"""
212
213         def __init__(self, session, nfo):
214                 Screen.__init__(self, session)
215                 self["changelog"] = ScrollLabel(nfo)
216
217                 self["ViewerActions"] = ActionMap(["SetupActions", "ColorActions", "DirectionActions"],
218                         {
219                                 "green": self.exit,
220                                 "red": self.exit,
221                                 "ok": self.exit,
222                                 "cancel": self.exit,
223                                 "down": self.pageDown,
224                                 "up": self.pageUp
225                         })
226         def pageUp(self):
227                 self["changelog"].pageUp()
228
229         def pageDown(self):
230                 self["changelog"].pageDown()
231
232         def exit(self):
233                 self.close(False)
234
235 class feedDownloader:
236         def __init__(self, feed_base, box, OE_vers):
237                 print "[feedDownloader::init] feed_base=%s, box=%s" % (feed_base, box)
238                 self.feed_base = feed_base
239                 self.OE_vers = OE_vers
240                 self.box = box
241         
242         def getList(self, callback, errback):
243                 self.urlbase = "%s/%s/%s/images/" % (self.feed_base, self.OE_vers, self.box)
244                 print "[getList]", self.urlbase
245                 self.callback = callback
246                 self.errback = errback
247                 client.getPage(self.urlbase).addCallback(self.feed_finished).addErrback(self.feed_failed)
248
249         def feed_failed(self, failure_instance):
250                 print "[feed_failed]", str(failure_instance)
251                 self.errback(failure_instance.getErrorMessage())
252
253         def feed_finished(self, feedhtml):
254                 print "[feed_finished]"
255                 fileresultmask = re.compile("<a class=[\'\"]nfi[\'\"] href=[\'\"](?P<url>.*?)[\'\"]>(?P<name>.*?.nfi)</a>", re.DOTALL)
256                 searchresults = fileresultmask.finditer(feedhtml)
257                 fileresultlist = []
258                 if searchresults:
259                         for x in searchresults:
260                                 url = x.group("url")
261                                 if url[0:7] != "http://":
262                                         url = self.urlbase + x.group("url")
263                                 name = x.group("name")
264                                 entry = (name, url)
265                                 fileresultlist.append(entry)
266                 self.callback(fileresultlist, self.OE_vers)
267
268 class DeviceBrowser(Screen, HelpableScreen):
269         skin = """
270                 <screen name="DeviceBrowser" position="center,center" size="520,430" title="Please select target medium" >
271                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" alphatest="on" />
272                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" alphatest="on" />
273                         <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" font="Regular;20" halign="center" valign="center" backgroundColor="#9f1313" transparent="1" />
274                         <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" font="Regular;20" halign="center" valign="center" backgroundColor="#1f771f" transparent="1" />
275                         <widget source="message" render="Label" position="5,50" size="510,150" font="Regular;16" />
276                         <widget name="filelist" position="5,210" size="510,220" scrollbarMode="showOnDemand" />
277                 </screen>"""
278
279         def __init__(self, session, startdir, message="", showDirectories = True, showFiles = True, showMountpoints = True, matchingPattern = "", useServiceRef = False, inhibitDirs = False, inhibitMounts = False, isTop = False, enableWrapAround = False, additionalExtensions = None):
280                 Screen.__init__(self, session)
281
282                 HelpableScreen.__init__(self)
283
284                 self["key_red"] = StaticText(_("Cancel"))
285                 self["key_green"] = StaticText()
286                 self["message"] = StaticText(message)
287
288                 self.filelist = FileList(startdir, showDirectories = showDirectories, showFiles = showFiles, showMountpoints = showMountpoints, matchingPattern = matchingPattern, useServiceRef = useServiceRef, inhibitDirs = inhibitDirs, inhibitMounts = inhibitMounts, isTop = isTop, enableWrapAround = enableWrapAround, additionalExtensions = additionalExtensions)
289                 self["filelist"] = self.filelist
290
291                 self["FilelistActions"] = ActionMap(["SetupActions", "ColorActions"],
292                         {
293                                 "green": self.use,
294                                 "red": self.exit,
295                                 "ok": self.ok,
296                                 "cancel": self.exit
297                         })
298                 
299                 hotplugNotifier.append(self.hotplugCB)
300                 self.onShown.append(self.updateButton)
301                 self.onClose.append(self.removeHotplug)
302
303         def hotplugCB(self, dev, action):
304                 print "[hotplugCB]", dev, action
305                 self.updateButton()
306         
307         def updateButton(self):
308                 
309                 if self["filelist"].getFilename() or self["filelist"].getCurrentDirectory():
310                         self["key_green"].text = _("Use")
311                 else:
312                         self["key_green"].text = ""
313         
314         def removeHotplug(self):
315                 print "[removeHotplug]"
316                 hotplugNotifier.remove(self.hotplugCB)
317
318         def ok(self):
319                 if self.filelist.canDescent():
320                         if self["filelist"].showMountpoints == True and self["filelist"].showDirectories == False:
321                                 self.use()
322                         else:
323                                 self.filelist.descent()
324
325         def use(self):
326                 print "[use]", self["filelist"].getCurrentDirectory(), self["filelist"].getFilename()
327                 if self["filelist"].getCurrentDirectory() is not None:
328                         if self.filelist.canDescent() and self["filelist"].getFilename() and len(self["filelist"].getFilename()) > len(self["filelist"].getCurrentDirectory()):
329                                 self.filelist.descent()
330                         self.close(self["filelist"].getCurrentDirectory())
331                 elif self["filelist"].getFilename():
332                         self.close(self["filelist"].getFilename())
333
334         def exit(self):
335                 self.close(False)
336
337 (ALLIMAGES, RELEASE, EXPERIMENTAL, STICK_WIZARD, START) = range(5)
338
339 class NFIDownload(Screen):
340         skin = """
341         <screen name="NFIDownload" position="center,center" size="610,410" title="NFIDownload" >
342                 <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" alphatest="on" />
343                 <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" alphatest="on" />
344                 <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" alphatest="on" />
345                 <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" alphatest="on" />
346                 <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#9f1313" transparent="1" />
347                 <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
348                 <widget source="key_yellow" render="Label" position="280,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#a08500" transparent="1" />
349                 <widget source="key_blue" render="Label" position="420,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#18188b" transparent="1" />
350                 <ePixmap pixmap="skin_default/border_menu_350.png" position="5,50" zPosition="1" size="350,300" transparent="1" alphatest="on" />
351                 <widget source="menu" render="Listbox" position="15,60" size="330,290" scrollbarMode="showOnDemand">
352                         <convert type="TemplatedMultiContent">
353                                 {"templates": 
354                                         {"default": (25, [
355                                                 MultiContentEntryText(pos = (2, 2), size = (330, 24), flags = RT_HALIGN_LEFT, text = 1), # index 0 is the MenuText,
356                                         ], True, "showOnDemand")
357                                         },
358                                 "fonts": [gFont("Regular", 22)],
359                                 "itemHeight": 25
360                                 }
361                         </convert>
362                 </widget>
363                 <widget source="menu" render="Listbox" position="360,50" size="240,300" scrollbarMode="showNever" selectionDisabled="1">
364                         <convert type="TemplatedMultiContent">
365                                 {"templates":
366                                         {"default": (300, [
367                                                 MultiContentEntryText(pos = (2, 2), size = (240, 300), flags = RT_HALIGN_CENTER|RT_VALIGN_CENTER|RT_WRAP, text = 2), # index 2 is the Description,
368                                         ], False, "showNever")
369                                         },      
370                                 "fonts": [gFont("Regular", 22)],
371                                 "itemHeight": 300
372                                 }
373                         </convert>
374                 </widget>
375                 <widget source="status" render="Label" position="5,360" zPosition="10" size="600,50" halign="center" valign="center" font="Regular;22" transparent="1" shadowColor="black" shadowOffset="-1,-1" />
376         </screen>"""
377                 
378         def __init__(self, session, destdir=None):
379                 Screen.__init__(self, session)
380                 #self.skin_path = plugin_path
381                 #self.menu = args
382                 
383                 self.box = HardwareInfo().get_device_name()
384                 self.feed_base = "http://www.dreamboxupdate.com/opendreambox" #/1.5/%s/images/" % self.box      
385                 self.usbmountpoint = "/mnt/usb/"
386
387                 self.menulist = []
388
389                 self["menu"] = List(self.menulist)
390                 self["key_red"] = StaticText(_("Close"))
391                 self["key_green"] = StaticText()
392                 self["key_yellow"] = StaticText()
393                 self["key_blue"] = StaticText()
394
395                 self["status"] = StaticText(_("Please wait... Loading list..."))
396
397                 self["shortcuts"] = ActionMap(["OkCancelActions", "ColorActions", "ShortcutActions", "DirectionActions"],
398                 {
399                         "ok": self.keyOk,
400                         "green": self.keyOk,
401                         "red": self.keyRed,
402                         "blue": self.keyBlue,
403                         "up": self.keyUp,
404                         "upRepeated": self.keyUp,
405                         "downRepeated": self.keyDown,
406                         "down": self.keyDown,
407                         "cancel": self.close,
408                 }, -1)
409                 self.onShown.append(self.go)
410                 self.feedlists = [[],[],[]]
411                 self.branch = START
412                 self.container = eConsoleAppContainer()
413                 self.container.dataAvail.append(self.tool_avail)
414                 self.taskstring = ""
415                 self.image_idx = 0
416                 self.nfofilename = ""
417                 self.nfo = ""
418                 self.target_dir = None
419
420         def tool_avail(self, string):
421                 print "[tool_avail]" + string
422                 self.taskstring += string
423
424         def go(self):
425                 self.onShown.remove(self.go)
426                 self["menu"].index = 0
427                 url = "http://www.dreamboxupdate.com/download/opendreambox/dreambox-nfiflasher-%s-md5sums" % self.box
428                 client.getPage(url).addCallback(self.md5sums_finished).addErrback(self.feed_failed)
429
430         def md5sums_finished(self, data):
431                 print "[md5sums_finished]", data
432                 self.stickimage_md5 = data
433                 self.checkUSBStick()
434
435         def keyRed(self):
436                 if self.branch == START:
437                         self.close()
438                 else:
439                         self.branch = START
440                         self["menu"].setList(self.menulist)
441                 #elif self.branch == ALLIMAGES or self.branch == STICK_WIZARD:
442
443         def keyBlue(self):
444                 if self.nfo != "":
445                         self.session.open(NFOViewer, self.nfo)
446
447         def keyOk(self):
448                 print "[keyOk]", self["menu"].getCurrent()
449                 current = self["menu"].getCurrent()
450                 if current:
451                         if self.branch == START:
452                                 currentEntry = current[0]
453                                 if currentEntry == RELEASE:
454                                         self.image_idx = 0
455                                         self.branch = RELEASE
456                                         self.askDestination()
457                                 elif currentEntry == EXPERIMENTAL:
458                                         self.image_idx = 0
459                                         self.branch = EXPERIMENTAL
460                                         self.askDestination()
461                                 elif currentEntry == ALLIMAGES:
462                                         self.branch = ALLIMAGES
463                                         self.listImages()
464                                 elif currentEntry == STICK_WIZARD:
465                                         self.askStartWizard()
466                         elif self.branch == ALLIMAGES:
467                                 self.image_idx = self["menu"].getIndex()
468                                 self.askDestination()
469                 self.updateButtons()
470
471         def keyUp(self):
472                 self["menu"].selectPrevious()
473                 self.updateButtons()
474
475         def keyDown(self):
476                 self["menu"].selectNext()
477                 self.updateButtons()
478                 
479         def updateButtons(self):
480                 current = self["menu"].getCurrent()
481                 if current:
482                         if self.branch == START:
483                                 self["key_red"].text = _("Close")
484                                 currentEntry = current[0]
485                                 if currentEntry in (RELEASE, EXPERIMENTAL):
486                                         self.nfo_download(currentEntry, 0)
487                                         self["key_green"].text = _("Download")
488                                 else:
489                                         self.nfofilename = ""
490                                         self.nfo = ""
491                                         self["key_blue"].text = ""
492                                         self["key_green"].text = _("continue")
493
494                         elif self.branch == ALLIMAGES:
495                                 self["key_red"].text = _("Back")
496                                 self["key_green"].text = _("Download")
497                                 self.nfo_download(ALLIMAGES, self["menu"].getIndex())
498
499         def listImages(self):
500                 print "[listImages]"
501                 imagelist = []
502                 mask = re.compile("%s/(?P<OE_vers>1\.\d)/%s/images/(?P<branch>.*?)-%s_(?P<version>.*?).nfi" % (self.feed_base, self.box, self.box), re.DOTALL)
503                 for name, url in self.feedlists[ALLIMAGES]:
504                         result = mask.match(url)
505                         if result:
506                                 if result.group("version").startswith("20"):
507                                         version = ( result.group("version")[:4]+'-'+result.group("version")[4:6]+'-'+result.group("version")[6:8] )
508                                 else:
509                                         version = result.group("version")
510                                 description = "\nOpendreambox %s\n%s image\n%s\n" % (result.group("OE_vers"), result.group("branch"), version)
511                                 imagelist.append((url, name, _("Download %s from Server" ) % description, None))
512                 self["menu"].setList(imagelist)
513         
514         def getUSBPartitions(self):
515                 allpartitions = [ (r.description, r.mountpoint) for r in harddiskmanager.getMountedPartitions(onlyhotplug = True)]
516                 print "[getUSBPartitions]", allpartitions
517                 usbpartition = []
518                 for x in allpartitions:
519                         print x, x[1] == '/', x[0].find("USB"), access(x[1], R_OK)
520                         if x[1] != '/' and x[0].find("USB") > -1:  # and access(x[1], R_OK) is True:
521                                 usbpartition.append(x)
522                 return usbpartition
523                                 
524         def askDestination(self):
525                 usbpartition = self.getUSBPartitions()
526                 if len(usbpartition) == 1:
527                         self.target_dir = usbpartition[0][1]
528                         self.ackDestinationDevice(device_description=usbpartition[0][0])
529                 else:
530                         self.session.openWithCallback(self.DeviceBrowserClosed, DeviceBrowser, None, showDirectories=True, showMountpoints=True, inhibitMounts=["/autofs/sr0/"])
531
532         def DeviceBrowserClosed(self, path):
533                 print "[DeviceBrowserClosed]", str(path)
534                 self.target_dir = path
535                 if path:
536                         self.ackDestinationDevice()
537                 else:
538                         self.keyRed()
539         
540         def ackDestinationDevice(self, device_description=None):
541                 if device_description == None:
542                         dev = self.target_dir
543                 else:
544                         dev = device_description
545                 message = _("Do you want to download the image to %s ?") % (dev)
546                 choices = [(_("Yes"), self.ackedDestination), (_("List of Storage Devices"),self.askDestination), (_("Cancel"),self.keyRed)]
547                 self.session.openWithCallback(self.ackDestination_query, ChoiceBox, title=message, list=choices)
548
549         def ackDestination_query(self, choice):
550                 print "[ackDestination_query]", choice
551                 if isinstance(choice, tuple):
552                         choice[1]()
553                 else:
554                         self.keyRed()
555
556         def ackedDestination(self):
557                 print "[ackedDestination]", self.branch, self.target_dir, self.target_dir[8:]
558                 self.container.setCWD("/mnt")
559                 if self.target_dir[:8] == "/autofs/":
560                         self.target_dir = "/dev/" + self.target_dir[8:-1]
561
562                         if self.branch == STICK_WIZARD:
563                                 job = StickWizardJob(self.target_dir)
564                                 job.afterEvent = "close"
565                                 job_manager.AddJob(job)
566                                 job_manager.failed_jobs = []
567                                 self.session.openWithCallback(self.StickWizardCB, JobView, job, afterEventChangeable = False)
568
569                         elif self.branch != STICK_WIZARD:
570                                 url = self.feedlists[self.branch][self.image_idx][1]
571                                 filename = self.feedlists[self.branch][self.image_idx][0]
572                                 print "[getImage] start downloading %s to %s" % (url, filename)
573                                 job = ImageDownloadJob(url, filename, self.target_dir, self.usbmountpoint)
574                                 job.afterEvent = "close"
575                                 job_manager.AddJob(job)
576                                 job_manager.failed_jobs = []
577                                 self.session.openWithCallback(self.ImageDownloadCB, JobView, job, afterEventChangeable = False)
578
579         def StickWizardCB(self, ret=None):
580                 print "[StickWizardCB]", ret
581                 print job_manager.active_jobs, job_manager.failed_jobs, job_manager.job_classes, job_manager.in_background, job_manager.active_job
582                 if len(job_manager.failed_jobs) == 0:
583                         self.session.open(MessageBox, _("The USB stick was prepared to be bootable.\nNow you can download an NFI image file!"), type = MessageBox.TYPE_INFO)
584                         if len(self.feedlists[ALLIMAGES]) == 0:
585                                 self.getFeed()
586                         else:
587                                 self.setMenu()
588                 else:
589                         self.checkUSBStick()
590
591         def ImageDownloadCB(self, ret):
592                 print "[ImageDownloadCB]", ret
593                 print job_manager.active_jobs, job_manager.failed_jobs, job_manager.job_classes, job_manager.in_background, job_manager.active_job
594                 if len(job_manager.failed_jobs) == 0:
595                         self.session.open(MessageBox, _("To update your Dreambox firmware, please follow these steps:\n1) Turn off your box with the rear power switch and plug in the bootable USB stick.\n2) Turn mains back on and hold the DOWN button on the front panel pressed for 10 seconds.\n3) Wait for bootup and follow instructions of the wizard."), type = MessageBox.TYPE_INFO)
596                 else:
597                         self.branch = START
598
599         def getFeed(self):
600                 self.feedDownloader15 = feedDownloader(self.feed_base, self.box, OE_vers="1.5")
601                 self.feedDownloader16 = feedDownloader(self.feed_base, self.box, OE_vers="1.6")
602                 self.feedlists = [[],[],[]]
603                 self.feedDownloader15.getList(self.gotFeed, self.feed_failed)
604                 self.feedDownloader16.getList(self.gotFeed, self.feed_failed)
605                 
606         def feed_failed(self, message=""):
607                 self["status"].text = _("Could not connect to Dreambox .NFI Image Feed Server:") + "\n" + str(message) + "\n" + _("Please check your network settings!")
608
609         def gotFeed(self, feedlist, OE_vers):
610                 print "[gotFeed]", OE_vers
611                 releaselist = []
612                 experimentallist = []
613                 
614                 for name, url in feedlist:
615                         if name.find("release") > -1:
616                                 releaselist.append((name, url))
617                         if name.find("experimental") > -1:
618                                 experimentallist.append((name, url))
619                         self.feedlists[ALLIMAGES].append((name, url))
620                 
621                 if OE_vers == "1.6":
622                         self.feedlists[RELEASE] = releaselist + self.feedlists[RELEASE]
623                         self.feedlists[EXPERIMENTAL] = experimentallist + self.feedlists[RELEASE]
624                 elif OE_vers == "1.5":
625                         self.feedlists[RELEASE] = self.feedlists[RELEASE] + releaselist
626                         self.feedlists[EXPERIMENTAL] = self.feedlists[EXPERIMENTAL] + experimentallist
627
628                 self.setMenu()
629
630         def checkUSBStick(self):
631                 self.target_dir = None
632                 allpartitions = [ (r.description, r.mountpoint) for r in harddiskmanager.getMountedPartitions(onlyhotplug = True)]
633                 print "[checkUSBStick] found partitions:", allpartitions
634                 usbpartition = []
635                 for x in allpartitions:
636                         print x, x[1] == '/', x[0].find("USB"), access(x[1], R_OK)
637                         if x[1] != '/' and x[0].find("USB") > -1:  # and access(x[1], R_OK) is True:
638                                 usbpartition.append(x)
639
640                 print usbpartition
641                 if len(usbpartition) == 1:
642                         self.target_dir = usbpartition[0][1]
643                         self.md5_passback = self.getFeed
644                         self.md5_failback = self.askStartWizard
645                         self.md5verify(self.stickimage_md5, self.target_dir)
646                 elif usbpartition == []:
647                         print "[NFIFlash] needs to create usb flasher stick first!"
648                         self.askStartWizard()
649                 else:
650                         self.askStartWizard()
651
652         def askStartWizard(self):
653                 self.branch = STICK_WIZARD
654                 message = _("""This plugin creates a USB stick which can be used to update the firmware of your Dreambox in case it has no network connection or only WLAN access.
655 First, you need to prepare a USB stick so that it is bootable.
656 In the next step, an NFI image file can be downloaded from the update server and saved on the USB stick.
657 If you already have a prepared bootable USB stick, please insert it now. Otherwise plug in a USB stick with a minimum size of 64 MB!""")
658                 self.session.openWithCallback(self.wizardDeviceBrowserClosed, DeviceBrowser, None, message, showDirectories=True, showMountpoints=True, inhibitMounts=["/","/autofs/sr0/","/autofs/sda1/","/media/hdd/","/media/net/","/media/usb/","/media/dvd/"])
659
660         def wizardDeviceBrowserClosed(self, path):
661                 print "[wizardDeviceBrowserClosed]", path
662                 self.target_dir = path
663                 if path:
664                         self.md5_passback = self.getFeed
665                         self.md5_failback = self.wizardQuery
666                         self.md5verify(self.stickimage_md5, self.target_dir)
667                 else:
668                         self.close()
669         
670         def wizardQuery(self):
671                 print "[wizardQuery]"
672                 description = self.target_dir
673                 for name, dev in self.getUSBPartitions():
674                         if dev == self.target_dir:
675                                 description = name
676                 message = _("You have chosen to create a new .NFI flasher bootable USB stick. This will repartition the USB stick and therefore all data on it will be erased.") + "\n"
677                 message += _("The following device was found:\n\n%s\n\nDo you want to write the USB flasher to this stick?") % description
678                 choices = [(_("Yes"), self.ackedDestination), (_("List of Storage Devices"),self.askStartWizard), (_("Cancel"),self.close)]
679                 self.session.openWithCallback(self.ackDestination_query, ChoiceBox, title=message, list=choices)
680                         
681         def setMenu(self):
682                 self.menulist = []
683                 try:
684                         latest_release = "Release %s (Opendreambox 1.5)" % self.feedlists[RELEASE][0][0][-9:-4]
685                         self.menulist.append((RELEASE, _("Get latest release image"), _("Download %s from Server" ) % latest_release, None))
686                 except IndexError:
687                         pass
688
689                 try:
690                         dat = self.feedlists[EXPERIMENTAL][0][0][-12:-4]
691                         latest_experimental = "Experimental %s-%s-%s (Opendreambox 1.6)" % (dat[:4], dat[4:6], dat[6:])
692                         self.menulist.append((EXPERIMENTAL, _("Get latest experimental image"), _("Download %s from Server") % latest_experimental, None))
693                 except IndexError:
694                         pass
695
696                 self.menulist.append((ALLIMAGES, _("Choose image to download"), _("Select desired image from feed list" ), None))
697                 self.menulist.append((STICK_WIZARD, _("USB stick wizard"), _("Prepare another USB stick for image flashing" ), None))
698                 self["menu"].setList(self.menulist)
699                 self["status"].text = _("Currently installed image") + ": %s" % (about.getImageVersionString())
700                 self.branch = START
701                 self.updateButtons()
702
703         def nfo_download(self, branch, idx):
704                 nfourl = (self.feedlists[branch][idx][1])[:-4]+".nfo"
705                 self.nfofilename = (self.feedlists[branch][idx][0])[:-4]+".nfo"
706                 print "[check_for_NFO]", nfourl
707                 client.getPage(nfourl).addCallback(self.nfo_finished).addErrback(self.nfo_failed)
708
709         def nfo_failed(self, failure_instance):
710                 print "[nfo_failed] " + str(failure_instance)
711                 self["key_blue"].text = ""
712                 self.nfofilename = ""
713                 self.nfo = ""
714
715         def nfo_finished(self,nfodata=""):
716                 print "[nfo_finished] " + str(nfodata)
717                 self["key_blue"].text = _("Changelog viewer")
718                 self.nfo = nfodata
719
720         def md5verify(self, md5, path):
721                 cmd = "md5sum -c -s"
722                 print "[verify_md5]", md5, path, cmd
723                 self.container.setCWD(path)
724                 self.container.appClosed.append(self.md5finished)
725                 self.container.execute(cmd)
726                 self.container.write(md5)
727                 self.container.dataSent.append(self.md5ready)
728
729         def md5ready(self, retval):
730                 self.container.sendEOF()
731
732         def md5finished(self, retval):
733                 print "[md5finished]", str(retval)
734                 self.container.appClosed.remove(self.md5finished)
735                 self.container.dataSent.remove(self.md5ready)
736                 if retval==0:
737                         print "check passed! calling", repr(self.md5_passback)
738                         self.md5_passback()
739                 else:
740                         print "check failed! calling", repr(self.md5_failback)
741                         self.md5_failback()
742
743 def main(session, **kwargs):
744         session.open(NFIDownload,"/home/root")
745
746 def filescan_open(list, session, **kwargs):
747         dev = "/dev/" + (list[0].path).rsplit('/',1)[0][7:]
748         print "mounting device " + dev + " to /mnt/usb..."
749         system("mount "+dev+" /mnt/usb/ -o rw,sync")
750         session.open(NFIDownload,"/mnt/usb/")
751
752 def filescan(**kwargs):
753         from Components.Scanner import Scanner, ScanPath
754         return \
755                 Scanner(mimetypes = ["application/x-dream-image"], 
756                         paths_to_scan = 
757                                 [
758                                         ScanPath(path = "", with_subdirs = False),
759                                 ], 
760                         name = "NFI", 
761                         description = (_("Download .NFI-Files for USB-Flasher")+"..."),
762                         openfnc = filescan_open, )