NFIFlash fixes (#480)
[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                 for name, url in self.feedlists[ALLIMAGES]:
503                         imagelist.append((url, name, _("Download %s from Server" ) % url, None))
504                 self["menu"].setList(imagelist)
505         
506         def getUSBPartitions(self):
507                 allpartitions = [ (r.description, r.mountpoint) for r in harddiskmanager.getMountedPartitions(onlyhotplug = True)]
508                 print "[getUSBPartitions]", allpartitions
509                 usbpartition = []
510                 for x in allpartitions:
511                         print x, x[1] == '/', x[0].find("USB"), access(x[1], R_OK)
512                         if x[1] != '/' and x[0].find("USB") > -1:  # and access(x[1], R_OK) is True:
513                                 usbpartition.append(x)
514                 return usbpartition
515                                 
516         def askDestination(self):
517                 usbpartition = self.getUSBPartitions()
518                 if len(usbpartition) == 1:
519                         self.target_dir = usbpartition[0][1]
520                         self.ackDestinationDevice(device_description=usbpartition[0][0])
521                 else:
522                         self.session.openWithCallback(self.DeviceBrowserClosed, DeviceBrowser, None, showDirectories=True, showMountpoints=True, inhibitMounts=["/autofs/sr0/"])
523
524         def DeviceBrowserClosed(self, path):
525                 print "[DeviceBrowserClosed]", str(path)
526                 self.target_dir = path
527                 if path:
528                         self.ackDestinationDevice()
529                 else:
530                         self.keyRed()
531         
532         def ackDestinationDevice(self, device_description=None):
533                 if device_description == None:
534                         dev = self.target_dir
535                 else:
536                         dev = device_description
537                 message = _("Do you want to download the image to %s ?") % (dev)
538                 choices = [(_("Yes"), self.ackedDestination), (_("List of Storage Devices"),self.askDestination), (_("Cancel"),self.keyRed)]
539                 self.session.openWithCallback(self.ackDestination_query, ChoiceBox, title=message, list=choices)
540
541         def ackDestination_query(self, choice):
542                 print "[ackDestination_query]", choice
543                 if isinstance(choice, tuple):
544                         choice[1]()
545                 else:
546                         self.keyRed()
547
548         def ackedDestination(self):
549                 print "[ackedDestination]", self.branch, self.target_dir, self.target_dir[8:]
550                 self.container.setCWD("/mnt")
551                 if self.target_dir[:8] == "/autofs/":
552                         self.target_dir = "/dev/" + self.target_dir[8:-1]
553
554                         if self.branch == STICK_WIZARD:
555                                 job = StickWizardJob(self.target_dir)
556                                 job.afterEvent = "close"
557                                 job_manager.AddJob(job)
558                                 job_manager.failed_jobs = []
559                                 self.session.openWithCallback(self.StickWizardCB, JobView, job, afterEventChangeable = False)
560
561                         elif self.branch != STICK_WIZARD:
562                                 url = self.feedlists[self.branch][self.image_idx][1]
563                                 filename = self.feedlists[self.branch][self.image_idx][0]
564                                 print "[getImage] start downloading %s to %s" % (url, filename)
565                                 job = ImageDownloadJob(url, filename, self.target_dir, self.usbmountpoint)
566                                 job.afterEvent = "close"
567                                 job_manager.AddJob(job)
568                                 job_manager.failed_jobs = []
569                                 self.session.openWithCallback(self.ImageDownloadCB, JobView, job, afterEventChangeable = False)
570
571         def StickWizardCB(self, ret=None):
572                 print "[StickWizardCB]", ret
573                 print job_manager.active_jobs, job_manager.failed_jobs, job_manager.job_classes, job_manager.in_background, job_manager.active_job
574                 if len(job_manager.failed_jobs) == 0:
575                         self.session.open(MessageBox, _("The USB stick was prepared to be bootable.\nNow you can download an NFI image file!"), type = MessageBox.TYPE_INFO)
576                         if len(self.feedlists[ALLIMAGES]) == 0:
577                                 self.getFeed()
578                         else:
579                                 self.setMenu()
580                 else:
581                         self.checkUSBStick()
582
583         def ImageDownloadCB(self, ret):
584                 print "[ImageDownloadCB]", ret
585                 print job_manager.active_jobs, job_manager.failed_jobs, job_manager.job_classes, job_manager.in_background, job_manager.active_job
586                 if len(job_manager.failed_jobs) == 0:
587                         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)
588                 else:
589                         self.branch = START
590
591         def getFeed(self):
592                 self.feedDownloader15 = feedDownloader(self.feed_base, self.box, OE_vers="1.5")
593                 self.feedDownloader16 = feedDownloader(self.feed_base, self.box, OE_vers="1.6")
594                 self.feedlists = [[],[],[]]
595                 self.feedDownloader15.getList(self.gotFeed, self.feed_failed)
596                 self.feedDownloader16.getList(self.gotFeed, self.feed_failed)
597                 
598         def feed_failed(self, message=""):
599                 self["status"].text = _("Could not connect to Dreambox .NFI Image Feed Server:") + "\n" + str(message) + "\n" + _("Please check your network settings!")
600
601         def gotFeed(self, feedlist, OE_vers):
602                 print "[gotFeed]", OE_vers
603                 releaselist = []
604                 experimentallist = []
605                 
606                 for name, url in feedlist:
607                         if name.find("release") > -1:
608                                 releaselist.append((name, url))
609                         if name.find("experimental") > -1:
610                                 experimentallist.append((name, url))
611                         self.feedlists[ALLIMAGES].append((name, url))
612                 
613                 if OE_vers == "1.6":
614                         self.feedlists[RELEASE] = releaselist + self.feedlists[RELEASE]
615                         self.feedlists[EXPERIMENTAL] = experimentallist + self.feedlists[RELEASE]
616                 elif OE_vers == "1.5":
617                         self.feedlists[RELEASE] = self.feedlists[RELEASE] + releaselist
618                         self.feedlists[EXPERIMENTAL] = self.feedlists[EXPERIMENTAL] + experimentallist
619
620                 self.setMenu()
621
622         def checkUSBStick(self):
623                 self.target_dir = None
624                 allpartitions = [ (r.description, r.mountpoint) for r in harddiskmanager.getMountedPartitions(onlyhotplug = True)]
625                 print "[checkUSBStick] found partitions:", allpartitions
626                 usbpartition = []
627                 for x in allpartitions:
628                         print x, x[1] == '/', x[0].find("USB"), access(x[1], R_OK)
629                         if x[1] != '/' and x[0].find("USB") > -1:  # and access(x[1], R_OK) is True:
630                                 usbpartition.append(x)
631
632                 print usbpartition
633                 if len(usbpartition) == 1:
634                         self.target_dir = usbpartition[0][1]
635                         self.md5_passback = self.getFeed
636                         self.md5_failback = self.askStartWizard
637                         self.md5verify(self.stickimage_md5, self.target_dir)
638                 elif usbpartition == []:
639                         print "[NFIFlash] needs to create usb flasher stick first!"
640                         self.askStartWizard()
641                 else:
642                         self.askStartWizard()
643
644         def askStartWizard(self):
645                 self.branch = STICK_WIZARD
646                 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.
647 First, you need to prepare a USB stick so that it is bootable.
648 In the next step, an NFI image file can be downloaded from the update server and saved on the USB stick.
649 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!""")
650                 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/"])
651
652         def wizardDeviceBrowserClosed(self, path):
653                 print "[wizardDeviceBrowserClosed]", path
654                 self.target_dir = path
655                 if path:
656                         self.md5_passback = self.getFeed
657                         self.md5_failback = self.wizardQuery
658                         self.md5verify(self.stickimage_md5, self.target_dir)
659                 else:
660                         self.close()
661         
662         def wizardQuery(self):
663                 print "[wizardQuery]"
664                 description = self.target_dir
665                 for name, dev in self.getUSBPartitions():
666                         if dev == self.target_dir:
667                                 description = name
668                 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"
669                 message += _("The following device was found:\n\n%s\n\nDo you want to write the USB flasher to this stick?") % description
670                 choices = [(_("Yes"), self.ackedDestination), (_("List of Storage Devices"),self.askStartWizard), (_("Cancel"),self.close)]
671                 self.session.openWithCallback(self.ackDestination_query, ChoiceBox, title=message, list=choices)
672                         
673         def setMenu(self):
674                 self.menulist = []
675                 try:
676                         latest_release = "Release %s (Opendreambox 1.5)" % self.feedlists[RELEASE][0][0][-9:-4]
677                         self.menulist.append((RELEASE, _("Get latest release image"), _("Download %s from Server" ) % latest_release, None))
678                 except IndexError:
679                         pass
680
681                 try:
682                         dat = self.feedlists[EXPERIMENTAL][0][0][-12:-4]
683                         latest_experimental = "Experimental %s-%s-%s (Opendreambox 1.6)" % (dat[:4], dat[4:6], dat[6:])
684                         self.menulist.append((EXPERIMENTAL, _("Get latest experimental image"), _("Download %s from Server") % latest_experimental, None))
685                 except IndexError:
686                         pass
687
688                 self.menulist.append((ALLIMAGES, _("Choose image to download"), _("Select desired image from feed list" ), None))
689                 self.menulist.append((STICK_WIZARD, _("USB stick wizard"), _("Prepare another USB stick for image flashing" ), None))
690                 self["menu"].setList(self.menulist)
691                 self["status"].text = _("Currently installed image") + ": %s" % (about.getImageVersionString())
692                 self.branch = START
693                 self.updateButtons()
694
695         def nfo_download(self, branch, idx):
696                 nfourl = (self.feedlists[branch][idx][1])[:-4]+".nfo"
697                 self.nfofilename = (self.feedlists[branch][idx][0])[:-4]+".nfo"
698                 print "[check_for_NFO]", nfourl
699                 client.getPage(nfourl).addCallback(self.nfo_finished).addErrback(self.nfo_failed)
700
701         def nfo_failed(self, failure_instance):
702                 print "[nfo_failed] " + str(failure_instance)
703                 self["key_blue"].text = ""
704                 self.nfofilename = ""
705                 self.nfo = ""
706
707         def nfo_finished(self,nfodata=""):
708                 print "[nfo_finished] " + str(nfodata)
709                 self["key_blue"].text = _("Changelog viewer")
710                 self.nfo = nfodata
711
712         def md5verify(self, md5, path):
713                 cmd = "md5sum -c -s"
714                 print "[verify_md5]", md5, path, cmd
715                 self.container.setCWD(path)
716                 self.container.appClosed.append(self.md5finished)
717                 self.container.execute(cmd)
718                 self.container.write(md5)
719                 self.container.dataSent.append(self.md5ready)
720
721         def md5ready(self, retval):
722                 self.container.sendEOF()
723
724         def md5finished(self, retval):
725                 print "[md5finished]", str(retval)
726                 self.container.appClosed.remove(self.md5finished)
727                 self.container.dataSent.remove(self.md5ready)
728                 if retval==0:
729                         print "check passed! calling", repr(self.md5_passback)
730                         self.md5_passback()
731                 else:
732                         print "check failed! calling", repr(self.md5_failback)
733                         self.md5_failback()
734
735 def main(session, **kwargs):
736         session.open(NFIDownload,"/home/root")
737
738 def filescan_open(list, session, **kwargs):
739         dev = "/dev/" + (list[0].path).rsplit('/',1)[0][7:]
740         print "mounting device " + dev + " to /mnt/usb..."
741         system("mount "+dev+" /mnt/usb/ -o rw,sync")
742         session.open(NFIDownload,"/mnt/usb/")
743
744 def filescan(**kwargs):
745         from Components.Scanner import Scanner, ScanPath
746         return \
747                 Scanner(mimetypes = ["application/x-dream-image"], 
748                         paths_to_scan = 
749                                 [
750                                         ScanPath(path = "", with_subdirs = False),
751                                 ], 
752                         name = "NFI", 
753                         description = (_("Download .NFI-Files for USB-Flasher")+"..."),
754                         openfnc = filescan_open, )