Commit 59b04e04 authored by Keven's avatar Keven
Browse files

Fix #11 Chunk files when too big

parent b7b1830f
......@@ -6,7 +6,8 @@
"prefer-stable": true,
"require": {
"psr/log": "~1.0",
"jms/serializer": "~0.16"
"jms/serializer": "~0.16",
"symfony/options-resolver": "~2.0"
},
"require-dev": {
"sami/sami": "~3.0"
......
......@@ -71,6 +71,18 @@ abstract class Command implements CommandInterface
*/
protected $client;
/**
*
* @var boolean
*/
protected $prepared = false;
/**
*
* @var string
*/
protected $url;
/**
*
* @param string $name
......@@ -140,39 +152,49 @@ abstract class Command implements CommandInterface
}
/**
* Prepare the command to send
*
* @param string $baseUrl
* @param \Libcast\Client\Http\CurlCLient $client
* @param SerializerInterface $serializer
* @return string
*
* @return Command
*/
public function execute($baseUrl, $client, SerializerInterface $serializer)
protected function prepare($baseUrl)
{
$url = $this->fullPath ? $this->fullPath : $baseUrl.$this->resolvepath($this->path, $this->vars);
$this->url = $this->fullPath ? $this->fullPath : $baseUrl.$this->resolvepath($this->path, $this->vars);
if ($this->queryData) {
$url .= '?'.http_build_query($this->queryData);
$this->url .= '?'.http_build_query($this->queryData);
}
if (in_array($this->method, array('HEAD', 'GET'))) {
$transaction = $client->getHeader('X-Transaction');
$client->removeHeader('X-Transaction');
}
$this->prepared = true;
/* @var $response Response */
$response = $client->request($this->method, $url, $this->getPayload(), $this->getRequestHeaders(), $this->getRequestOptions());
return $this;
}
if (!empty($transaction)) {
$client->setHeader('X-Transaction', $transaction);
/**
*
* @param \Libcast\Client\Http\CurlCLient $client $client
*
* @return Response
*/
protected function send($client)
{
if (!$this->prepared) {
throw new \RuntimeException('Command must be prepared before sending.');
}
switch ($response->getStatusCode()) {
case 100: return $this->getTransactionResult();
case 204: return true;
case 401: throw new UnauthorizedException($response->getReasonPhrase());
case 403: throw new ForbiddenException($response->getReasonPhrase());
case 404: throw new NotFoundException($response->getReasonPhrase());
case 500: throw new ServerException($response->getReasonPhrase());
}
return $client->request($this->method, $this->url, $this->getPayload(), $this->getRequestHeaders(), $this->getRequestOptions());
}
/**
*
* @param string $baseUrl
* @param \Libcast\Client\Http\CurlCLient $client
* @param SerializerInterface $serializer
* @return string
*/
public function execute($baseUrl, $client, SerializerInterface $serializer)
{
$response = $this->prepare($baseUrl)->send($client);
return $this->process($response, $serializer);
}
......@@ -212,10 +234,20 @@ abstract class Command implements CommandInterface
*
* @param Response $response
* @param SerializerInterface $serializer
* @return array
*
* @return mixed
*/
protected function process($response, $serializer)
{
switch ($response->getStatusCode()) {
case 100: return $this->getTransactionResult();
case 204: return true;
case 401: throw new UnauthorizedException($response->getReasonPhrase());
case 403: throw new ForbiddenException($response->getReasonPhrase());
case 404: throw new NotFoundException($response->getReasonPhrase());
case 500: throw new ServerException($response->getReasonPhrase());
}
if ($this->collectionResponse) {
return new Collection($response->getContent(), $this->objectClass, $this->client, $serializer);
} else {
......@@ -246,4 +278,4 @@ abstract class Command implements CommandInterface
{
return substr(strrchr($href, "/"), 1);
}
}
\ No newline at end of file
}
......@@ -5,13 +5,28 @@ namespace Libcast\Client\Command;
use Libcast\Client\Model\FolderInterface;
use Libcast\Client\Exception\BadParameterException;
use Symfony\Component\OptionsResolver\OptionsResolver;
use JMS\Serializer\SerializerInterface;
class SendFile extends FileCommand
{
/**
*
* @var resource stream
* @var array cURL options
*/
private $requestOptions = array();
/**
*
* @var array Command options
*/
private $options;
/**
*
* @var int
*/
private $options = array();
private $fileSize;
/**
*
......@@ -23,9 +38,13 @@ class SendFile extends FileCommand
*
* @param string $name
* @param string $path Path is either an absolute path to the file or a stream resource
* @param mixed $folder Folder into which upload the file
* @param array $options Upload options
*/
public function __construct($name, $path = null, $folder = null)
public function __construct($name, $path = null, $folder = null, $options = [])
{
$this->resolveOptions($options);
//  `sendFile('/path/to/file')` is possible via this hack
if (is_null($path)) {
$path = $name;
......@@ -44,34 +63,134 @@ class SendFile extends FileCommand
}
if (is_readable($path)) { // Local file
$this->fileSize = filesize($path);
$fields = array('path' => "@$path", 'name' => $name);
if (!is_null($slug)) {
$fields['folder'] = $slug;
}
$this->options[CURLOPT_POSTFIELDS] = $fields;
$this->requestOptions[CURLOPT_POSTFIELDS] = $fields;
$this->setPayload(array('name' => $name, 'folder' => $slug));
} elseif (!is_null(parse_url($path, PHP_URL_HOST))) { // Remote file
$fields = array('path' => "$path", 'name' => $name);
if (isset($slug)) {
$fields['folder'] = $slug;
}
$this->options[CURLOPT_POSTFIELDS] = $fields;
$this->requestOptions[CURLOPT_POSTFIELDS] = $fields;
$this->setPayload($fields);
} elseif (is_resource($path) && ('stream' === get_resource_type($path))) {
$this->options[CURLOPT_POSTFIELDS] = array('name' => $name, 'folder' => $slug);
$this->options[CURLOPT_INFILE] = $path;
} elseif (is_resource($path) && ('stream' === get_resource_type($path))) { // This doesn't work yet...
$this->requestOptions[CURLOPT_POSTFIELDS] = array('name' => $name, 'folder' => $slug);
$this->requestOptions[CURLOPT_INFILE] = $path;
$this->setPayload(array('name' => $name, 'folder' => $slug));
} else {
throw new BadParameterException('Path parameter must be an absolute path to a readable file or a stream resource.');
}
}
/**
*
* @param string $baseUrl
* @param \Libcast\Client\Http\CurlCLient $client
* @param SerializerInterface $serializer
* @return string
*/
public function execute($baseUrl, $client, SerializerInterface $serializer)
{
if (!$this->mustBeChunked()) {
return parent::execute($baseUrl, $client, $serializer);
}
$path = $this->requestOptions[CURLOPT_POSTFIELDS]['path'];
if (false === $chunks = $this->splitFile($path, $this->options['chunk-size'])) {
return false;
}
$this->prepare($baseUrl);
$this->requestOptions[CURLOPT_POSTFIELDS]['chunks'] = count($chunks);
$chunk = 0;
foreach ($chunks as $chunkPath) {
$this->requestOptions[CURLOPT_POSTFIELDS]['chunk'] = $chunk++;
$this->requestOptions[CURLOPT_POSTFIELDS]['path'] = "@$chunkPath";
$response = $this->send($client);
unlink($chunkPath);
}
return $this->process($response, $serializer);
}
/**
*
* @param string $path
* @param int $chunkSize Chunk size in bytes
*
* @return array List of chunk files (FALSE if an error occured)
*/
private function splitFile($path, $chunkSize)
{
if ('@' === $path{0}) {
$path = substr($path, 1);
}
$destination = $path.'-';
$log = [];
$command = sprintf('split -d --verbose -b %s %s %s', $chunkSize, escapeshellarg($path), escapeshellarg($destination));
exec($command, $log, $status);
if (0 !== $status) {
return false;
}
preg_match_all('/creating file `([^\']+)/', implode('', $log), $matches);
return $matches[1];
}
/**
* Whether or not the file must be chunked
*
* @return boolean
*/
private function mustBeChunked()
{
return $this->fileSize and ( // A remote file cannot be chunked...
$this->options['chunked'] or (
$this->options['autochunk'] and
$this->options['chunk-size'] < $this->fileSize
)
);
}
/**
*
* @param array $options
*/
private function resolveOptions(array $options)
{
$resolver = new OptionsResolver();
$this->configureOptions($resolver);
$this->options = $resolver->resolve($options);
}
/**
*
* @param OptionsResolver $resolver
*/
private function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'chunked' => false, // Use chunked upload?
'chunk-size' => 64 * 1024 * 1024, // 64 MiB
'autochunk' => true, // Use chunk upload if file size > chunk-size?
]);
$resolver->setRequired(array('chunked', 'chunk-size', 'autochunk'));
}
/**
*
* @return array
*/
protected function getRequestOptions()
{
return $this->options + parent::getRequestOptions();
return $this->requestOptions + parent::getRequestOptions();
}
}
......@@ -87,15 +87,16 @@ class Folder extends Entity implements FolderInterface
*
* @param string $path File path
* @param string $name File name
* @param array $options Upload options
*
* @return File
*/
public function upload($path, $name = null)
public function upload($path, $name = null, $options = [])
{
if (is_null($name)) {
$file = $this->client->upload($path, null, $this);
$file = $this->client->upload($path, null, $this, $options);
} else {
$file = $this->client->upload($name, $path, $this);
$file = $this->client->upload($name, $path, $this, $options);
}
return $file;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment