1# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import errno
6import os
7import stat
8
9from catapult_base import cloud_storage
10
11from dependency_manager import exceptions
12
13class CloudStorageInfo(object):
14  def __init__(self, cs_bucket, cs_hash, download_path, cs_remote_path,
15               version_in_cs=None, archive_info=None):
16    """ Container for the information needed to download a dependency from
17        cloud storage.
18
19    Args:
20          cs_bucket: The cloud storage bucket the dependency is located in.
21          cs_hash: The hash of the file stored in cloud storage.
22          download_path: Where the file should be downloaded to.
23          cs_remote_path: Where the file is stored in the cloud storage bucket.
24          version_in_cs: The version of the file stored in cloud storage.
25          archive_info: An instance of ArchiveInfo if this dependency is an
26              archive. Else None.
27    """
28    self._download_path = download_path
29    self._cs_remote_path = cs_remote_path
30    self._cs_bucket = cs_bucket
31    self._cs_hash = cs_hash
32    self._version_in_cs = version_in_cs
33    self._archive_info = archive_info
34    if not self._has_minimum_data:
35      raise ValueError(
36          'Not enough information specified to initialize a cloud storage info.'
37          ' %s' % self)
38
39  def DependencyExistsInCloudStorage(self):
40    return cloud_storage.Exists(self._cs_bucket, self._cs_remote_path)
41
42  def GetRemotePath(self):
43    """Gets the path to a downloaded version of the dependency.
44
45    May not download the file if it has already been downloaded.
46    Will unzip the downloaded file if a non-empty archive_info was passed in at
47    init.
48
49    Returns: A path to an executable that was stored in cloud_storage, or None
50       if not found.
51
52    Raises:
53        CredentialsError: If cloud_storage credentials aren't configured.
54        PermissionError: If cloud_storage credentials are configured, but not
55            with an account that has permission to download the needed file.
56        NotFoundError: If the needed file does not exist where expected in
57            cloud_storage or the downloaded zip file.
58        ServerError: If an internal server error is hit while downloading the
59            needed file.
60        CloudStorageError: If another error occured while downloading the remote
61            path.
62        FileNotFoundError: If the download was otherwise unsuccessful.
63    """
64    if not self._has_minimum_data:
65      return None
66
67    download_dir = os.path.dirname(self._download_path)
68    if not os.path.exists(download_dir):
69      try:
70        os.makedirs(download_dir)
71      except OSError as e:
72        # The logic above is racy, and os.makedirs will raise an OSError if
73        # the directory exists.
74        if e.errno != errno.EEXIST:
75          raise
76
77    dependency_path = self._download_path
78    cloud_storage.GetIfHashChanged(
79        self._cs_remote_path, self._download_path, self._cs_bucket,
80        self._cs_hash)
81    if not os.path.exists(dependency_path):
82      raise exceptions.FileNotFoundError(dependency_path)
83
84    if self.has_archive_info:
85      dependency_path = self._archive_info.GetUnzippedPath()
86    else:
87      mode = os.stat(dependency_path).st_mode
88      os.chmod(dependency_path, mode | stat.S_IXUSR)
89    return os.path.abspath(dependency_path)
90
91  @property
92  def version_in_cs(self):
93    return self._version_in_cs
94
95  @property
96  def _has_minimum_data(self):
97    return all([self._cs_bucket, self._cs_remote_path, self._download_path,
98                self._cs_hash])
99
100
101  @property
102  def has_archive_info(self):
103    return bool(self._archive_info)
104
105  def __repr__(self):
106    return (
107        'CloudStorageInfo(download_path=%s, cs_remote_path=%s, cs_bucket=%s, '
108        'cs_hash=%s, version_in_cs=%s, archive_info=%s)' % (
109            self._download_path, self._cs_remote_path, self._cs_bucket,
110            self._cs_hash, self._version_in_cs, self._archive_info))
111