Put actual generation into separate thread.
[crypto-install.git] / crypto-install.py
index c5fe7db..eec72f7 100755 (executable)
@@ -2,7 +2,7 @@
 # -*- mode: python; coding: utf-8-unix; -*-
 
 
-import argparse, errno, os, re, readline, subprocess, sys, tempfile, textwrap
+import argparse, errno, os, re, readline, subprocess, sys, tempfile, textwrap, threading
 
 
 if sys.version_info[0] == 2:
@@ -10,6 +10,7 @@ if sys.version_info[0] == 2:
     from tkMessageBox import *
     from Tix import *
     from ScrolledText import *
+    from Queue import *
 
     def input_string (prompt=""):
         return raw_input (prompt)
@@ -18,6 +19,7 @@ elif sys.version_info[0] > 2:
     from tkinter.messagebox import *
     from tkinter.tix import *
     from tkinter.scrolledtext import *
+    from queue import *
 
     def input_string (prompt=""):
         return input (prompt)
@@ -178,13 +180,13 @@ def input_passphrase (arguments):
                                            stdout = subprocess.PIPE,
                                            stderr = subprocess.PIPE,
                                            env = batch_env)
-    (stdout, stderr) = passphrase_process.communicate (batch_passphrase)
+    (stdout, stderr) = passphrase_process.communicate (batch_passphrase.encode ("UTF-8"))
 
     if passphrase_process.returncode != 0:
         raise Exception ("Couldn't read passphrase.")
 
     for line in stdout.splitlines ():
-        if line.startswith ("D "):
+        if line.decode ("UTF-8").startswith ("D "):
             return line[2:]
 
     return ""
@@ -234,7 +236,7 @@ def gnupg_setup (arguments, name = None, email = None, comment = None):
         if comment != "":
             batch_key += "Name-Comment: {}\n".format (comment)
 
-        tmp.write (batch_key)
+        tmp.write (batch_key.encode ("UTF-8"))
         tmp.flush ()
 
         batch_env = dict (os.environ)
@@ -256,9 +258,9 @@ def gnupg_setup (arguments, name = None, email = None, comment = None):
 
         while True:
             line = gnupg_process.stdout.readline ()
-            if line == "":
+            if len (line) == 0:
                 break
-            sys.stdout.write (line)
+            sys.stdout.write (line.decode ("UTF-8"))
 
         if gnupg_process.returncode != 0:
             raise Exception ("Couldn't create GnuPG key.")
@@ -322,26 +324,36 @@ def openssh_setup (arguments, comment = None):
 
     while True:
         line = openssh_process.stdout.readline ()
-        if line == "":
+        if len (line) == 0:
             break
-        sys.stdout.write (line)
+        sys.stdout.write (line.decode ("UTF-8"))
 
     if openssh_process.returncode != 0:
         raise Exception ("Couldn't create OpenSSH key.")
 
 
 # http://www.blog.pythonlibrary.org/2014/07/14/tkinter-redirecting-stdout-stderr/
+# http://www.virtualroadside.com/blog/index.php/2012/11/10/glib-idle_add-for-tkinter-in-python/
 class RedirectText (object):
-    def __init__ (self, widget):
+    def __init__ (self, root, widget):
+        self.root = root
         self.widget = widget
 
+        self.queue = Queue ()
+
     def write (self, string):
         self.widget.insert (END, string)
 
+    def enqueue (self, value):
+        self.queue.put (value)
+        self.root.event_generate ("<<Idle>>", when = "tail")
+
 
 class CryptoInstallProgress (Toplevel):
-    def __init__ (self):
-        Toplevel.__init__ (self)
+    def __init__ (self, parent):
+        Toplevel.__init__ (self, parent)
+
+        self.parent = parent
 
         self.create_widgets ()
 
@@ -351,7 +363,7 @@ class CryptoInstallProgress (Toplevel):
         self.text = ScrolledText (self)
         self.text.pack (fill = BOTH, expand = True)
 
-        self.redirect = RedirectText (self.text)
+        self.redirect = RedirectText (self.parent, self.text)
 
         self._quit = Button (self)
         self._quit["text"] = "Quit"
@@ -568,29 +580,46 @@ class CryptoInstall (Tk):
         self.balloon.bind_widget (self.openssh, msg = msg)
         self.balloon.bind_widget (self.openssh_label, msg = msg)
 
-    def generate (self):
-        progress = CryptoInstallProgress ()
-
+    def generate_thread (self):
         stdout = sys.stdout
+
         try:
-            sys.stdout = progress.redirect
+            sys.stdout = self.progress.redirect
 
             # TODO: capture and show stdout and stderr
             if self.gnupg_var.get ():
+                # TODO: make get calls thread-safe
                 gnupg_setup (self.arguments,
                              self.name_var.get (),
                              self.email_var.get (),
                              self.comment_var.get ())
+                # TODO: put update into queue
                 self.update_widgets ()
 
             if self.openssh_var.get ():
                 comment = "{}@{}".format (self.user_var.get (),
                                           self.host_var.get ())
                 openssh_setup (self.arguments, comment)
+                # TODO: put update into queue
                 self.update_widgets ()
         finally:
             sys.stdout = stdout
 
+    def _on_idle ():
+        while True:
+            try:
+                self.progress.redirect.write (self.progress.queue.get (block = False))
+            except Empty:
+                break
+
+    def generate (self):
+        self.progress = CryptoInstallProgress (self)
+
+        self.bind ("<<Idle>>", self._on_idle)
+
+        thread = threading.Thread (target = self.generate_thread)
+        thread.start ()
+
 
 def main ():
     arguments = parse_arguments ()