Fix various typos, add .gitignore file
[snf-image-creator] / image_creator / dialog_main.py
index 1bc0ec5..2079269 100644 (file)
@@ -43,7 +43,10 @@ import optparse
 
 from image_creator import __version__ as version
 from image_creator.util import FatalError, MD5
+from image_creator.output import Output
+from image_creator.output.cli import SimpleOutput
 from image_creator.output.dialog import GaugeOutput, InfoBoxOutput
+from image_creator.output.composite import CompositeOutput
 from image_creator.disk import Disk
 from image_creator.os_type import os_cls
 from image_creator.kamaki_wrapper import Kamaki, ClientError
@@ -146,7 +149,7 @@ def update_background_title(session):
 
     MB = 2 ** 20
 
-    size = (dev.meta['SIZE'] + MB - 1) // MB
+    size = (dev.size + MB - 1) // MB
     shrinked = 'shrinked' in session and session['shrinked']
     postfix = " (shrinked)" if shrinked else ''
 
@@ -201,33 +204,37 @@ def extract_image(session):
                        "\n".join(overwrite), width=YESNO_WIDTH):
                 continue
 
-        out = GaugeOutput(d, "Image Extraction", "Extracting image...")
+        gauge = GaugeOutput(d, "Image Extraction", "Extracting image...")
         try:
             dev = session['device']
-            if "checksum" not in session:
-                size = dev.meta['SIZE']
-                md5 = MD5(out)
-                session['checksum'] = md5.compute(session['snapshot'], size)
-
-            # Extract image file
-            dev.out = out
-            dev.dump(path)
-
-            # Extract metadata file
-            out.output("Extracting metadata file...")
-            with open('%s.meta' % path, 'w') as f:
-                f.write(extract_metadata_string(session))
-            out.success('done')
-
-            # Extract md5sum file
-            out.output("Extracting md5sum file...")
-            md5str = "%s %s\n" % (session['checksum'], name)
-            with open('%s.md5sum' % path, 'w') as f:
-                f.write(md5str)
-            out.success("done")
-
+            out = dev.out
+            out.add(gauge)
+            try:
+                if "checksum" not in session:
+                    size = dev.size
+                    md5 = MD5(out)
+                    session['checksum'] = md5.compute(session['snapshot'],
+                                                      size)
+
+                # Extract image file
+                dev.dump(path)
+
+                # Extract metadata file
+                out.output("Extracting metadata file...")
+                with open('%s.meta' % path, 'w') as f:
+                    f.write(extract_metadata_string(session))
+                out.success('done')
+
+                # Extract md5sum file
+                out.output("Extracting md5sum file...")
+                md5str = "%s %s\n" % (session['checksum'], name)
+                with open('%s.md5sum' % path, 'w') as f:
+                    f.write(md5str)
+                out.success("done")
+            finally:
+                out.remove(gauge)
         finally:
-            out.cleanup()
+            gauge.cleanup()
         d.msgbox("Image file `%s' was successfully extracted!" % path,
                  width=MSGBOX_WIDTH)
         break
@@ -237,7 +244,8 @@ def extract_image(session):
 
 def upload_image(session):
     d = session["dialog"]
-    size = session['device'].meta['SIZE']
+    dev = session['device']
+    size = dev.size
 
     if "account" not in session:
         d.msgbox("You need to provide your ~okeanos login username before you "
@@ -262,43 +270,50 @@ def upload_image(session):
         if len(filename) == 0:
             d.msgbox("Filename cannot be empty", width=MSGBOX_WIDTH)
             continue
-
+        session['upload'] = filename
         break
 
-    out = GaugeOutput(d, "Image Upload", "Uploading...")
+    gauge = GaugeOutput(d, "Image Upload", "Uploading...")
     try:
-        if 'checksum' not in session:
-            md5 = MD5(out)
-            session['checksum'] = md5.compute(session['snapshot'], size)
-        kamaki = Kamaki(session['account'], session['token'], out)
+        out = dev.out
+        out.add(gauge)
         try:
-            # Upload image file
-            with open(session['snapshot'], 'rb') as f:
-                session["upload"] = kamaki.upload(f, size, filename,
-                                                  "Calculating block hashes",
-                                                  "Uploading missing blocks")
-            # Upload metadata file
-            out.output("Uploading metadata file...")
-            metastring = extract_metadata_string(session)
-            kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
-                          remote_path="%s.meta" % filename)
-            out.success("done")
-
-            # Upload md5sum file
-            out.output("Uploading md5sum file...")
-            md5str = "%s %s\n" % (session['checksum'], filename)
-            kamaki.upload(StringIO.StringIO(md5str), size=len(md5str),
-                          remote_path="%s.md5sum" % filename)
-            out.success("done")
-
-        except ClientError as e:
-            d.msgbox("Error in pithos+ client: %s" % e.message,
-                     title="Pithos+ Client Error", width=MSGBOX_WIDTH)
-            if 'upload' in session:
-                del session['upload']
-            return False
+            if 'checksum' not in session:
+                md5 = MD5(out)
+                session['checksum'] = md5.compute(session['snapshot'], size)
+
+            kamaki = Kamaki(session['account'], session['token'], out)
+            try:
+                # Upload image file
+                with open(session['snapshot'], 'rb') as f:
+                    session["pithos_uri"] = kamaki.upload(f, size, filename,
+                                                    "Calculating block hashes",
+                                                    "Uploading missing blocks")
+                # Upload metadata file
+                out.output("Uploading metadata file...")
+                metastring = extract_metadata_string(session)
+                kamaki.upload(StringIO.StringIO(metastring),
+                              size=len(metastring),
+                              remote_path="%s.meta" % filename)
+                out.success("done")
+
+                # Upload md5sum file
+                out.output("Uploading md5sum file...")
+                md5str = "%s %s\n" % (session['checksum'], filename)
+                kamaki.upload(StringIO.StringIO(md5str), size=len(md5str),
+                              remote_path="%s.md5sum" % filename)
+                out.success("done")
+
+            except ClientError as e:
+                d.msgbox("Error in pithos+ client: %s" % e.message,
+                         title="Pithos+ Client Error", width=MSGBOX_WIDTH)
+                if 'pithos_uri' in session:
+                    del session['pithos_uri']
+                return False
+        finally:
+            out.remove(gauge)
     finally:
-        out.cleanup()
+        gauge.cleanup()
 
     d.msgbox("Image file `%s' was successfully uploaded to pithos+" % filename,
              width=MSGBOX_WIDTH)
@@ -308,6 +323,7 @@ def upload_image(session):
 
 def register_image(session):
     d = session["dialog"]
+    dev = session['device']
 
     if "account" not in session:
         d.msgbox("You need to provide your ~okeanos login username before you "
@@ -321,7 +337,7 @@ def register_image(session):
                  width=MSGBOX_WIDTH)
         return False
 
-    if "upload" not in session:
+    if "pithos_uri" not in session:
         d.msgbox("You need to upload the image to pithos+ before you can "
                  "register it to cyclades", width=MSGBOX_WIDTH)
         return False
@@ -344,20 +360,25 @@ def register_image(session):
         for key in session['task_metadata']:
             metadata[key] = 'yes'
 
-    out = GaugeOutput(d, "Image Registration", "Registrating image...")
+    gauge = GaugeOutput(d, "Image Registration", "Registering image...")
     try:
-        out.output("Registring image to cyclades...")
+        out = dev.out
+        out.add(gauge)
         try:
-            kamaki = Kamaki(session['account'], session['token'], out)
-            kamaki.register(name, session['upload'], metadata)
-            out.success('done')
-        except ClientError as e:
-            d.msgbox("Error in pithos+ client: %s" % e.message)
-            return False
+            out.output("Registering image with Cyclades...")
+            try:
+                kamaki = Kamaki(session['account'], session['token'], out)
+                kamaki.register(name, session['pithos_uri'], metadata)
+                out.success('done')
+            except ClientError as e:
+                d.msgbox("Error in pithos+ client: %s" % e.message)
+                return False
+        finally:
+            out.remove(gauge)
     finally:
-        out.cleanup()
+        gauge.cleanup()
 
-    d.msgbox("Image `%s' was successfully registered to cyclades as `%s'" %
+    d.msgbox("Image `%s' was successfully registered with Cyclades as `%s'" %
              (session['upload'], name), width=MSGBOX_WIDTH)
     return True
 
@@ -419,7 +440,7 @@ def kamaki_menu(session):
                 del session["token"]
             else:
                 session["token"] = answer.strip()
-                Kamaki.save_token(session['account'])
+                Kamaki.save_token(session['token'])
                 default_item = "Upload"
         elif choice == "Upload":
             if upload_image(session):
@@ -639,9 +660,9 @@ def sysprep(session):
             index += 1
 
         (code, tags) = d.checklist(
-            "Please choose which system preperation tasks you would like to "
+            "Please choose which system preparation tasks you would like to "
             "run on the image. Press <Help> to see details about the system "
-            "preperation tasks.", title="Run system preperation tasks",
+            "preparation tasks.", title="Run system preparation tasks",
             choices=choices, width=70, ok_label="Run", help_button=1)
 
         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
@@ -657,30 +678,31 @@ def sysprep(session):
                 else:
                     image_os.disable_sysprep(syspreps[i])
 
-            out = InfoBoxOutput(d, "Image Configuration")
+            infobox = InfoBoxOutput(d, "Image Configuration")
             try:
                 dev = session['device']
-                dev.out = out
-                dev.mount(readonly=False)
+                dev.out.add(infobox)
                 try:
-                    # The checksum is invalid. We have mounted the image rw
-                    if 'checksum' in session:
-                        del session['checksum']
-
-                    # Monitor the metadata changes during syspreps
-                    with metadata_monitor(session, image_os.meta):
-                        image_os.out = out
-                        image_os.do_sysprep()
-                        image_os.out.finalize()
-
-                    # Disable syspreps that have ran
-                    for sysprep in session['exec_syspreps']:
-                        image_os.disable_sysprep(sysprep)
-
+                    dev.mount(readonly=False)
+                    try:
+                        # The checksum is invalid. We have mounted the image rw
+                        if 'checksum' in session:
+                            del session['checksum']
+
+                        # Monitor the metadata changes during syspreps
+                        with metadata_monitor(session, image_os.meta):
+                            image_os.do_sysprep()
+                            infobox.finalize()
+
+                        # Disable syspreps that have ran
+                        for sysprep in session['exec_syspreps']:
+                            image_os.disable_sysprep(sysprep)
+                    finally:
+                        dev.umount()
                 finally:
-                    dev.umount()
+                    dev.out.remove(infobox)
             finally:
-                out.cleanup()
+                infobox.cleanup()
             break
     return True
 
@@ -705,9 +727,13 @@ def shrink(session):
     if not d.yesno("%s\n\nDo you want to continue?" % msg, width=70,
                    height=12, title="Image Shrinking"):
         with metadata_monitor(session, dev.meta):
-            dev.out = InfoBoxOutput(d, "Image Shrinking", height=4)
-            dev.shrink()
-            dev.out.finalize()
+            infobox = InfoBoxOutput(d, "Image Shrinking", height=4)
+            dev.out.add(infobox)
+            try:
+                dev.shrink()
+                infobox.finalize()
+            finally:
+                dev.out.remove(infobox)
 
         session['shrinked'] = True
         update_background_title(session)
@@ -720,7 +746,7 @@ def shrink(session):
 def customization_menu(session):
     d = session['dialog']
 
-    choices = [("Sysprep", "Run various image preperation tasks"),
+    choices = [("Sysprep", "Run various image preparation tasks"),
                ("Shrink", "Shrink image"),
                ("View/Modify", "View/Modify image properties"),
                ("Delete", "Delete image properties"),
@@ -784,50 +810,16 @@ def main_menu(session):
             actions[choice](session)
 
 
-def select_file(d, media):
-    root = os.sep
-    while 1:
-        if media is not None:
-            if not os.path.exists(media):
-                d.msgbox("The file `%s' you choose does not exist." % media,
-                         width=MSGBOX_WIDTH)
-            else:
-                break
-
-        (code, media) = d.fselect(root, 10, 50,
-                                  title="Please select input media")
-        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
-            if confirm_exit(d, "You canceled the media selection dialog box."):
-                sys.exit(0)
-            else:
-                media = None
-                continue
-
-    return media
-
-
-def image_creator(d):
-
-    usage = "Usage: %prog [options] [<input_media>]"
-    parser = optparse.OptionParser(version=version, usage=usage)
-
-    options, args = parser.parse_args(sys.argv[1:])
-
-    if len(args) > 1:
-        parser.error("Wrong numver of arguments")
+def image_creator(d, media, out):
 
     d.setBackgroundTitle('snf-image-creator')
 
-    if os.geteuid() != 0:
-        raise FatalError("You must run %s as root" % parser.get_prog_name())
-
-    media = select_file(d, args[0] if len(args) == 1 else None)
-
-    out = GaugeOutput(d, "Initialization", "Initializing...")
+    gauge = GaugeOutput(d, "Initialization", "Initializing...")
+    out.add(gauge)
     disk = Disk(media, out)
 
     def signal_handler(signum, frame):
-        out.cleanup()
+        gauge.cleanup()
         disk.cleanup()
 
     signal.signal(signal.SIGINT, signal_handler)
@@ -849,12 +841,13 @@ def image_creator(d):
             metadata[str(key)] = str(value)
 
         out.success("done")
-        out.cleanup()
+        gauge.cleanup()
+        out.remove(gauge)
 
-        # Make sure the signal handler does not call out.cleanup again
+        # Make sure the signal handler does not call gauge.cleanup again
         def dummy(self):
             pass
-        out.cleanup = type(GaugeOutput.cleanup)(dummy, out, GaugeOutput)
+        gauge.cleanup = type(GaugeOutput.cleanup)(dummy, gauge, GaugeOutput)
 
         session = {"dialog": d,
                    "disk": disk,
@@ -864,15 +857,18 @@ def image_creator(d):
                    "metadata": metadata}
 
         msg = "snf-image-creator detected a %s system on the input media. " \
-              "Would you like to run a wizards to assists you through the " \
-              "image creation process?\n\nChoose <Yes> to run the wizard, " \
-              "<No> to run the snf-image-creator in expert mode or press " \
+              "Would you like to run a wizard to assist you through the " \
+              "image creation process?\n\nChoose <Wizard> to run the wizard," \
+              " <Expert> to run the snf-image-creator in expert mode or press " \
               "ESC to quit the program." \
-              % (dev.ostype if dev.ostype == dev.distro else "%s/%s" %
-                 (dev.distro, dev.ostype))
+              % (dev.ostype if dev.ostype == dev.distro else "%s (%s)" %
+                 (dev.ostype, dev.distro))
+
+        update_background_title(session)
 
         while True:
-            code = d.yesno(msg, width=YESNO_WIDTH, height=12)
+            code = d.yesno(msg, width=YESNO_WIDTH, height=12,
+                           yes_label="Wizard", no_label="Expert")
             if code == d.DIALOG_OK:
                 if wizard(session):
                     break
@@ -890,6 +886,28 @@ def image_creator(d):
     return 0
 
 
+def select_file(d, media):
+    root = os.sep
+    while 1:
+        if media is not None:
+            if not os.path.exists(media):
+                d.msgbox("The file `%s' you choose does not exist." % media,
+                         width=MSGBOX_WIDTH)
+            else:
+                break
+
+        (code, media) = d.fselect(root, 10, 50,
+                                  title="Please select input media")
+        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+            if confirm_exit(d, "You canceled the media selection dialog box."):
+                sys.exit(0)
+            else:
+                media = None
+                continue
+
+    return media
+
+
 def main():
 
     d = dialog.Dialog(dialog="dialog")
@@ -901,16 +919,60 @@ def main():
     dialog._common_args_syntax["extra_label"] = \
         lambda string: ("--extra-label", string)
 
-    while 1:
-        try:
+    # Allow yes-no label overwriting
+    dialog._common_args_syntax["yes_label"] = \
+        lambda string: ("--yes-label", string)
+
+    dialog._common_args_syntax["no_label"] = \
+        lambda string: ("--no-label", string)
+
+    usage = "Usage: %prog [options] [<input_media>]"
+    parser = optparse.OptionParser(version=version, usage=usage)
+    parser.add_option("-l", "--logfile", type="string", dest="logfile",
+                      default=None, help="log all messages to FILE",
+                      metavar="FILE")
+
+    options, args = parser.parse_args(sys.argv[1:])
+
+    if len(args) > 1:
+        parser.error("Wrong number of arguments")
+
+    d.setBackgroundTitle('snf-image-creator')
+
+    try:
+        if os.geteuid() != 0:
+            raise FatalError("You must run %s as root" % \
+                             parser.get_prog_name())
+
+        media = select_file(d, args[0] if len(args) == 1 else None)
+
+        logfile = None
+        if options.logfile is not None:
             try:
-                ret = image_creator(d)
-                sys.exit(ret)
-            except FatalError as e:
-                msg = textwrap.fill(str(e), width=70)
-                d.infobox(msg, width=INFOBOX_WIDTH, title="Fatal Error")
-                sys.exit(1)
-        except Reset:
-            continue
+                logfile = open(options.logfile, 'w')
+            except IOError as e:
+                raise FatalError(
+                    "Unable to open logfile `%s' for writing. Reason: %s" % \
+                    (options.logfile, e.strerror))
+        try:
+            log = SimpleOutput(False, logfile) if logfile is not None \
+                                               else Output()
+            while 1:
+                try:
+                    out = CompositeOutput([log])
+                    out.output("Starting %s v%s..." % \
+                               (parser.get_prog_name(), version))
+                    ret = image_creator(d, media, out)
+                    sys.exit(ret)
+                except Reset:
+                    log.output("Resetting everything...")
+                    continue
+        finally:
+            if logfile is not None:
+                logfile.close()
+    except FatalError as e:
+        msg = textwrap.fill(str(e), width=70)
+        d.infobox(msg, width=INFOBOX_WIDTH, title="Fatal Error")
+        sys.exit(1)
 
 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :