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 not isinstance(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