#!/usr/bin/python -ttu
# vim: ai ts=4 sts=4 et sw=4

#    Copyright (c) 2008 Intel Corporation
#
#    This program is free software; you can redistribute it and/or modify it
#    under the terms of the GNU General Public License as published by the Free
#    Software Foundation; version 2 of the License
#
#    This program is distributed in the hope that it will be useful, but
#    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
#    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
#    for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc., 59
#    Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import sys
import os
import gettext
import re
import subprocess
import time
import select

_ = gettext.lgettext
PROG_NAME = "image-writer"
COLOR_BLACK = "\033[00m"
COLOR_RED =   "\033[1;31m"
COLOR_BLUE =  "\033[1;34m"

def main():

    # Python version check
    if sys.hexversion < 0x2040000:
        print >> sys.stderr, _("Error: %s depends on a Python " \
                               "v2.4 or greater!") % (PROG_NAME)
        sys.exit(1)

    # Check for root priveleges
    if not are_we_root():
        print >> sys.stderr, _("%s must be run using " \
                               "root priveleges") % (PROG_NAME)
        sys.exit(1)

    # Parameters check
    if len(sys.argv) != 2:
        display_help()
        sys.exit(1)

    # Check image validity
    image = os.path.realpath(os.path.abspath(sys.argv[1]))
    if not is_valid_image(image):
        sys.exit(1)

    # Get USB drive
    usbd=get_usb_disk()
    if len(usbd)==0:
        sys.exit(1)

    # Unmount USB drive
    if not umount_device(usbd):
        sys.exit(1)

    # Warn of pending USB content doom
    msg = _("Warning:  The USB drive (%s) will be completely erased!") % usbd
    if not user_confirm (msg, COLOR_RED):
        sys.exit(1)

    # Write image to USB drive
    if not write_image_to_disk (image, usbd):
        sys.exit(1)

    print _("You may now boot your mobile device with this USB drive\n")
    return 0

#-----------------------------------------------------------------------

def display_help():
    print _("\nUsage: image-writer <full-path-to-image>\n")

# ret True = running as root
def are_we_root():
    if os.getuid() == 0:
        return True
    return False

def is_valid_image(image):
    if not os.path.isfile(image):
        print _("The image path does not point to a file")
        return False;
    if not (os.path.getsize(image) > (180*1024*1024)):
        msg = _("The file specified does not appear to be " \
                "large enough to be a valid image.")
        if not user_confirm (msg):
            return False;
    return True

def user_confirm(msg, text_color=COLOR_BLACK,
                 confirm=_(" Do you want to continue anyway? (y/n) ")):
    print _("%s%s%s") % (text_color, msg, COLOR_BLACK)
    confirm_msg = _("%s%s%s") % (text_color, confirm, COLOR_BLACK)
    name=""
    while name!="n" and name!="y":
        name = raw_input(confirm_msg)
        name = name.lower()
    if name=="n":
        return False;
    return True

def get_usb_disk():
    usbd = ""
    udisks = get_current_udisks();
    if len(udisks)==0:
        print _("No USB drives detected.")
        print _("Please insert a USB drive large enough to store the " \
                "given image")
        return ""
    elif len(udisks)==1:
        usbd=udisks[0]
    elif len(udisks)>0:
        print _("\nMultiple USB drives discovered:")
        i=1
        for usbd in udisks:
            print "%s) %s" % (i, usbd)
            i+=1
        sel_usb = ""
        while sel_usb not in range (1,len(udisks)+1):
            try:
                sel_usb = int(raw_input(" Select the USB " \
                                        "drive to use (1-%s): " % (len(udisks))))
            except ValueError:
                continue
        usbd = udisks[sel_usb-1]
    #print "Drive selected: '%s'" % (usbd)
    return usbd

def get_current_udisks():
    usb_devices = []
    dirname = os.path.realpath(os.path.abspath('/sys/bus/scsi'))
    work_list = get_usb_dir_tree(dirname)
    usb_list = [ x for x in work_list if re.search(r'usb', x) ]
    for filename in usb_list:
        #print _("usb_list file is %s") % filename
        device_dir = os.path.join('/sys/devices', filename)
        block_dir = os.path.join(device_dir, 'block')
        if (os.path.isdir(block_dir)) :
            for result in os.listdir(block_dir):
                #print result
                usb_dev = os.path.join('/dev',result)
                if os.path.exists(usb_dev):
                    usb_devices.append(usb_dev)
                break
        elif os.path.isdir(device_dir):
            for device_file in os.listdir(device_dir):
                full_path = os.path.join(device_dir, device_file)
                result = re.search(r'^block:(?P<dev>.*)', device_file)
                if result:
                    usb_dev = os.path.join('/dev', result.group('dev'))
                    if os.path.exists(usb_dev):
                        usb_devices.append(usb_dev)
                    break
    return usb_devices

def get_usb_dir_tree(dirname):
    file_set = set()
    for filename in os.listdir(dirname):
        full_path = os.path.join(dirname, filename)
        if os.path.islink(full_path):
            file_set.add(os.path.realpath(full_path))
        elif os.path.isdir(full_path):
            file_set.update(get_usb_dir_tree(full_path))
        else:
            file_set.add(full_path)
    return file_set

def umount_device(device):
    """umount a device if it is mounted"""
    dev_path = "%s1 " % os.path.realpath(os.path.abspath(device))

    if is_mounted(dev_path):
        print _("Unmounting %s...") % (dev_path) , 
        result = os.system("umount %s" % (dev_path))
        if result and is_mounted(dev_path):
            print _("Failed.\n%s could not be unmounted") % device
            return False
        print _("Done.")
    return True

def is_mounted (dirname):
    mount_file = open('/proc/mounts', 'r')
    for line in mount_file:
        if line.find(dirname) == 0:
            #return True  (causes failures later)
            return False
    return False

def write_image_to_disk (image_filename, usb_disk):
# image write timings
#   200 == 78s
#   422 == 165s
#  1036 == 413s
    size = os.path.getsize(image_filename)
    print _("Source:      %s") % image_filename
    print _("Size:        %s MB") % (int(size/(1024*1024)))
    print _("Destination: %s") % usb_disk
    #(tbd, get usb capacity): print _("Capacity:    %s") % ("1GB")

    # get time estimate based on image size (assume 10MB/sec)
    totsec = int(size / (5*1024*1024))
    min,sec = divmod(totsec, 60)

    msg = _("Writing image (Est. %smin %ssec)...    ") % (min,sec)
    print _("%s%s%s") % (COLOR_BLUE, msg, COLOR_BLACK) ,

    cmd = "dd bs=4096 if=%s of=%s" % (image_filename, usb_disk)

    p = subprocess.Popen(cmd.split(), 
                         stdout = subprocess.PIPE, 
                         stderr = subprocess.STDOUT, 
                         stdin = subprocess.PIPE, 
                         close_fds = True)

    # show progress (percentage ticking)
    poll = select.poll()
    poll.register(p.stdout, select.POLLIN)
    interval = (totsec / 100.0)
    perc = 0
    while p.poll() == None:
        print _("%s\b\b\b\b%2s%%%s") % (COLOR_BLUE, perc, COLOR_BLACK) ,
        time.sleep(interval)
        if perc < 99:
            perc+=1
    print _("%s\b\b\b\b100%%%s") % (COLOR_BLUE, COLOR_BLACK)

    # show output of command
    (sout,serr) = p.communicate()
    for line in sout.split('\n'):
        print line

    result = p.returncode
    if result != 0:
        print _("Error:  The image could not be written to the USB drive")
        return False
    print _("The image was successfully written to the USB drive")
    return True


if __name__ == '__main__':
    sys.exit(main())
