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 logging
6import os
7
8from py_utils import cloud_storage
9
10from dependency_manager import exceptions
11
12
13BACKUP_PATH_EXTENSION = 'old'
14
15
16class CloudStorageUploader(object):
17  def __init__(self, bucket, remote_path, local_path, cs_backup_path=None):
18    if not bucket or not remote_path or not local_path:
19      raise ValueError(
20          'Attempted to partially initialize upload data with bucket %s, '
21          'remote_path %s, and local_path %s', bucket, remote_path, local_path)
22    if not os.path.exists(local_path):
23      raise ValueError('Attempting to initilize UploadInfo with missing '
24                       'local path %s', local_path)
25
26    self._cs_bucket = bucket
27    self._cs_remote_path = remote_path
28    self._local_path = local_path
29    self._cs_backup_path = (cs_backup_path or
30                            '%s.%s' % (self._cs_remote_path,
31                                       BACKUP_PATH_EXTENSION))
32    self._updated = False
33    self._backed_up = False
34
35  def Upload(self, force=False):
36    """Upload all pending files and then write the updated config to disk.
37
38    Will attempt to copy files existing in the upload location to a backup
39    location in the same bucket in cloud storage if |force| is True.
40
41    Args:
42      force: True if files should be uploaded to cloud storage even if a
43          file already exists in the upload location.
44
45    Raises:
46      CloudStorageUploadConflictError: If |force| is False and the potential
47          upload location of a file already exists.
48      CloudStorageError: If copying an existing file to the backup location
49          or uploading the new file fails.
50    """
51    if cloud_storage.Exists(self._cs_bucket, self._cs_remote_path):
52      if not force:
53        #pylint: disable=nonstandard-exception
54        raise exceptions.CloudStorageUploadConflictError(self._cs_bucket,
55                                                         self._cs_remote_path)
56        #pylint: enable=nonstandard-exception
57      logging.debug('A file already exists at upload path %s in self.cs_bucket'
58                    ' %s', self._cs_remote_path, self._cs_bucket)
59      try:
60        cloud_storage.Copy(self._cs_bucket, self._cs_bucket,
61                           self._cs_remote_path, self._cs_backup_path)
62        self._backed_up = True
63      except cloud_storage.CloudStorageError:
64        logging.error('Failed to copy existing file %s in cloud storage bucket '
65                      '%s to backup location %s', self._cs_remote_path,
66                      self._cs_bucket, self._cs_backup_path)
67        raise
68
69    try:
70      cloud_storage.Insert(
71          self._cs_bucket, self._cs_remote_path, self._local_path)
72    except cloud_storage.CloudStorageError:
73      logging.error('Failed to upload %s to %s in cloud_storage bucket %s',
74                    self._local_path, self._cs_remote_path, self._cs_bucket)
75      raise
76    self._updated = True
77
78  def Rollback(self):
79    """Attempt to undo the previous call to Upload.
80
81    Does nothing if no previous call to Upload was made, or if nothing was
82    successfully changed.
83
84    Returns:
85      True iff changes were successfully rolled back.
86    Raises:
87      CloudStorageError: If copying the backed up file to its original
88          location or removing the uploaded file fails.
89    """
90    cloud_storage_changed = False
91    if self._backed_up:
92      cloud_storage.Copy(self._cs_bucket, self._cs_bucket, self._cs_backup_path,
93                         self._cs_remote_path)
94      cloud_storage_changed = True
95      self._cs_backup_path = None
96    elif self._updated:
97      cloud_storage.Delete(self._cs_bucket, self._cs_remote_path)
98      cloud_storage_changed = True
99    self._updated = False
100    return cloud_storage_changed
101
102  def __eq__(self, other, msg=None):
103    if type(self) != type(other):
104      return False
105    return (self._local_path == other._local_path and
106            self._cs_remote_path == other._cs_remote_path and
107            self._cs_bucket == other._cs_bucket)
108
109