Add a new devserver call to locate a file in build artifact

The new call `locate_file` looks up the given file name inside specified
build artifacts. One use case is to help caller to locate an apk file
inside a build artifact.

BUG=chromium:586320
TEST=local run devserver
curl
http://127.0.0.1:8082/locate_file?file_name=sl4a.apk\&target=shamu-userdebug\&build_id=2457013\&artifacts=test_zip\&branch=git_mnc-release\&os_type=android
expected retun: DATA/priv-app/sl4a/sl4a.apk
python devserver_integration_test.py

Change-Id: Ia029aae09b9bf52670c78fe2be4ccef62283ba41
Reviewed-on: https://chromium-review.googlesource.com/327278
Commit-Ready: Dan Shi <dshi@google.com>
Tested-by: Dan Shi <dshi@google.com>
Reviewed-by: Dan Shi <dshi@google.com>
diff --git a/devserver.py b/devserver.py
index c5bd8bd..54c25e3 100755
--- a/devserver.py
+++ b/devserver.py
@@ -60,6 +60,7 @@
 from cherrypy.process import plugins
 
 import autoupdate
+import artifact_info
 import build_artifact
 import cherrypy_ext
 import common_util
@@ -759,6 +760,44 @@
     return 'Success'
 
   @cherrypy.expose
+  def locate_file(self, **kwargs):
+    """Get the path to the given file name.
+
+    This method looks up the given file name inside specified build artifacts.
+    One use case is to help caller to locate an apk file inside a build
+    artifact. The location of the apk file could be different based on the
+    branch and target.
+
+    Args:
+      file_name: Name of the file to look for.
+      artifacts: A list of artifact names to search for the file.
+
+    Returns:
+      Path to the file with the given name. It's relative to the folder for the
+      build, e.g., DATA/priv-app/sl4a/sl4a.apk
+
+    """
+    dl, _ = _get_downloader_and_factory(kwargs)
+    try:
+      file_name = kwargs['file_name'].lower()
+      artifacts = kwargs['artifacts']
+    except KeyError:
+      raise DevServerError('`file_name` and `artifacts` are required to search '
+                           'for a file in build artifacts.')
+    build_path = dl.GetBuildDir()
+    for artifact in artifacts:
+      # Get the unzipped folder of the artifact. If it's not defined in
+      # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
+      # directory directly.
+      folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
+      artifact_path = os.path.join(build_path, folder)
+      for root, _, filenames in os.walk(artifact_path):
+        if file_name in set([f.lower() for f in filenames]):
+          return os.path.relpath(os.path.join(root, file_name), build_path)
+    raise DevServerError('File `%s` can not be found in artifacts: %s' %
+                         (file_name, artifacts))
+
+  @cherrypy.expose
   def setup_telemetry(self, **kwargs):
     """Extracts and sets up telemetry