/** * Save an Object into Object Storage. * * This takes an \OpenStack\ObjectStore\v1\Resource\Object * and stores it in the given container in the present * container on the remote object store. * * @param object $obj \OpenStack\ObjectStore\v1\Resource\Object The object to * store. * @param resource $file An optional file argument that, if set, will be * treated as the contents of the object. * * @return boolean true if the object was saved. * * @throws \OpenStack\Common\Transport\Exception\LengthRequiredException if the Content-Length could not be * determined and chunked encoding was * not enabled. This should not occur for * this class, which always automatically * generates Content-Length headers. * However, subclasses could generate * this error. * @throws \OpenStack\Common\Transport\Exception\UnprocessableEntityException if the checksum passed here does not * match the checksum calculated remotely. * @throws \OpenStack\Common\Exception when an unexpected (usually * network-related) error condition arises. */ public function save(Object $obj, $file = null) { if (empty($this->token)) { throw new Exception('Container does not have an auth token.'); } if (empty($this->url)) { throw new Exception('Container does not have a URL to send data.'); } //$url = $this->url . '/' . rawurlencode($obj->name()); $url = self::objectUrl($this->url, $obj->name()); // See if we have any metadata. $headers = []; $md = $obj->metadata(); if (!empty($md)) { $headers = self::generateMetadataHeaders($md, Container::METADATA_HEADER_PREFIX); } // Set the content type. $headers['Content-Type'] = $obj->contentType(); // Add content encoding, if necessary. $encoding = $obj->encoding(); if (!empty($encoding)) { $headers['Content-Encoding'] = rawurlencode($encoding); } // Add content disposition, if necessary. $disposition = $obj->disposition(); if (!empty($disposition)) { $headers['Content-Disposition'] = $disposition; } // Auth token. $headers['X-Auth-Token'] = $this->token; // Add any custom headers: $moreHeaders = $obj->additionalHeaders(); if (!empty($moreHeaders)) { $headers += $moreHeaders; } if (empty($file)) { // Now build up the rest of the headers: $headers['Etag'] = $obj->eTag(); // If chunked, we set transfer encoding; else // we set the content length. if ($obj->isChunked()) { // How do we handle this? Does the underlying // stream wrapper pay any attention to this? $headers['Transfer-Encoding'] = 'chunked'; } else { $headers['Content-Length'] = $obj->contentLength(); } $response = $this->client->put($url, $obj->content(), ['headers' => $headers]); } else { // Rewind the file. rewind($file); // XXX: What do we do about Content-Length header? //$headers['Transfer-Encoding'] = 'chunked'; $stat = fstat($file); $headers['Content-Length'] = $stat['size']; // Generate an eTag: $hash = hash_init('md5'); hash_update_stream($hash, $file); $etag = hash_final($hash); $headers['Etag'] = $etag; // Not sure if this is necessary: rewind($file); $response = $this->client->put($url, $file, ['headers' => $headers]); } if ($response->getStatusCode() != 201) { throw new Exception('An unknown error occurred while saving: ' . $response->status()); } return true; }
public function eTag() { if (!empty($this->content)) { return parent::eTag(); } return $this->etag; }