[NFIFlash] ask whether to run configuration backup after image download (add #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                 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.openDeviceBrowser()
543         
544         def openDeviceBrowser(self):
545                 self.session.openWithCallback(self.DeviceBrowserClosed, DeviceBrowser, None, showDirectories=True, showMountpoints=True, inhibitMounts=["/autofs/sr0/"])
546
547         def DeviceBrowserClosed(self, path):
548                 print "[DeviceBrowserClosed]", str(path)
549                 self.target_dir = path
550                 if path:
551                         self.ackDestinationDevice()
552                 else:
553                         self.keyRed()
554         
555         def ackDestinationDevice(self, device_description=None):
556                 if device_description == None:
557                         dev = self.target_dir
558                 else:
559                         dev = device_description
560                 message = _("Do you want to download the image to %s ?") % (dev)
561                 choices = [(_("Yes"), self.ackedDestination), (_("List of Storage Devices"),self.openDeviceBrowser), (_("Cancel"),self.keyRed)]
562                 self.session.openWithCallback(self.ackDestination_query, ChoiceBox, title=message, list=choices)
563
564         def ackDestination_query(self, choice):
565                 print "[ackDestination_query]", choice
566                 if isinstance(choice, tuple):
567                         choice[1]()
568                 else:
569                         self.keyRed()
570
571         def ackedDestination(self):
572                 print "[ackedDestination]", self.branch, self.target_dir
573                 self.container.setCWD("/mnt")
574                 if self.target_dir[:8] == "/autofs/":
575                         self.target_dir = "/dev/" + self.target_dir[8:-1]
576
577                 if self.branch == STICK_WIZARD:
578                         job = StickWizardJob(self.target_dir)
579                         job.afterEvent = "close"
580                         job_manager.AddJob(job)
581                         job_manager.failed_jobs = []
582                         self.session.openWithCallback(self.StickWizardCB, JobView, job, afterEventChangeable = False)
583
584                 elif self.branch != STICK_WIZARD:
585                         url = self.feedlists[self.branch][self.image_idx][1]
586                         filename = self.feedlists[self.branch][self.image_idx][0]
587                         print "[getImage] start downloading %s to %s" % (url, filename)
588                         if self.target_dir.startswith("/dev/"):
589                                 job = ImageDownloadJob(url, filename, self.target_dir, self.usbmountpoint)
590                         else:
591                                 job = ImageDownloadJob(url, filename, None, self.target_dir)
592                         job.afterEvent = "close"
593                         job_manager.AddJob(job)
594                         job_manager.failed_jobs = []
595                         self.session.openWithCallback(self.ImageDownloadCB, JobView, job, afterEventChangeable = False)
596
597         def StickWizardCB(self, ret=None):
598                 print "[StickWizardCB]", ret
599 #               print job_manager.active_jobs, job_manager.failed_jobs, job_manager.job_classes, job_manager.in_background, job_manager.active_job
600                 if len(job_manager.failed_jobs) == 0:
601                         self.session.open(MessageBox, _("The USB stick was prepared to be bootable.\nNow you can download an NFI image file!"), type = MessageBox.TYPE_INFO)
602                         if len(self.feedlists[ALLIMAGES]) == 0:
603                                 self.getFeed()
604                         else:
605                                 self.setMenu()
606                 else:
607                         self.umountCallback = self.checkUSBStick
608                         self.umount()
609
610         def ImageDownloadCB(self, ret):
611                 print "[ImageDownloadCB]", ret
612 #               print job_manager.active_jobs, job_manager.failed_jobs, job_manager.job_classes, job_manager.in_background, job_manager.active_job
613                 if len(job_manager.failed_jobs) == 0:
614                         self.session.openWithCallback(self.askBackupCB, MessageBox, _("The wizard can backup your current settings. Do you want to do a backup now?"), MessageBox.TYPE_YESNO)
615                 else:
616                         self.umountCallback = self.keyRed
617                         self.umount()
618
619         def askBackupCB(self, ret):
620                 if ret:
621                         from Plugins.SystemPlugins.SoftwareManager.BackupRestore import BackupScreen
622
623                         class USBBackupScreen(BackupScreen):
624                                 def __init__(self, session, usbmountpoint):
625                                         BackupScreen.__init__(self, session, runBackup = True)
626                                         self.backuppath = usbmountpoint
627                                         self.fullbackupfilename = self.backuppath + "/" + self.backupfile
628
629                         self.session.openWithCallback(self.showHint, USBBackupScreen, self.usbmountpoint)
630                 else:
631                         self.showHint()
632
633         def showHint(self, ret=None):
634                 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)
635                 self.umountCallback = self.keyRed
636                 self.umount()
637
638         def getFeed(self):
639                 self.feedDownloader15 = feedDownloader(self.feed_base, self.box, OE_vers="1.5")
640                 self.feedDownloader16 = feedDownloader(self.feed_base, self.box, OE_vers="1.6")
641                 self.feedlists = [[],[],[]]
642                 self.feedDownloader15.getList(self.gotFeed, self.feed_failed)
643                 self.feedDownloader16.getList(self.gotFeed, self.feed_failed)
644                 
645         def feed_failed(self, message=""):
646                 self["status"].text = _("Could not connect to Dreambox .NFI Image Feed Server:") + "\n" + str(message) + "\n" + _("Please check your network settings!")
647
648         def gotFeed(self, feedlist, OE_vers):
649                 print "[gotFeed]", OE_vers
650                 releaselist = []
651                 experimentallist = []
652                 
653                 for name, url in feedlist:
654                         if name.find("release") > -1:
655                                 releaselist.append((name, url))
656                         if name.find("experimental") > -1:
657                                 experimentallist.append((name, url))
658                         self.feedlists[ALLIMAGES].append((name, url))
659                 
660                 if OE_vers == "1.6":
661                         self.feedlists[RELEASE] = releaselist + self.feedlists[RELEASE]
662                         self.feedlists[EXPERIMENTAL] = experimentallist + self.feedlists[RELEASE]
663                 elif OE_vers == "1.5":
664                         self.feedlists[RELEASE] = self.feedlists[RELEASE] + releaselist
665                         self.feedlists[EXPERIMENTAL] = self.feedlists[EXPERIMENTAL] + experimentallist
666
667                 self.setMenu()
668
669         def checkUSBStick(self):
670                 self.target_dir = None
671                 allpartitions = [ (r.description, r.mountpoint) for r in harddiskmanager.getMountedPartitions(onlyhotplug = True)]
672                 print "[checkUSBStick] found partitions:", allpartitions
673                 usbpartition = []
674                 for x in allpartitions:
675                         print x, x[1] == '/', x[0].find("USB"), access(x[1], R_OK)
676                         if x[1] != '/' and x[0].find("USB") > -1:  # and access(x[1], R_OK) is True:
677                                 usbpartition.append(x)
678
679                 print usbpartition
680                 if len(usbpartition) == 1:
681                         self.target_dir = usbpartition[0][1]
682                         self.md5_passback = self.getFeed
683                         self.md5_failback = self.askStartWizard
684                         self.md5verify(self.stickimage_md5, self.target_dir)
685                 elif usbpartition == []:
686                         print "[NFIFlash] needs to create usb flasher stick first!"
687                         self.askStartWizard()
688                 else:
689                         self.askStartWizard()
690
691         def askStartWizard(self):
692                 self.branch = STICK_WIZARD
693                 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.
694 First, you need to prepare a USB stick so that it is bootable.
695 In the next step, an NFI image file can be downloaded from the update server and saved on the USB stick.
696 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!""")
697                 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/"])
698
699         def wizardDeviceBrowserClosed(self, path):
700                 print "[wizardDeviceBrowserClosed]", path
701                 self.target_dir = path
702                 if path:
703                         self.md5_passback = self.getFeed
704                         self.md5_failback = self.wizardQuery
705                         self.md5verify(self.stickimage_md5, self.target_dir)
706                 else:
707                         self.close()
708         
709         def wizardQuery(self):
710                 print "[wizardQuery]"
711                 description = self.target_dir
712                 for name, dev in self.getUSBPartitions():
713                         if dev == self.target_dir:
714                                 description = name
715                 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"
716                 message += _("The following device was found:\n\n%s\n\nDo you want to write the USB flasher to this stick?") % description
717                 choices = [(_("Yes"), self.ackedDestination), (_("List of Storage Devices"),self.askStartWizard), (_("Cancel"),self.close)]
718                 self.session.openWithCallback(self.ackDestination_query, ChoiceBox, title=message, list=choices)
719                         
720         def setMenu(self):
721                 self.menulist = []
722                 try:
723                         latest_release = "Release %s (Opendreambox 1.5)" % self.feedlists[RELEASE][0][0][-9:-4]
724                         self.menulist.append((RELEASE, _("Get latest release image"), _("Download %s from Server" ) % latest_release, None))
725                 except IndexError:
726                         pass
727
728                 try:
729                         dat = self.feedlists[EXPERIMENTAL][0][0][-12:-4]
730                         latest_experimental = "Experimental %s-%s-%s (Opendreambox 1.6)" % (dat[:4], dat[4:6], dat[6:])
731                         self.menulist.append((EXPERIMENTAL, _("Get latest experimental image"), _("Download %s from Server") % latest_experimental, None))
732                 except IndexError:
733                         pass
734
735                 self.menulist.append((ALLIMAGES, _("Choose image to download"), _("Select desired image from feed list" ), None))
736                 self.menulist.append((STICK_WIZARD, _("USB stick wizard"), _("Prepare another USB stick for image flashing" ), None))
737                 self["menu"].setList(self.menulist)
738                 self["status"].text = _("Currently installed image") + ": %s" % (about.getImageVersionString())
739                 self.branch = START
740                 self.updateButtons()
741
742         def nfo_download(self, branch, idx):
743                 nfourl = (self.feedlists[branch][idx][1])[:-4]+".nfo"
744                 self.nfofilename = (self.feedlists[branch][idx][0])[:-4]+".nfo"
745                 print "[check_for_NFO]", nfourl
746                 client.getPage(nfourl).addCallback(self.nfo_finished).addErrback(self.nfo_failed)
747
748         def nfo_failed(self, failure_instance):
749                 print "[nfo_failed] " + str(failure_instance)
750                 self["key_blue"].text = ""
751                 self.nfofilename = ""
752                 self.nfo = ""
753
754         def nfo_finished(self,nfodata=""):
755                 print "[nfo_finished] " + str(nfodata)
756                 self["key_blue"].text = _("Changelog viewer")
757                 self.nfo = nfodata
758
759         def md5verify(self, md5, path):
760                 cmd = "md5sum -c -s"
761                 print "[verify_md5]", md5, path, cmd
762                 self.container.setCWD(path)
763                 self.container.appClosed.append(self.md5finished)
764                 self.container.execute(cmd)
765                 self.container.write(md5)
766                 self.container.dataSent.append(self.md5ready)
767
768         def md5ready(self, retval):
769                 self.container.sendEOF()
770
771         def md5finished(self, retval):
772                 print "[md5finished]", str(retval)
773                 self.container.appClosed.remove(self.md5finished)
774                 self.container.dataSent.remove(self.md5ready)
775                 if retval==0:
776                         print "check passed! calling", repr(self.md5_passback)
777                         self.md5_passback()
778                 else:
779                         print "check failed! calling", repr(self.md5_failback)
780                         self.md5_failback()
781
782         def umount(self):
783                 cmd = "umount " + self.usbmountpoint
784                 print "[umount]", cmd
785                 self.container.setCWD('/')
786                 self.container.appClosed.append(self.umountFinished)
787                 self.container.execute(cmd)
788
789         def umountFinished(self, retval):
790                 print "[umountFinished]", str(retval)
791                 self.container.appClosed.remove(self.umountFinished)
792                 self.umountCallback()
793
794 def main(session, **kwargs):
795         session.open(NFIDownload,"/home/root")
796
797 def filescan_open(list, session, **kwargs):
798         dev = "/dev/" + (list[0].path).rsplit('/',1)[0][7:]
799         print "mounting device " + dev + " to /mnt/usb..."
800         system("mount "+dev+" /mnt/usb/ -o rw,sync")
801         session.open(NFIDownload,"/mnt/usb/")
802
803 def filescan(**kwargs):
804         from Components.Scanner import Scanner, ScanPath
805         return \
806                 Scanner(mimetypes = ["application/x-dream-image"], 
807                         paths_to_scan = 
808                                 [
809                                         ScanPath(path = "", with_subdirs = False),
810                                 ], 
811                         name = "NFI", 
812                         description = (_("Download .NFI-Files for USB-Flasher")+"..."),
813                         openfnc = filescan_open, )