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