. */ require_once 'aws-sdk/sdk.class.php'; class OC_Filestorage_AmazonS3 extends OC_Filestorage_Common { private $s3; private $bucket; private $objects = array(); private static $tempFiles = array(); // TODO Update to new AWS SDK public function __construct($params) { if (isset($params['key']) && isset($params['secret']) && isset($params['bucket'])) { $this->s3 = new AmazonS3(array('key' => $params['key'], 'secret' => $params['secret'])); $this->bucket = $params['bucket']; $test = $this->s3->get_canonical_user_id(); if (!isset($test['id']) || $test['id'] == '') { throw new Exception(); } } else { throw new Exception(); } } private function getObject($path) { if (array_key_exists($path, $this->objects)) { return $this->objects[$path]; } else { $response = $this->s3->get_object_metadata($this->bucket, $path); if ($response) { $this->objects[$path] = $response; return $response; // This object could be a folder, a '/' must be at the end of the path } else if (substr($path, -1) != '/') { $response = $this->s3->get_object_metadata($this->bucket, $path.'/'); if ($response) { $this->objects[$path] = $response; return $response; } } } return false; } public function mkdir($path) { // Folders in Amazon S3 are 0 byte objects with a '/' at the end of the name if (substr($path, -1) != '/') { $path .= '/'; } $response = $this->s3->create_object($this->bucket, $path, array('body' => '')); return $response->isOK(); } public function rmdir($path) { if (substr($path, -1) != '/') { $path .= '/'; } return $this->unlink($path); } public function opendir($path) { if ($path == '' || $path == '/') { // Use the '/' delimiter to only fetch objects inside the folder $opt = array('delimiter' => '/'); } else { if (substr($path, -1) != '/') { $path .= '/'; } $opt = array('delimiter' => '/', 'prefix' => $path); } $response = $this->s3->list_objects($this->bucket, $opt); if ($response->isOK()) { $files = array(); foreach ($response->body->Contents as $object) { // The folder being opened also shows up in the list of objects, don't add it to the files if ($object->Key != $path) { $files[] = basename($object->Key); } } // Sub folders show up as CommonPrefixes foreach ($response->body->CommonPrefixes as $object) { $files[] = basename($object->Prefix); } OC_FakeDirStream::$dirs['amazons3'.$path] = $files; return opendir('fakedir://amazons3'.$path); } return false; } public function stat($path) { if ($path == '' || $path == '/') { $stat['size'] = $this->s3->get_bucket_filesize($this->bucket); $stat['atime'] = time(); $stat['mtime'] = $stat['atime']; $stat['ctime'] = $stat['atime']; } else { $object = $this->getObject($path); if ($object) { $stat['size'] = $object['Size']; $stat['atime'] = time(); $stat['mtime'] = strtotime($object['LastModified']); $stat['ctime'] = $stat['mtime']; } } if (isset($stat)) { return $stat; } return false; } public function filetype($path) { if ($path == '' || $path == '/') { return 'dir'; } else { $object = $this->getObject($path); if ($object) { // Amazon S3 doesn't have typical folders, this is an alternative method to detect a folder if (substr($object['Key'], -1) == '/' && $object['Size'] == 0) { return 'dir'; } else { return 'file'; } } } return false; } public function isReadable($path) { // TODO Check acl and determine who grantee is return true; } public function isUpdatable($path) { // TODO Check acl and determine who grantee is return true; } public function file_exists($path) { if ($this->filetype($path) == 'dir' && substr($path, -1) != '/') { $path .= '/'; } return $this->s3->if_object_exists($this->bucket, $path); } public function unlink($path) { $response = $this->s3->delete_object($this->bucket, $path); return $response->isOK(); } public function fopen($path, $mode) { switch ($mode) { case 'r': case 'rb': $tmpFile = OC_Helper::tmpFile(); $handle = fopen($tmpFile, 'w'); $response = $this->s3->get_object($this->bucket, $path, array('fileDownload' => $handle)); if ($response->isOK()) { return fopen($tmpFile, 'r'); } break; case 'w': case 'wb': case 'a': case 'ab': case 'r+': case 'w+': case 'wb+': case 'a+': case 'x': case 'x+': case 'c': case 'c+': if (strrpos($path, '.') !== false) { $ext = substr($path, strrpos($path, '.')); } else { $ext = ''; } $tmpFile = OC_Helper::tmpFile($ext); OC_CloseStreamWrapper::$callBacks[$tmpFile] = array($this, 'writeBack'); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); file_put_contents($tmpFile, $source); } self::$tempFiles[$tmpFile] = $path; return fopen('close://'.$tmpFile, $mode); } return false; } public function writeBack($tmpFile) { if (isset(self::$tempFiles[$tmpFile])) { $handle = fopen($tmpFile, 'r'); $response = $this->s3->create_object($this->bucket, self::$tempFiles[$tmpFile], array('fileUpload' => $handle)); if ($response->isOK()) { unlink($tmpFile); } } } public function getMimeType($path) { if ($this->filetype($path) == 'dir') { return 'httpd/unix-directory'; } else { $object = $this->getObject($path); if ($object) { return $object['ContentType']; } } return false; } public function free_space($path) { // Infinite? return false; } public function touch($path, $mtime = null) { if (is_null($mtime)) { $mtime = time(); } if ($this->filetype($path) == 'dir' && substr($path, -1) != '/') { $path .= '/'; } $response = $this->s3->update_object($this->bucket, $path, array('meta' => array('LastModified' => $mtime))); return $response->isOK(); } }