Commit 9bc6f8f7 authored by Guillaume Perréal's avatar Guillaume Perréal
Browse files

Encapsule complétement l'accès aux données des UploadedFile dans l'objet métier.

Showing with 138 additions and 54 deletions
+138 -54
...@@ -102,43 +102,34 @@ class UploadController extends Controller ...@@ -102,43 +102,34 @@ class UploadController extends Controller
{ {
$this->validateToken($request); $this->validateToken($request);
$range = $request->headers->get('Content-Range'); if(null !== $range = $request->headers->get('Content-Range', null)) {
$final = true;
if($range) {
$matches = []; $matches = [];
if(!preg_match('@^bytes (\d+)-(\d+)/(\d+)$@', $range, $matches)) { if(!preg_match('@^bytes (\d+)-(\d+)/(\d+)$@', $range, $matches)) {
throw new BadRequestHttpException("Invalid Content-Range"); throw new BadRequestHttpException("Invalid Content-Range");
} }
$start = intval($matches[1]); $start = intval($matches[1]);
$end = intval($matches[2]); $end = intval($matches[2]);
$total = intval($matches[3]); $total = intval($matches[3]);
if($start < 0 || $start >= $end || $end >= $total) {
throw new HttpException(Response::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE);
}
$stream = $file->open('ab'); $offset = $start;
$stream->seek($start); $maxlen = 1 + ($end - $start);
$complete = $end === ($total-1);
$final = $end === ($total-1);
} else { } else {
$stream = $file->open('wb'); $offset = 0;
$maxlen = -1;
$complete = true;
} }
// Demande un filehandle plutôt que charger le contenu en mémoire // Demande un filehandle plutôt que charger le contenu en mémoire
$input = $request->getContent(true); $input = $request->getContent(true);
$copied = $file->copyFrom($input, $maxlen, $offset);
if(false !== $fileHandler = $stream->cast(STREAM_CAST_AS_STREAM)) {
// Utilise stream_copy_to_stream si le Gaufrette\Stream peut nous retourner un filehandle
stream_copy_to_stream($input, $fileHandler);
} else {
// Sinon fait une copie par blocs (moins performant)
while(!feof($input)) {
$stream->write(fread($input, 8192));
}
}
fclose($input); fclose($input);
$stream->close();
if(!$final) { if(!$complete) {
return $this->createResponse(Response::HTTP_OK, 'Chunk received', ['range' => compact('start', 'end', 'total')]); return $this->createResponse(Response::HTTP_OK, 'Chunk received', ['range' => compact('start', 'end', 'total')]);
} }
......
...@@ -10,7 +10,6 @@ namespace Irstea\FileUploadBundle\Entity; ...@@ -10,7 +10,6 @@ namespace Irstea\FileUploadBundle\Entity;
use DateTime; use DateTime;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Gaufrette\Filesystem; use Gaufrette\Filesystem;
use Gaufrette\Stream;
use Gaufrette\StreamMode; use Gaufrette\StreamMode;
use InvalidArgumentException; use InvalidArgumentException;
use Rhumsaa\Uuid\Uuid; use Rhumsaa\Uuid\Uuid;
...@@ -21,6 +20,9 @@ use Rhumsaa\Uuid\Uuid; ...@@ -21,6 +20,9 @@ use Rhumsaa\Uuid\Uuid;
*/ */
class UploadedFile class UploadedFile
{ {
// Taille de bloc utilisé pour les copies
static public $copyBlockSize = 8192;
const ETAT_EN_COURS = 'en-cours'; const ETAT_EN_COURS = 'en-cours';
const ETAT_ORPHELIN = 'orphelin'; const ETAT_ORPHELIN = 'orphelin';
const ETAT_NORMAL = 'normal'; const ETAT_NORMAL = 'normal';
...@@ -348,32 +350,140 @@ class UploadedFile ...@@ -348,32 +350,140 @@ class UploadedFile
return $this->getEtat() === self::ETAT_ORPHELIN; return $this->getEtat() === self::ETAT_ORPHELIN;
} }
/** /** Retourne la date de dernière modification dans le filesystem.
* *
* @param type $mode * @return \DateTime
* @return Stream
*/ */
public function open($mode = 'rb') public function getLastModified()
{ {
$stream = $this->filesystem->createStream($this->getPath()); return new \DateTime(sprintf('@%d', $this->filesystem->mtime($this->getPath())));
$stream->open(new StreamMode($mode));
return $stream;
} }
/** /** Retourne le contenu du fichier.
* *
* @return string * @return string Une chaîne si $asResource vaut faux,
*/ */
public function getContent() public function getContent()
{ {
return $this->filesystem->read($this->getPath()); return $this->filesystem->read($this->getPath());
} }
/** /** Ecrit dans le fichier.
* @return \DateTime *
* @param string $content
*/ */
public function getLastModified() public function setContent($content)
{ {
return new \DateTime(sprintf('@%d', $this->filesystem->mtime($this->getPath()))); return $this->filesystem->write($this->getPath(), $content, true);
}
/** Ecrit dans le fichier depuis un descripteur de fichier.
*
* @param resource $source
* @param int $maxlen
* @param int $writeOffset
* @return int
*/
public function copyFrom($source, $maxlen = PHP_INT_MAX, $writeOffset = 0)
{
if($maxlen === 0) {
return 0;
}
$stream = $this->filesystem->createStream($this->getPath(), new StreamMode('cb'));
$stream->seek($writeOffset);
if(false !== $fileHandler = $stream->cast(STREAM_CAST_AS_STREAM)) {
// Utilise stream_copy_to_stream si le Gaufrette\Stream peut nous retourner un filehandle
$copied = $this->stream_copy_to_stream($source, $fileHandler, $maxlen);
} else {
// Sinon fait une copie par blocs (moins performant)
$copied = 0;
while(!$this->feof($source) && $copied <= $maxlen) {
$copied += $stream->write($this->fread($source, min(static::$copyBlockSize, $maxlen - $copied)));
}
}
$stream->close();
return $copied;
}
/** Envoie le contenu du fichier dan un descripteur de fichier.
*
* @param $dest $resource
* @param int $maxlen
* @param int $readOffset
* @return int
*/
public function copyTo($dest, $maxlen = PHP_INT_MAX, $readOffset = 0)
{
$actualLength = min($maxlen, $this->getSize() - $readOffset);
if (0 <= $actualLength) {
return 0;
}
$stream = $this->filesystem->createStream($this->getPath(), new StreamMode('rb'));
$stream->seek($readOffset);
if(false !== $fileHandle = $stream->cast(STREAM_CAST_AS_STREAM)) {
// Utilise stream_copy_to_stream si le Stream nous renvoie un filehandle
$copied = $this->stream_copy_to_stream($fileHandle, $dest, $actualLength);
} else {
// Sinon, on fait ça à la main par blocs de 8ko
$copied = 0;
while(!$stream->eof() && $copied < $actualLength) {
$copied += $this->fwrite($dest, $stream->read(min(static::$copyBlockSize, $actualLength - $copied)));
}
}
$stream->close();
return $copied;
}
/** Wrapper de stream_copy_to_stream
*
* @param resource $source
* @param resource $dest
* @param int $maxlen
* @param int $offset
* @return int
*/
protected function stream_copy_to_stream($source, $dest, $maxlen = -1, $offset = 0)
{
return stream_copy_to_stream($source, $dest, $maxlen, $offset);
}
/** Wrapper de feof
*
* @param resource $fh
* @return boolean
*/
protected function feof($fh)
{
return feof($fh);
}
/** Wrapper de fread
*
* @param resource $fh
* @param int $maxlen
* @return int|boolean
*/
protected function fread($fh, $maxlen = -1)
{
return fread($fh, $maxlen);
}
/** Wrapper de fwrite
*
* @param resource $fh
* @param int $maxlen
* @return int|boolean
*/
protected function fwrite($fh, $maxlen = -1)
{
return fwrite($fh, $maxlen);
} }
} }
...@@ -203,24 +203,7 @@ class UploadedFileResponse extends Response ...@@ -203,24 +203,7 @@ class UploadedFileResponse extends Response
} }
$out = fopen('php://output', 'wb'); $out = fopen('php://output', 'wb');
$stream = $this->file->open('rb'); $this->file->copyTo($out, $this->maxlen, $this->offset);
if(false !== $fileHandle = $stream->cast(STREAM_CAST_AS_STREAM)) {
// Utilise stream_copy_to_stream si le Stream nous renvoie un filehandle
stream_copy_to_stream($fileHandle, $out, $this->maxlen, $this->offset);
} else {
// Sinon, on fait ça à la main par blocs de 8ko
$stream->seek($this->offset);
for($i = floor($this->maxlen / 8192); $i > 0; $i--) {
fwrite($out, $stream->read(8192));
}
$remaining = $this->maxlen % 8192;
if($remaining > 0) {
fwrite($out, $stream->read($remaining));
}
}
$stream->close();
fclose($out); fclose($out);
} }
......
Supports Markdown
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