Refactor devserver to print out relative paths to the update file

Change-Id: Idb98a1e2ff56716d1aadcfda6eb83108dff913f2

BUG=chromium-os:10723
TEST=Ran it with the full test harness -q

Committed: http://chrome-svn/viewvc/chromeos?view=rev&revision=d3de9ef

Review URL: http://codereview.chromium.org/6362008
diff --git a/autoupdate.py b/autoupdate.py
index f8fb57b..1add025 100644
--- a/autoupdate.py
+++ b/autoupdate.py
@@ -16,8 +16,9 @@
 def _LogMessage(message):
   cherrypy.log(message, 'UPDATE')
 
-UPDATE_FILE='update.gz'
-STATEFUL_FILE='stateful.tgz'
+UPDATE_FILE = 'update.gz'
+STATEFUL_FILE = 'stateful.tgz'
+CACHE_DIR = 'cache'
 
 
 def _ChangeUrlPort(url, new_port):
@@ -82,8 +83,8 @@
     self.board = board
     self.copy_to_static_root = copy_to_static_root
 
-    # Track update pregeneration, so we don't recopy if not needed.
-    self.pregenerated = False
+    # Path to pre-generated file.
+    self.pregenerated_path = None
 
   def _GetSecondsSinceMidnight(self):
     """Returns the seconds since midnight as a decimal value."""
@@ -252,7 +253,7 @@
             src_image))
     _LogMessage(mkupdate_command)
     if os.system(mkupdate_command) != 0:
-      _LogMessage('Failed to create base update file')
+      _LogMessage('Failed to create update payload')
       return None
 
     return UPDATE_FILE
@@ -284,17 +285,17 @@
        yet. The directory will be inside static_image_dir, and of the form:
 
        Non-delta updates:
-         cache/12345678
+         CACHE_DIR/12345678
 
        Delta updates:
-         cache/12345678_12345678
+         CACHE_DIR/12345678_12345678
        """
     # If there is no src, we only have an image file, check image for changes
     if not src_image:
-      return os.path.join('cache', self._GetMd5(dest_image))
+      return os.path.join(CACHE_DIR, self._GetMd5(dest_image))
 
     # If we have src and dest, we are a delta, and check both for changes
-    return os.path.join('cache',
+    return os.path.join(CACHE_DIR,
                         "%s_%s" % (self._GetMd5(src_image),
                                    self._GetMd5(dest_image)))
 
@@ -323,14 +324,9 @@
 
     if update_file and stateful_update_file:
       return update_file
-
-    _LogMessage('Failed to generate update')
-
-    # Cleanup incomplete files, if they exist
-    if update_file and os.path.exists(update_file):
-      os.remove(update_file)
-
-    return None
+    else:
+      _LogMessage('Failed to generate update.')
+      return None
 
   def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
     """Force generates an update payload based on the given image_path.
@@ -340,56 +336,60 @@
       static_image_dir: the directory to move images to after generating.
     Returns:
       update filename (not directory) relative to static_image_dir on success,
-        or None
+        or None.
     """
     _LogMessage('Generating update for src %s image %s' % (self.src_image,
                                                            image_path))
 
-    # If it was pregenerated, don't regenerate
-    if self.pregenerated:
-      return UPDATE_FILE
+    # If it was pregenerated_path, don't regenerate
+    if self.pregenerated_path:
+      return self.pregenerated_path
 
     # Which sub_dir of static_image_dir should hold our cached update image
     cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
     _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir)
 
+    update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
+
     # The cached payloads exist in a cache dir
     cache_update_payload = os.path.join(static_image_dir,
-                                        cache_sub_dir,
-                                        UPDATE_FILE)
+                                        update_path)
     cache_stateful_payload = os.path.join(static_image_dir,
                                           cache_sub_dir,
                                           STATEFUL_FILE)
 
-    # The final results exist directly in static
-    update_payload = os.path.join(static_image_dir,
-                                  UPDATE_FILE)
-    stateful_payload = os.path.join(static_image_dir,
-                                    STATEFUL_FILE)
-
-    # If there isn't a cached payload, make one
-    if not os.path.exists(cache_update_payload):
+    # Check to see if this cache directory is valid.
+    if not os.path.exists(cache_update_payload) or not os.path.exists(
+        cache_stateful_payload):
       full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
+      # Clean up stale state.
+      os.system('rm -rf "%s"' % full_cache_dir)
+      os.makedirs(full_cache_dir)
+      return_path = self.GenerateUpdateImage(image_path,
+                                             full_cache_dir)
 
-      # Create the directory for the cache values
-      if not os.path.exists(full_cache_dir):
-        os.makedirs(full_cache_dir)
-
-      result = self.GenerateUpdateImage(image_path,
-                                        full_cache_dir)
-
-      if not result:
-        # Clean up cache dir if it's not valid
-        os.system("rm -rf %s" % os.path.join(static_image_dir, cache_sub_dir))
+      # Clean up cache dir since it's not valid.
+      if not return_path:
+        os.system('rm -rf "%s"' % full_cache_dir)
         return None
+      else:
+        assert (return_path == update_path,
+                'Returned path %s not equal to %s' % (return_path, update_path))
+
+    self.pregenerated_path = update_path
 
     # Generation complete, copy if requested.
     if self.copy_to_static_root:
+      # The final results exist directly in static
+      update_payload = os.path.join(static_image_dir,
+                                    UPDATE_FILE)
+      stateful_payload = os.path.join(static_image_dir,
+                                      STATEFUL_FILE)
       self._Copy(cache_update_payload, update_payload)
       self._Copy(cache_stateful_payload, stateful_payload)
-
-    # Return just the filename in static_image_dir.
-    return UPDATE_FILE
+      return UPDATE_FILE
+    else:
+      return self.pregenerated_path
 
   def GenerateLatestUpdateImage(self, board_id, client_version,
                                 static_image_dir):
@@ -528,7 +528,6 @@
       # If the forced payload is not already in our static_image_dir,
       # copy it there.
       src_path = os.path.abspath(self.forced_payload)
-
       src_stateful = os.path.join(os.path.dirname(src_path),
                                   STATEFUL_FILE)
 
@@ -567,24 +566,25 @@
                                               client_version,
                                               static_image_dir)
 
-      _LogMessage('You must set --board for pre-generating latest update.')
+      _LogMessage('Failed to genereate update. '
+                  'You must set --board when pre-generating latest update.')
       return None
 
   def PreGenerateUpdate(self):
-    """Pre-generates an update.  Returns True on success.
+    """Pre-generates an update and prints out the relative path it.
+
+    Returns relative path of the update on success.
     """
      # Does not work with factory config.
     assert(not self.factory_config)
     _LogMessage('Pre-generating the update payload.')
     # Does not work with labels so just use static dir.
-    if self.GenerateUpdatePayloadForNonFactory(self.board, '0.0.0.0',
-                                               self.static_dir):
-      _LogMessage('Pre-generated update successfully.')
-      self.pregenerated = True
-      return True
-    else:
-      _LogMessage('Failed to pre-generate update.')
-      return False
+    pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
+        self.board, '0.0.0.0', self.static_dir)
+    if pregenerated_update:
+      print 'PREGENERATED_UPDATE=%s' % pregenerated_update
+
+    return pregenerated_update
 
   def HandleUpdatePing(self, data, label=None):
     """Handles an update ping from an update client.