Allow async call to stage artifacts.

Add async argument to allow async call to stage artifacts. Default to False. In
async mode, client will poll devserver.is_staged method to check if artifacts
are staged.

BUG=chromium:249426
TEST=local devserver with link like:
http://localhost:8093/stage?archive_url=gs://chromeos-image-archive/lumpy-release/R30-4408.0.0/&artifacts=autotest,test_suites&async=True
http://localhost:8093/is_staged?archive_url=gs://chromeos-image-archive/lumpy-release/R28-4100.7.0&artifacts=autotest,test_suites
run_suite from local setup to stage a build that's not in the devserver.

Change-Id: I7777d5d25e8870fef7edf8a9084bca18f3624c46
Reviewed-on: https://gerrit.chromium.org/gerrit/64310
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Queue: Dan Shi <dshi@chromium.org>
Reviewed-by: Dan Shi <dshi@chromium.org>
Tested-by: Dan Shi <dshi@chromium.org>
diff --git a/downloader.py b/downloader.py
index e19c84f..d927181 100755
--- a/downloader.py
+++ b/downloader.py
@@ -92,7 +92,7 @@
       os.remove(file_name)
       os.rmdir(directory_path)
 
-  def Download(self, artifacts):
+  def Download(self, artifacts, async=False):
     """Downloads and caches the |artifacts|.
 
     Downloads and caches the |artifacts|. Returns once these
@@ -100,8 +100,10 @@
     non-specified artifacts in the background following the principle of
     spatial locality.
 
-    Args:
-      artifacts: a list of artifact names that correspond to artifacts to stage.
+    @params artifacts: A list of artifact names that correspond to artifacts to
+                       stage.
+    @params async: True to return without waiting for download to complete.
+
     """
     common_util.MkDirP(self._build_dir)
 
@@ -121,18 +123,36 @@
     str_repr = [str(a) for a in required_artifacts]
     self._Log('Downloading artifacts %s.', ' '.join(str_repr))
     try:
-      self._DownloadArtifactsSerially(required_artifacts, no_wait=True)
+      if async:
+        self._DownloadArtifactsInBackground(required_artifacts)
+      else:
+        self._DownloadArtifactsSerially(required_artifacts, no_wait=True)
     except gsutil_util.GSUtilError:
       Downloader._TryRemoveStageDir(self._build_dir)
       raise
 
+  def IsStaged(self, artifacts):
+    """Check if all artifacts have been downloaded.
+
+    @param artifacts: A list of artifacts to be checked.
+    @returns: True if all artifacts are staged.
+
+    """
+    # Create factory to create build_artifacts from artifact names.
+    build = self.ParseUrl(self._archive_url)[1]
+    factory = build_artifact.ArtifactFactory(self._build_dir, self._archive_url,
+                                             artifacts, build)
+    required_artifacts = factory.RequiredArtifacts()
+    return all([artifact.ArtifactStaged() for artifact in required_artifacts])
+
   def _DownloadArtifactsSerially(self, artifacts, no_wait):
     """Simple function to download all the given artifacts serially.
 
-    Args:
-      artifacts: List of build_artifact.BuildArtifact instances to download.
-      no_wait: If True, don't block waiting for artifact to exist if we fail to
-               immediately find it.
+    @param artifacts: A list of build_artifact.BuildArtifact instances to
+                      download.
+    @param no_wait: If True, don't block waiting for artifact to exist if we
+                    fail to immediately find it.
+
     """
     for artifact in artifacts:
       artifact.Process(no_wait)