Skip to content
This repository was archived by the owner on Oct 13, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 168 additions & 147 deletions code/S3Bucket.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,151 +15,172 @@

class S3Bucket extends CloudBucket
{
const CONTAINER = 'Container';
const REGION = 'Region';
const API_KEY = 'ApiKey';
const API_SECRET = 'ApiSecret';
const FORCE_DL = 'ForceDownload';

protected $client;


/**
* @param string $path
* @param array $cfg
* @throws Exception
*/
public function __construct($path, array $cfg=array()) {
parent::__construct($path, $cfg);
if (empty($cfg[self::CONTAINER])) throw new Exception('S3Bucket: missing configuration key - ' . self::CONTAINER);
if (empty($cfg[self::REGION])) throw new Exception('S3Bucket: missing configuration key - ' . self::REGION);
if (empty($cfg[self::API_KEY])) throw new Exception('S3Bucket: missing configuration key - ' . self::API_KEY);
if (empty($cfg[self::API_SECRET])) throw new Exception('S3Bucket: missing configuration key - ' . self::API_SECRET);
$this->containerName = $this->config[self::CONTAINER];

$this->client = S3Client::factory(array(
'key' => $this->config[self::API_KEY],
'secret' => $this->config[self::API_SECRET],
'region' => $this->config[self::REGION]
));
}


/**
* @param File $f
* @throws Exception
*/
public function put(File $f) {
$fp = fopen($f->getFullPath(), 'r');
if (!$fp) throw new Exception('Unable to open file: ' . $f->getFilename());

$uploader = UploadBuilder::newInstance()
->setClient($this->client)
->setSource($f->getFullPath())
->setBucket($this->containerName)
->setKey($this->getRelativeLinkFor($f))
->build();

try {
$uploader->upload();
} catch (MultipartUploadException $e) {
$uploader->abort();
}
}


/**
* @param File|string $f
*/
public function delete($f) {
$this->client->deleteObject(array(
'Bucket' => $this->containerName,
'Key' => $this->getRelativeLinkFor($f),
));
}

/**
* @param File $f
* @param string $beforeName - contents of the Filename property (i.e. relative to site root)
* @param string $afterName - contents of the Filename property (i.e. relative to site root)
*/
public function rename(File $f, $beforeName, $afterName) {
$obj = $this->getFileObjectFor($beforeName);
$result = $this->client->copyObject(array(
'Bucket' => $this->containerName,
'CopySource' => urlencode($this->containerName . '/' . $this->getRelativeLinkFor($beforeName)),
'Key' => $this->getRelativeLinkFor($afterName),
));
if($result) $this->client->deleteObject(array(
'Bucket' => $this->containerName,
'Key' => $this->getRelativeLinkFor($beforeName),
));
}


/**
* @param File $f
* @return string
*/
public function getContents(File $f) {
$obj = $this->getFileObjectFor($f);
return $obj['Body'];
}


/**
* This version just returns a normal link. I'm assuming most
* buckets will implement this but I want it to be optional.
* NOTE: I'm not sure how reliably this is working.
*
* @param File|string $f
* @param int $expires [optional] - Expiration time in seconds
* @return string
*/
public function getTemporaryLinkFor($f, $expires=3600) {
$obj = $this->getFileObjectFor($this->getRelativeLinkFor($f));
return $obj['Body']->getUri();
}


/**
* @param $f - File object or filename
* @return bool
*/
public function checkExists(File $f) {
return $this->client->doesObjectExist(
$this->containerName,
$this->getRelativeLinkFor($f)
);
}


/**
* @param $f - File object or filename
* @return int - if file doesn't exist, returns -1
*/
public function getFileSize(File $f) {
if($obj = $this->getFileObjectFor($f)) {
return $obj['ContentLength'];
} else {
return -1;
}
}


/**
* @param File|string $f
* @return \Guzzle\Http\EntityBody
*/
protected function getFileObjectFor(File $f) {
try {
$result = $this->client->getObject(array(
'Bucket' => $this->containerName,
'Key' => $this->getRelativeLinkFor($f)
));
return $result;
} catch (\Aws\S3\Exception\NoSuchKeyException $e) {
return -1;
}
}
const CONTAINER = 'Container';
const REGION = 'Region';
const API_KEY = 'ApiKey';
const API_SECRET = 'ApiSecret';
const FORCE_DL = 'ForceDownload';

protected $client;


/**
* @param string $path
* @param array $cfg
* @throws Exception
*/
public function __construct($path, array $cfg=array())
{
parent::__construct($path, $cfg);
if (empty($cfg[self::CONTAINER])) {
throw new Exception('S3Bucket: missing configuration key - ' . self::CONTAINER);
}
if (empty($cfg[self::REGION])) {
throw new Exception('S3Bucket: missing configuration key - ' . self::REGION);
}
if (empty($cfg[self::API_KEY])) {
throw new Exception('S3Bucket: missing configuration key - ' . self::API_KEY);
}
if (empty($cfg[self::API_SECRET])) {
throw new Exception('S3Bucket: missing configuration key - ' . self::API_SECRET);
}
$this->containerName = $this->config[self::CONTAINER];

$this->client = S3Client::factory(array(
'key' => $this->config[self::API_KEY],
'secret' => $this->config[self::API_SECRET],
'region' => $this->config[self::REGION]
));
}


/**
* @param File $f
* @throws Exception
*/
public function put(File $f)
{
$fp = fopen($f->getFullPath(), 'r');
if (!$fp) {
throw new Exception('Unable to open file: ' . $f->getFilename());
}

$uploader = UploadBuilder::newInstance()
->setClient($this->client)
->setSource($f->getFullPath())
->setBucket($this->containerName)
->setKey($this->getRelativeLinkFor($f))
->build();

try {
$uploader->upload();
} catch (MultipartUploadException $e) {
$uploader->abort();
}
}


/**
* @param File|string $f
*/
public function delete($f)
{
$this->client->deleteObject(array(
'Bucket' => $this->containerName,
'Key' => $this->getRelativeLinkFor($f),
));
}

/**
* @param File $f
* @param string $beforeName - contents of the Filename property (i.e. relative to site root)
* @param string $afterName - contents of the Filename property (i.e. relative to site root)
*/
public function rename(File $f, $beforeName, $afterName)
{
$obj = $this->getFileObjectFor($beforeName);
$result = $this->client->copyObject(array(
'Bucket' => $this->containerName,
'CopySource' => urlencode($this->containerName . '/' . $this->getRelativeLinkFor($beforeName)),
'Key' => $this->getRelativeLinkFor($afterName),
));
if ($result) {
$this->client->deleteObject(array(
'Bucket' => $this->containerName,
'Key' => $this->getRelativeLinkFor($beforeName),
));
}
}


/**
* @param File $f
* @return string
*/
public function getContents(File $f)
{
$obj = $this->getFileObjectFor($f);
return $obj['Body'];
}


/**
* This version just returns a normal link. I'm assuming most
* buckets will implement this but I want it to be optional.
* NOTE: I'm not sure how reliably this is working.
*
* @param File|string $f
* @param int $expires [optional] - Expiration time in seconds
* @return string
*/
public function getTemporaryLinkFor($f, $expires=3600)
{
$obj = $this->getFileObjectFor($this->getRelativeLinkFor($f));
return $obj['Body']->getUri();
}


/**
* @param $f - File object or filename
* @return bool
*/
public function checkExists(File $f)
{
return $this->client->doesObjectExist(
$this->containerName,
$this->getRelativeLinkFor($f)
);
}


/**
* @param $f - File object or filename
* @return int - if file doesn't exist, returns -1
*/
public function getFileSize(File $f)
{
if ($obj = $this->getFileObjectFor($f)) {
return $obj['ContentLength'];
} else {
return -1;
}
}


/**
* @param File|string $f
* @return \Guzzle\Http\EntityBody
*/
protected function getFileObjectFor(File $f)
{
try {
$result = $this->client->getObject(array(
'Bucket' => $this->containerName,
'Key' => $this->getRelativeLinkFor($f)
));
return $result;
} catch (\Aws\S3\Exception\NoSuchKeyException $e) {
return -1;
}
}
}
61 changes: 33 additions & 28 deletions tests/CloudAssetsS3Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,37 @@
* Requires a mapping in your config that includes assets/Uploads
*/

class CloudAssetsS3Test extends SapphireTest {

private $bucket;

function setUp() {
$this->bucket = CloudAssets::inst()->map('assets/Uploads/CloudAssetsS3Test.txt');
parent::setUpOnce();
}

function testInstantiateBucket() {
$this->assertInstanceOf('S3Bucket', $this->bucket);
}

function testGetFile() {
if(!$this->bucket) $this->fail('Cannot continue without valid bucket');

$file = new File();
$file->setName('S3ConnectionTest.txt');
try {
$size = $this->bucket->getFileSize($file);
} catch(Exception $e) {
$error = true;
echo $e->getMessage();
}

$this->assertFalse(isset($error));
}

class CloudAssetsS3Test extends SapphireTest
{

private $bucket;

public function setUp()
{
$this->bucket = CloudAssets::inst()->map('assets/Uploads/CloudAssetsS3Test.txt');
parent::setUpOnce();
}

public function testInstantiateBucket()
{
$this->assertInstanceOf('S3Bucket', $this->bucket);
}

public function testGetFile()
{
if (!$this->bucket) {
$this->fail('Cannot continue without valid bucket');
}

$file = new File();
$file->setName('S3ConnectionTest.txt');
try {
$size = $this->bucket->getFileSize($file);
} catch (Exception $e) {
$error = true;
echo $e->getMessage();
}

$this->assertFalse(isset($error));
}
}