/**
  * Perform the actual rename of a GCS storage object.
  * Renaming an object has the following steps.
  * 1. stat the 'from' object to get the ETag and content type.
  * 2. Use x-goog-copy-source-if-match to copy the object.
  * 3. Delete the original object.
  */
 public function rename()
 {
     $token_header = $this->getOAuthTokenHeader(parent::WRITE_SCOPE);
     if ($token_header === false) {
         if (!$this->quiet) {
             trigger_error("Unable to acquire OAuth token.", E_USER_WARNING);
         }
         return false;
     }
     // Stat the from object to get the etag and content-type
     $http_response = $this->makeHttpRequest($this->url, "HEAD", $token_header);
     if ($http_response === false) {
         trigger_error("Unable to connect to Google Cloud Storage Service.", E_USER_WARNING);
         return false;
     }
     $status_code = $http_response['status_code'];
     if ($status_code != HttpResponse::OK) {
         trigger_error(sprintf("Unable to rename: %s. Cloud Storage Error: %s", sprintf("gs://%s%s", $this->to_bucket, $this->to_object), HttpResponse::getStatusMessage($status_code)), E_USER_WARNING);
         return false;
     }
     $copy_headers = ['x-goog-copy-source' => sprintf("/%s%s", $this->from_bucket, $this->from_object), 'x-goog-copy-source-if-match' => $this->getHeaderValue('ETag', $http_response['headers'])];
     // TODO: b/13132830: Remove once feature releases.
     if (!ini_get('google_app_engine.enable_additional_cloud_storage_headers')) {
         foreach (static::$METADATA_HEADERS as $key) {
             // Leave Content-Type since it has been supported.
             if ($key != 'Content-Type') {
                 unset($this->context_options[$key]);
             }
         }
     }
     // Check if any metadata context options have been set and only copy values
     // below if one option needs to be changed.
     $is_meta = isset($this->context_options['metadata']);
     foreach (static::$METADATA_HEADERS as $key) {
         // Stop after first meta option found.
         $is_meta = $is_meta ?: isset($this->context_options[$key]);
         if ($is_meta) {
             break;
         }
     }
     // Metadata-directive applies to headers outside of x-goog-meta-* like
     // Content-Type. If any metadata changes are included in context then all
     // other meta data must be filled in since the metadata-directive will be
     // set to REPLACE and non-passed in values should remain the same.
     if ($is_meta) {
         $copy_headers['x-goog-metadata-directive'] = 'REPLACE';
         // Copy all meta data fields to preserve values when using REPLACE
         // directive. If a value exists in context_options it is given preference.
         foreach (static::$METADATA_HEADERS as $key) {
             if (isset($this->context_options[$key])) {
                 $copy_headers[$key] = $this->context_options[$key];
             } else {
                 $value = $this->getHeaderValue($key, $http_response['headers']);
                 if ($value !== null) {
                     $copy_headers[$key] = $value;
                 }
             }
         }
         // Special case since metadata option converts to multiple headers, this
         // also handles copying over previous values if no new ones speicified.
         $metadata = isset($this->context_options['metadata']) ? $this->context_options['metadata'] : static::extractMetaData($http_response['headers']);
         foreach ($metadata as $key => $value) {
             $copy_headers['x-goog-meta-' . $key] = $value;
         }
     } else {
         $copy_headers['x-goog-metadata-directive'] = 'COPY';
     }
     if (array_key_exists("acl", $this->context_options)) {
         $acl = $this->context_options["acl"];
         if (in_array($acl, parent::$valid_acl_values)) {
             $copy_headers["x-goog-acl"] = $acl;
         } else {
             trigger_error(sprintf("Invalid ACL value: %s", $acl), E_USER_WARNING);
             return false;
         }
     }
     $to_url = $this->createObjectUrl($this->to_bucket, $this->to_object);
     $http_response = $this->makeHttpRequest($to_url, "PUT", array_merge($token_header, $copy_headers));
     if ($http_response === false) {
         trigger_error("Unable to copy source to destination.", E_USER_WARNING);
         return false;
     }
     $status_code = $http_response['status_code'];
     if ($status_code != HttpResponse::OK) {
         trigger_error(sprintf("Error copying to %s. Cloud Storage Error: %s", sprintf("gs://%s%s", $this->to_bucket, $this->to_object), HttpResponse::getStatusMessage($status_code)), E_USER_WARNING);
         return false;
     }
     // Unlink the original file.
     $http_response = $this->makeHttpRequest($this->url, "DELETE", $token_header);
     if ($http_response === false) {
         trigger_error("Failed to delete the from cloud storage object.", E_USER_WARNING);
         return false;
     }
     $status_code = $http_response['status_code'];
     if ($status_code !== HttpResponse::NO_CONTENT) {
         trigger_error(sprintf("Unable to unlink: %s. Cloud Storage Error: %s", sprintf("gs://%s%s", $this->from_bucket, $this->from_object), HttpResponse::getStatusMessage($status_code)), E_USER_WARNING);
         return false;
     }
     clearstatcache(true, $this->createGcsFilename($this->from_bucket, $this->from_object));
     clearstatcache(true, $this->createGcsFilename($this->to_bucket, $this->to_object));
     return true;
 }
 /**
  * Perform the actual rename of a GCS storage object.
  * Renaming an object has the following steps.
  * 1. stat the 'from' object to get the ETag and content type.
  * 2. Use x-goog-copy-source-if-match to copy the object.
  * 3. Delete the original object.
  */
 public function rename()
 {
     $token_header = $this->getOAuthTokenHeader(parent::WRITE_SCOPE);
     if ($token_header === false) {
         if (!$this->quiet) {
             trigger_error("Unable to acquire OAuth token.", E_USER_WARNING);
         }
         return false;
     }
     // Stat the from object to get the etag and content-type
     $http_response = $this->makeHttpRequest($this->url, "HEAD", $token_header);
     if ($http_response === false) {
         trigger_error("Unable to connect to Google Cloud Storage Service.", E_USER_WARNING);
         return false;
     }
     $status_code = $http_response['status_code'];
     if ($status_code != HttpResponse::OK) {
         trigger_error(sprintf("Unable to rename: %s. Cloud Storage Error: %s", sprintf("gs://%s%s", $this->to_bucket, $this->to_object), HttpResponse::getStatusMessage($status_code)), E_USER_WARNING);
         return false;
     }
     $from_etag = $this->getHeaderValue('ETag', $http_response['headers']);
     $content_type = $this->getHeaderValue('Content-Type', $http_response['headers']);
     $copy_headers = ['x-goog-copy-source' => sprintf("/%s%s", $this->from_bucket, $this->from_object), 'x-goog-copy-source-if-match' => $from_etag, 'content-type' => $content_type, 'x-goog-metadata-directive' => 'COPY'];
     if (array_key_exists("acl", $this->context_options)) {
         $acl = $this->context_options["acl"];
         if (in_array($acl, parent::$valid_acl_values)) {
             $copy_headers["x-goog-acl"] = $acl;
         } else {
             trigger_error(sprintf("Invalid ACL value: %s", $acl), E_USER_WARNING);
             return false;
         }
     }
     $to_url = $this->createObjectUrl($this->to_bucket, $this->to_object);
     $http_response = $this->makeHttpRequest($to_url, "PUT", array_merge($token_header, $copy_headers));
     if ($http_response === false) {
         trigger_error("Unable to copy source to destination.", E_USER_WARNING);
         return false;
     }
     $status_code = $http_response['status_code'];
     if ($status_code != HttpResponse::OK) {
         trigger_error(sprintf("Error copying to %s. Cloud Storage Error: %s", sprintf("gs://%s%s", $this->to_bucket, $this->to_object), HttpResponse::getStatusMessage($status_code)), E_USER_WARNING);
         return false;
     }
     // Unlink the original file.
     $http_response = $this->makeHttpRequest($this->url, "DELETE", $token_header);
     if ($http_response === false) {
         trigger_error("Failed to delete the from cloud storage object.", E_USER_WARNING);
         return false;
     }
     $status_code = $http_response['status_code'];
     if ($status_code !== HttpResponse::NO_CONTENT) {
         trigger_error(sprintf("Unable to unlink: %s. Cloud Storage Error: %s", sprintf("gs://%s%s", $this->from_bucket, $this->from_object), HttpResponse::getStatusMessage($status_code)), E_USER_WARNING);
         return false;
     }
     return true;
 }