Index: src/mlx/config.py
===================================================================
--- src/mlx/config.py	(revision 36)
+++ src/mlx/config.py	(revision 36)
@@ -0,0 +1,14 @@
+# Configuration and related stuff
+
+#-------------------------------------------------------------------------------
+
+class Config(object):
+    """Our configuration."""
+    def __init__(self):
+        """Construct the configuration with default values."""
+        self.autoUpdate = True
+        
+        self.updateURL = \
+            "http://mlx.varadiistvan.hu/update"
+
+#-------------------------------------------------------------------------------
Index: src/mlx/const.py
===================================================================
--- src/mlx/const.py	(revision 34)
+++ src/mlx/const.py	(revision 36)
@@ -4,5 +4,5 @@
 
 # The version of the program
-VERSION="0.003"
+VERSION="0.004"
 
 #-------------------------------------------------------------------------------
Index: src/mlx/gui/gui.py
===================================================================
--- src/mlx/gui/gui.py	(revision 35)
+++ src/mlx/gui/gui.py	(revision 36)
@@ -3,4 +3,5 @@
 from statusicon import StatusIcon
 from statusbar import Statusbar
+from update import Updater
 from mlx.gui.common import *
 
@@ -31,8 +32,10 @@
 class GUI(fs.ConnectionListener):
     """The main GUI class."""
-    def __init__(self):
+    def __init__(self, programDirectory, config):
         """Construct the GUI."""
         gobject.threads_init()
 
+        self._programDirectory = programDirectory
+        self._config = config
         self._connecting = False
         self._connected = False
@@ -40,17 +43,18 @@
         self._flight = None
         self._simulator = None
+        self._stdioAfterNewLine = True
 
     def build(self, iconDirectory):
         """Build the GUI."""
         
-        win = gtk.Window()
-        win.set_title("MAVA Logger X " + const.VERSION)
-        win.set_icon_from_file("logo.ico")
-        win.connect("delete-event",
-                    lambda a, b: self.hideMainWindow())
-        win.connect("window-state-event", self._handleMainWindowState)
+        window = gtk.Window()
+        window.set_title("MAVA Logger X " + const.VERSION)
+        window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
+        window.connect("delete-event",
+                       lambda a, b: self.hideMainWindow())
+        window.connect("window-state-event", self._handleMainWindowState)
 
         mainVBox = gtk.VBox()
-        win.add(mainVBox)
+        window.add(mainVBox)
 
         setupFrame = self._buildSetupFrame()
@@ -71,7 +75,7 @@
         mainVBox.pack_start(self._statusbar, False, False, 0)
 
-        win.show_all()        
-
-        self._mainWindow = win
+        window.show_all()        
+
+        self._mainWindow = window
 
         self._statusIcon = StatusIcon(iconDirectory, self)
@@ -79,5 +83,12 @@
     def run(self):
         """Run the GUI."""
+        if self._config.autoUpdate:
+            self._updater = Updater(self._programDirectory,
+                                    self._config.updateURL,
+                                    self._mainWindow)
+            self._updater.start()
+
         gtk.main()
+
         if self._flight is not None:
             simulator = self._flight.simulator
@@ -173,4 +184,29 @@
         else:
             self.showMainWindow()
+
+    def writeStdIO(self, text):
+        """Write the given text into standard I/O log."""
+        gobject.idle_add(self._writeStdIO, text)
+
+    def _writeStdIO(self, text):
+        """Perform the real writing."""
+        lines = text.splitlines()
+        if text[-1]=="\n":
+            text = ""
+        else:
+            text = lines[-1]
+            lines = lines[:-1]
+            
+        for line in lines:
+            if self._stdioAfterNewLine:
+                line = "[STDIO] " + line
+            self._writeLog(line + "\n")
+            self._stdioAfterNewLine = True
+
+        if text:
+            if self._stdioAfterNewLine:
+                text = "[STDIO] " + text
+            self._writeLog(text)
+            self._stdioAfterNewLine = False
             
     def _connectToggled(self, button):
Index: src/mlx/gui/update.py
===================================================================
--- src/mlx/gui/update.py	(revision 36)
+++ src/mlx/gui/update.py	(revision 36)
@@ -0,0 +1,281 @@
+# The GUI part of the update
+
+#-------------------------------------------------------------------------------
+
+from mlx.gui.common import *
+
+from mlx.update import update, restart
+
+import mlx.const as const
+
+import threading
+import os
+import sys
+
+#-------------------------------------------------------------------------------
+
+class Updater(threading.Thread):
+    """The updater thread."""
+
+    # The removal or renaming of one file equals to the downloading of this
+    # many bytes in time (for the progress calculation)
+    REMOVE2BYTES = 100
+
+    _progressWindow = None
+
+    _progressLabel = None
+    
+    _progressBar = None
+    
+    _progressOKButton = None
+
+    _sudoDialog = None
+
+    @staticmethod
+    def _createGUI(parentWindow):
+        """Create the GUI elements, if needed."""
+        if Updater._progressWindow is not None:
+            return
+
+        Updater._progressWindow = window = gtk.Window()
+        window.set_title("MAVA Logger X " + const.VERSION + " Update")
+        window.set_transient_for(parentWindow)
+        #win.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
+        window.set_size_request(400, -1)
+        window.set_resizable(False)
+        window.set_modal(True)
+        window.connect("delete-event", lambda a, b: True)
+        window.set_deletable(False)
+        window.set_position(gtk.WindowPosition.CENTER_ON_PARENT if pygobject
+                            else gtk.WIN_POS_CENTER_ON_PARENT)
+
+        mainAlignment = gtk.Alignment(xscale = 1.0)
+        mainAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                  padding_left = 16, padding_right = 16)
+        window.add(mainAlignment)
+
+        mainVBox = gtk.VBox()
+        mainAlignment.add(mainVBox)
+
+        labelAlignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
+        Updater._progressLabel = progressLabel = gtk.Label()
+        labelAlignment.add(progressLabel)
+        mainVBox.pack_start(labelAlignment, True, True, 4)
+        
+        Updater._progressBar = progressBar = gtk.ProgressBar()
+        mainVBox.pack_start(progressBar, True, True, 4)
+
+        buttonAlignment = gtk.Alignment(xalign = 0.5, xscale = 0.1)
+        Updater._progressOKButton = progressOKButton = gtk.Button("OK")
+        buttonAlignment.add(progressOKButton)        
+        mainVBox.pack_start(buttonAlignment, True, True, 4)
+
+        Updater._sudoDialog = sudoDialog = \
+            gtk.Dialog("MAVA Logger X " + const.VERSION + " Update",
+                       parentWindow,
+                       gtk.DialogFlags.MODAL if pygobject else gtk.DIALOG_MODAL,
+                       (gtk.STOCK_CANCEL, 0,
+                        gtk.STOCK_OK, 1))
+
+        infoLabelAlignment = gtk.Alignment(xalign = 0.5, xscale = 0.1)
+        infoLabelAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                       padding_left = 16, padding_right = 16)
+
+        infoLabel = gtk.Label("There is an update available, but the program cannot write\n"
+                              "its directory due to insufficient privileges.\n\n"
+                              "Click OK, if you want to run a helper program\n"
+                              "with administrator privileges "
+                              "to complete the update,\n"
+                              "Cancel otherwise.")
+        infoLabel.set_justify(gtk.Justification.CENTER if pygobject
+                              else gtk.JUSTIFY_CENTER)
+        infoLabelAlignment.add(infoLabel)
+        sudoDialog.vbox.pack_start(infoLabelAlignment, True, True, 4)
+
+        sudoDialog.set_position(gtk.WindowPosition.CENTER_ON_PARENT if pygobject
+                                else gtk.WIN_POS_CENTER_ON_PARENT)
+
+    def __init__(self, programDirectory, updateURL, parentWindow):
+        """Construct the updater. If not created yet, the windows used by the
+        updater are also created."""
+        super(Updater, self).__init__()
+        
+        self._programDirectory = programDirectory
+        self._updateURL = updateURL
+
+        self._totalProgress = 0
+        self._waitAfterFinish = False
+        self._restart = False
+
+        self._sudoReply = None
+        self._sudoCondition = threading.Condition()
+
+        Updater._createGUI(parentWindow)
+
+    def run(self):
+        """Execute the thread's operation."""
+        gobject.idle_add(self._startUpdate)
+        update(self._programDirectory, self._updateURL, self, fromGUI = True)
+        if not self._waitAfterFinish:
+            gobject.idle_add(self._progressWindow.hide)
+
+    def downloadingManifest(self):
+        """Called when the downloading of the manifest has started."""
+        gobject.idle_add(self._downloadingManifest)
+
+    def _downloadingManifest(self):
+        """Called when the downloading of the manifest has started."""
+        self._progressLabel.set_text("Downloading manifest...")
+        self._progressBar.set_fraction(0)
+
+    def downloadedManifest(self):
+        """Called when the downloading of the manifest has finished."""
+        gobject.idle_add(self._downloadedManifest)
+
+    def _downloadedManifest(self):
+        """Called when the downloading of the manifest has finished."""
+        self._progressLabel.set_text("Downloaded manifest...")
+        self._progressBar.set_fraction(0.05)
+
+    def needSudo(self):
+        """Called when the program is interested in whether we want to run a
+        program with administrator rights to do the update."""
+        gobject.idle_add(self._needSudo)
+        with self._sudoCondition:
+            while self._sudoReply is None:
+                self._sudoCondition.wait(1)
+            return self._sudoReply
+
+    def _needSudo(self):
+        """Called when the program is interested in whether we want to run a
+        program with administrator rights to do the update."""
+        self._sudoDialog.show_all()
+        result = self._sudoDialog.run()
+        self._sudoDialog.hide()
+        with self._sudoCondition:
+            self._sudoReply = result!=0
+            self._sudoCondition.notify()
+        
+    def setTotalSize(self, numToModifyAndNew, totalSize, numToRemove):
+        """Called when the downloading of the files has started."""
+        self._numToModifyAndNew = numToModifyAndNew
+        self._numModifiedOrNew = 0
+        self._totalSize = totalSize
+        self._downloaded = 0
+        self._numToRemove = numToRemove
+        self._numRemoved = 0
+
+        self._totalProgress = self._totalSize + \
+                              (self._numToModifyAndNew + self._numToRemove) * \
+                              Updater.REMOVE2BYTES
+        self._waitAfterFinish = self._totalProgress > 0
+
+    def _startDownload(self):
+        """Called when the download has started."""
+        self._progressLabel.set_text("Downloading files...")
+
+    def setDownloaded(self, downloaded):
+        """Called when a certain number of bytes are downloaded."""
+        self._downloaded = downloaded
+        gobject.idle_add(self._setDownloaded, downloaded)
+
+    def _setDownloaded(self, downloaded):
+        """Called when a certain number of bytes are downloaded."""
+        self._progressLabel.set_text("Downloaded %d of %d" % \
+                                     (downloaded, self._totalSize))
+        self._setProgress()
+
+    def startRenaming(self):
+        """Called when the renaming of files has started."""
+        gobject.idle_add(self._startRenaming)
+
+    def _startRenaming(self):
+        """Called when the renaming of files has started."""
+        self._progressLabel.set_text("Renaming downloaded files...")
+
+    def renamed(self, path, count):
+        """Called when a file has been renamed."""
+        self._numModifiedOrNew = count
+        gobject.idle_add(self._renamed, path, count)
+
+    def _renamed(self, path, count):
+        """Called when a file has been renamed."""
+        self._progressLabel.set_text("Renamed %s" % (path,))
+        self._setProgress()
+
+    def startRemoving(self):
+        """Called when the removing of files has started."""
+        gobject.idle_add(self._startRemoving)
+
+    def _startRemoving(self):
+        """Called when the removing of files has started."""
+        self._progressLabel.set_text("Removing files...")
+
+    def removed(self, path, count):
+        """Called when a file has been removed."""
+        self._numRemoved = count
+        gobject.idle_add(self._removed, path, count)
+
+    def _removed(self, path, count):
+        """Called when a file has been removed."""
+        self._progressLabel.set_text("Removed %s" % (path,))
+        self._setProgress()
+
+    def writingManifest(self):
+        """Called when the writing of the new manifest file has started."""
+        gobject.idle_add(self._writingManifest)
+
+    def _writingManifest(self):
+        """Called when the writing of the new manifest file has started."""
+        self._progressLabel.set_text("Writing the new manifest")
+        
+    def done(self):
+        """Called when the update has been done."""
+        gobject.idle_add(self._done)
+        self._restart = self._totalProgress>0
+
+    def _done(self):
+        """Called when the writing of the new manifest file has started."""
+        self._progressBar.set_fraction(1)
+        if self._totalProgress>0:
+            self._progressLabel.set_text("Finished updating. Press OK to restart the program.")
+            self._progressOKButton.set_sensitive(True)
+        else:
+            self._progressLabel.set_text("There was nothing to update")
+        
+    def _setProgress(self):
+        """Set the progress bar based on the current stage."""
+        if self._totalProgress>0:
+            progress = self._downloaded + \
+                       (self._numModifiedOrNew + self._numRemoved) * \
+                       Updater.REMOVE2BYTES
+            self._progressBar.set_fraction(0.05 + 0.94 * progress / self._totalProgress)
+
+    def failed(self, what):
+        """Called when the downloading has failed."""
+        self._waitAfterFinish = True
+        gobject.idle_add(self._failed, what)
+
+    def _failed(self, what):
+        """Called when the downloading has failed."""        
+        self._progressLabel.set_text("Failed, see the debug log for details.")
+        self._progressBar.set_fraction(1)
+        self._progressOKButton.set_sensitive(True)
+
+    def _startUpdate(self):
+        """Start the update.
+
+        It resets the GUI elements."""
+        self._progressLabel.set_text("")
+        self._progressBar.set_fraction(0)
+        self._progressWindow.show_all()
+        self._progressOKButton.set_sensitive(False)
+        self._progressOKButton.connect("clicked", self._progressOKClicked)
+
+    def _progressOKClicked(self, button):
+        """Called when the OK button on the progress window is clicked."""
+        self._progressWindow.hide()
+        if self._restart:
+            restart()
+
+#-------------------------------------------------------------------------------
Index: src/mlx/mlx.py
===================================================================
--- src/mlx/mlx.py	(revision 29)
+++ src/mlx/mlx.py	(revision 36)
@@ -2,18 +2,40 @@
 
 from .gui.gui import GUI
-from .gui.common import *
+
+from config import Config
 
 import os
+import sys
 
-def main(iconDirectory):
+#--------------------------------------------------------------------------------------
+
+class StdIOHandler(object):
+    """Handler for the standard I/O messages."""
+    def __init__(self, gui):
+        """Construct the handler."""
+        self._gui = gui
+
+    def write(self, text):
+        """Write the given text into the log."""
+        self._gui.writeStdIO(text)
+
+#--------------------------------------------------------------------------------------
+
+def main():
     """The main operation of the program."""
-    gui = GUI()
+    programDirectory = os.path.dirname(sys.argv[0])
 
-    gui.build(iconDirectory)
+    config = Config()
+    gui = GUI(programDirectory, config)
+
+    sys.stdout = StdIOHandler(gui)
+    sys.stderr = StdIOHandler(gui)
+
+    gui.build(programDirectory)
 
     gui.run()
 
+#--------------------------------------------------------------------------------------
+
 if __name__ == "__main__":
-    main(os.path.dirname(__file__))
-
-
+    main()
Index: src/mlx/update.py
===================================================================
--- src/mlx/update.py	(revision 36)
+++ src/mlx/update.py	(revision 36)
@@ -0,0 +1,500 @@
+# Module handling the download of updates
+
+#------------------------------------------------------------------------------
+
+from config import Config
+
+import os
+import sys
+import urllib2
+import tempfile
+import socket
+import subprocess
+
+if os.name=="nt":
+    import win32api
+
+#------------------------------------------------------------------------------
+
+manifestName = "MLXMANIFEST"
+
+#------------------------------------------------------------------------------
+
+class Manifest(object):
+    """The manifest of the files.
+
+    The manifest file consists of one line for each file. Each line contains 3
+    items separated by tabs:
+    - the path of the file relative to the distribution's root
+    - the size of the file
+    - the MD5 sum of the file."""
+    def __init__(self):
+        """Construct the manifest."""
+        self._files = {}
+
+    @property
+    def files(self):
+        """Get an iterator over the files.
+
+        Each file is returned as a 3-tuple with items as in the file."""
+        for (path, (size, sum)) in self._files.iteritems():
+            yield (path, size, sum)
+        
+    def addFile(self, path, size, sum):
+        """Add a file to the manifest."""
+        self._files[path] = ( size, sum)
+
+    def readFrom(self, file):
+        """Read a manifest from the given file object."""
+        for line in iter(file.readline, ""):
+            (path, size, sum) = line.strip().split("\t")
+            self._files[path] = (int(size), sum)
+
+    def writeInto(self, file):
+        """Write the manifest into the file at the given path."""
+        for (path, (size, sum)) in self._files.iteritems():
+            file.write("%s\t%d\t%s\n" % (path, size, sum))
+
+    def compare(self, other):
+        """Compare this manifest with the other one.
+
+        This returns a tuple of two lists:
+        - the files that are either different or are present in other, but not
+        here. Each file is returned as a 3-tuple as with other functions, the
+        size and sum are those of the new file.
+        - the paths of the files that are present here, but not in other."""
+        modifiedAndNew = []
+        for (path, otherSize, otherSum) in other.files:
+            if path in self._files:
+                (size, sum) = self._files[path]
+                if size!=otherSize or sum!=otherSum:
+                    modifiedAndNew.append((path, otherSize, otherSum))
+            else:
+                modifiedAndNew.append((path, otherSize, otherSum))
+
+        removed = [path for path in self._files if path not in other._files]
+        removed.sort(reverse = True)
+        
+        return (modifiedAndNew, removed)
+            
+#------------------------------------------------------------------------------
+
+class ClientListener(object):
+    """A listener that sends any requests via a socket."""
+    def __init__(self, sock):
+        """Construct the listener."""
+        self._sock = sock
+
+    def downloadingManifest(self):
+        """Called when the downloading of the manifest has started."""
+        self._send(["downloadingManifest"])
+
+    def downloadedManifest(self):
+        """Called when the manifest has been downloaded."""
+        self._send(["downloadedManifest"])
+
+    def needSudo(self):
+        """Called when an admin-level program must be called to complete the
+        update.
+
+        This is not valid from a client, as that client is supposed to be the
+        admin-level program :)"""
+        assert False
+
+    def setTotalSize(self, numToModifyAndNew, totalSize, numToRemove):
+        """Called when starting the downloading of the files."""
+        self._send(["setTotalSize", str(numToModifyAndNew), str(totalSize),
+                    str(numToRemove)])
+
+    def setDownloaded(self, downloaded):
+        """Called periodically after downloading a certain amount of data."""
+        self._send(["setDownloaded", str(downloaded)])
+
+    def startRenaming(self):
+        """Called when the renaming of the downloaded files is started."""
+        self._send(["startRenaming"])
+
+    def renamed(self, path, count):
+        """Called when a file has been renamed."""
+        self._send(["renamed", path, str(count)])
+
+    def startRemoving(self):
+        """Called when the removing of files has started."""
+        self._send(["startRemoving"])
+
+    def removed(self, path, count):
+        """Called when a file has been removed."""
+        self._send(["removed", path, str(count)])
+
+    def writingManifest(self):
+        """Called when we have started writing the manifest."""
+        self._send(["writingManifest"])
+
+    def done(self):
+        """Called when the update has completed."""
+        self._send(["done"])
+
+    def failed(self, what):
+        """Called when something has failed."""
+        self._send(["failed", what])
+
+    def _send(self, words):
+        """Send the given words via the socket."""
+        self._sock.send("\t".join(words) + "\n")
+
+#------------------------------------------------------------------------------
+
+def readLocalManifest(directory):
+    """Read the local manifest from the given directory."""
+    manifestPath = os.path.join(directory, manifestName)
+
+    manifest = Manifest()
+    try:
+        with open(manifestPath, "rt") as f:
+            manifest.readFrom(f)
+    except Exception, e:
+        print "Error reading the manifest, ignoring:", str(e)
+        manifest = Manifest()
+
+    return manifest
+
+#------------------------------------------------------------------------------
+
+def prepareUpdate(directory, updateURL, listener):
+    """Prepare the update by downloading the manifest and comparing it with the
+    local one."""
+    manifest = readLocalManifest(directory)
+        
+    updateURL += "/" + os.name
+
+    listener.downloadingManifest()
+    f = None
+    try:
+        updateManifest = Manifest()
+        f= urllib2.urlopen(updateURL + "/" + manifestName)
+        updateManifest.readFrom(f)
+
+    except Exception, e:
+        print >> sys.stderr, "Error downloading manifest:", str(e)
+        listener.failed(str(e))
+        return None
+    finally:
+        if f is not None: f.close()
+
+    listener.downloadedManifest()
+
+    (modifiedAndNew, removed) = manifest.compare(updateManifest)
+
+    return (manifest, updateManifest, modifiedAndNew, removed)
+
+#------------------------------------------------------------------------------
+
+def createLocalPath(directory, path):
+    """Create a local path from the given manifest path."""
+    localPath = directory
+    for element in path.split("/"):
+        localPath = os.path.join(localPath, element)
+    return localPath
+
+#------------------------------------------------------------------------------
+
+def updateFiles(directory, updateURL, listener,
+                manifest, modifiedAndNew, removed):
+    """Update the files according to the given information."""
+    totalSize = 0
+    for (path, size, sum) in modifiedAndNew:
+        totalSize += size
+
+    listener.setTotalSize(len(modifiedAndNew), totalSize, len(removed))
+
+    downloaded = 0
+    fin = None
+    try:        
+        updateURL += "/" + os.name
+
+        for (path, size, sum) in modifiedAndNew:
+            targetFile = createLocalPath(directory, path)
+            targetFile += "."
+            targetFile += sum
+
+            targetDirectory = os.path.dirname(targetFile)
+            if not os.path.isdir(targetDirectory):
+                os.makedirs(targetDirectory)
+                
+            with open(targetFile, "wb") as fout:
+                fin = urllib2.urlopen(updateURL + "/" + path)
+                while True:
+                    data = fin.read(4096)
+                    if not data:
+                        break
+                    fout.write(data)
+                    downloaded += len(data)
+                    listener.setDownloaded(downloaded)
+                fin.close()
+                fin = None
+
+        listener.startRenaming()
+        count = 0
+        for (path, size, sum) in modifiedAndNew:
+            targetFile = createLocalPath(directory, path)
+
+            downloadedFile = targetFile + "." + sum
+            if os.name=="nt" and os.path.isfile(targetFile):
+                try:
+                    os.remove(targetFile)
+                except:
+                    try:
+                        os.remove(targetFile + ".tmp")
+                    except:
+                        pass
+                    os.rename(targetFile, targetFile + ".tmp")
+            os.rename(downloadedFile, targetFile)
+            count += 1
+            listener.renamed(path, count)
+        
+        listener.startRemoving()
+        count = 0
+        for path in removed:            
+            os.remove(path)
+            count += 1
+            pathDirectory = os.path.dirname(path)
+            try:
+                os.rmdir(pathDirectory)
+            except:
+                pass
+            listener.removed(path, count)
+
+        listener.writingManifest()
+
+        manifestPath = os.path.join(directory, manifestName)
+        with open(manifestPath, "wt") as f:
+            manifest.writeInto(f)
+        
+        listener.done()
+    except Exception, e:
+        print >> sys.stderr, "Error:", str(e)
+        listener.failed(str(e))        
+
+#------------------------------------------------------------------------------
+
+def isDirectoryWritable(directory):
+    """Determine if the given directory can be written."""
+    checkFile = os.path.join(directory, "writable.chk")
+    try:
+        f = open(checkFile, "wt")
+        f.close()
+        return True
+    except Exception, e:
+        return False
+    finally:
+        try:
+            os.remove(checkFile)
+        except:
+            pass
+
+#------------------------------------------------------------------------------
+
+def processMLXUpdate(buffer, listener):
+    """Process the given buffer supposedly containing a list of commands."""
+    endsWithLine = buffer[-1]=="\n"
+    lines = buffer.splitlines()
+
+    if endsWithLine:
+        buffer = ""
+    else:
+        buffer = lines[-1]
+        lines = lines[:-1]
+        
+    for line in lines:
+        words = line.split("\t")
+        try:
+            command = words[0]
+            if command=="downloadingManifest":
+                listener.downloadingManifest()
+            elif command=="downloadedManifest":
+                listener.downloadedManifest()
+            elif command=="setTotalSize":
+                listener.setTotalSize(int(words[1]), long(words[2]),
+                                      int(words[3]))
+            elif command=="setDownloaded":
+                listener.setDownloaded(long(words[1]))
+            elif command=="startRenaming":
+                listener.startRenaming()
+            elif command=="renamed":
+                listener.renamed(words[1], int(words[2]))
+            elif command=="startRemoving":
+                listener.startRemoving()
+            elif command=="removed":
+                listener.removed(words[1], int(words[2]))
+            elif command=="writingManifest":
+                listener.writingManifest()
+            elif command=="done":
+                listener.done()
+            elif command=="failed":
+                listener.failed(words[1])
+        except Exception, e:
+            print >> sys.stderr, "Failed to parse line '%s': %s" % \
+                  (line, str(e))                
+
+    return buffer
+
+#------------------------------------------------------------------------------
+
+def sudoUpdate(directory, updateURL, listener, manifest):
+    """Perform the update via the mlxupdate program."""
+    manifestFD = None
+    manifestFile = None
+    serverSocket = None
+    mlxUpdateSocket = None
+    try:
+        (manifestFD, manifestFile) = tempfile.mkstemp()
+        f = os.fdopen(manifestFD, "wt")
+        try:
+            manifest.writeInto(f)
+        finally:
+            f.close()            
+            manifestFD = None
+
+        serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        serverSocket.bind(("127.0.0.1", 0))
+        (_host, port) = serverSocket.getsockname()
+        serverSocket.listen(1)
+
+        process = subprocess.Popen([os.path.join(directory, "mlxupdate"),
+                                    str(port), manifestFile],
+                                   shell = os.name=="nt")
+
+        (mlxUpdateSocket, _) = serverSocket.accept()
+        serverSocket.close()
+        serverSocket = None
+    
+        buffer = ""
+        while True:
+            data = mlxUpdateSocket.recv(4096)
+            if not data:
+                break;
+
+            buffer += data
+            buffer = processMLXUpdate(buffer, listener)
+
+        mlxUpdateSocket.close()
+        mlxUpdateSocket = None
+
+        process.wait()
+    except Exception, e:
+        print >> sys.stderr, "Failed updating:", str(e)
+        listener.failed(str(e))
+    finally:
+        if serverSocket is not None:
+            try:
+                serverSocket.close()
+            except:
+                pass
+        if mlxUpdateSocket is not None:
+            try:
+                mlxUpdateSocket.close()
+            except:
+                pass
+        if manifestFD is not None:
+            try:
+                os.close(manifestFD)
+            except:
+                pass
+        if manifestFile is not None:
+            try:
+                os.remove(manifestFile)
+            except:
+                pass
+        
+
+#------------------------------------------------------------------------------
+
+def update(directory, updateURL, listener, fromGUI = False):
+    """Perform the update."""
+    result = prepareUpdate(directory, updateURL, listener)
+    if result is None:
+        return
+
+    (manifest, updateManifest, modifiedAndNew, removed) = result        
+
+    if not modifiedAndNew and not removed:
+        listener.done()
+        return
+
+    if fromGUI and not isDirectoryWritable(directory):
+        if listener.needSudo():
+            sudoUpdate(directory, updateURL, listener,
+                       updateManifest)
+    else:
+        updateFiles(directory, updateURL, listener, updateManifest,
+                    modifiedAndNew, removed)
+
+#------------------------------------------------------------------------------
+
+def restart():
+    """Restart the program."""
+    programPath = os.path.join(os.path.dirname(sys.argv[0]),
+                               "runmlx.exe" if os.name=="nt" else "runmlx.sh")
+    if os.name=="nt":
+        programPath = win32api.GetShortPathName(programPath)
+    os.execl(programPath, programPath)
+        
+#------------------------------------------------------------------------------
+
+def updateProcess():
+    """This is called in the child process, when we need a child process."""
+    clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    
+    clientSocket.connect(("127.0.0.1", int(sys.argv[1])))
+
+    config = Config()
+
+    directory = os.path.dirname(sys.argv[0])
+
+    manifest = readLocalManifest(directory)
+    
+    updateManifest = Manifest()
+    with open(sys.argv[2], "rt") as f:
+        updateManifest.readFrom(f)
+
+    (modifiedAndNew, removed) = manifest.compare(updateManifest)
+
+    updateFiles(directory, config.updateURL,
+                ClientListener(clientSocket),
+                updateManifest, modifiedAndNew, removed)
+
+#------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+    manifest1 = Manifest()
+    manifest1.addFile("file1.exe", 3242, "40398754589435345934")
+    manifest1.addFile("dir/file2.zip", 45645, "347893245873456987")
+    manifest1.addFile("dir/file3.txt", 123, "3432434534534534")
+
+    with open("manifest1", "wt") as f:
+        manifest1.writeInto(f)
+
+    manifest2 = Manifest()
+    manifest2.addFile("file1.exe", 4353, "390734659834756349876")
+    manifest2.addFile("dir/file2.zip", 45645, "347893245873456987")
+    manifest2.addFile("dir/file4.log", 2390, "56546546546546")
+
+    with open("manifest2", "wt") as f:
+        manifest2.writeInto(f)
+
+    manifest1 = Manifest()
+    with open("manifest1", "rt") as f:
+        manifest1.readFrom(f)
+
+    manifest2 = Manifest()
+    with open("manifest2", "rt") as f:
+        manifest2.readFrom(f)
+
+    (modifiedAndNew, removed) = manifest1.compare(manifest2)
+
+    for (path, size, sum) in modifiedAndNew:
+        print "modified or new:", path, size, sum
+
+    for path in removed:
+        print "removed:", path
+
+#------------------------------------------------------------------------------
