<?php
namespace Pion\Laravel\ChunkUpload\Save;
use Illuminate\Http\UploadedFile;
use Pion\Laravel\ChunkUpload\Config\AbstractConfig;
use Pion\Laravel\ChunkUpload\Exceptions\ChunkSaveException;
use Pion\Laravel\ChunkUpload\FileMerger;
use Pion\Laravel\ChunkUpload\Handler\AbstractHandler;
use Pion\Laravel\ChunkUpload\Storage\ChunkStorage;
use Symfony\Component\HttpKernel\Kernel as SymfonyKernel;
class ChunkSave extends AbstractSave
{
/**
* Is this the final chunk?
*
* @var bool
*/
protected $isLastChunk;
/**
* What is the chunk file name.
*
* @var string
*/
protected $chunkFileName;
/**
* The chunk file path.
*
* @var string
*/
protected $chunkFullFilePath = null;
/**
* @var UploadedFile|null
*/
protected $fullChunkFile;
/**
* @var ChunkStorage
*/
protected $chunkStorage;
/**
* AbstractUpload constructor.
*
* @param UploadedFile $file the uploaded file (chunk file)
* @param AbstractHandler $handler the handler that detected the correct save method
* @param ChunkStorage $chunkStorage the chunk storage
* @param AbstractConfig $config the config manager
*
* @throws ChunkSaveException
*/
public function __construct(UploadedFile $file, AbstractHandler $handler, $chunkStorage, $config)
{
parent::__construct($file, $handler, $config);
$this->chunkStorage = $chunkStorage;
$this->isLastChunk = $handler->isLastChunk();
$this->chunkFileName = $handler->getChunkFileName();
// build the full disk path
$this->chunkFullFilePath = $this->getChunkFilePath(true);
$this->handleChunk();
}
/**
* Checks if the file upload is finished (last chunk).
*
* @return bool
*/
public function isFinished()
{
return parent::isFinished() && $this->isLastChunk;
}
/**
* Returns the chunk file path in the current disk instance.
*
* @param bool $absolutePath
*
* @return string
*/
public function getChunkFilePath($absolutePath = false)
{
return $this->getChunkDirectory($absolutePath).$this->chunkFileName;
}
/**
* Returns the full file path.
*
* @return string
*/
public function getChunkFullFilePath()
{
return $this->chunkFullFilePath;
}
/**
* Returns the folder for the cunks in the storage path on current disk instance.
*
* @param bool $absolutePath
*
* @return string
*/
public function getChunkDirectory($absolutePath = false)
{
$paths = [];
if ($absolutePath) {
$paths[] = $this->chunkStorage()->getDiskPathPrefix();
}
$paths[] = $this->chunkStorage()->directory();
return implode('', $paths);
}
/**
* Returns the uploaded file if the chunk if is not completed, otherwise passes the
* final chunk file.
*
* @return UploadedFile|null
*/
public function getFile()
{
if ($this->isLastChunk) {
return $this->fullChunkFile;
}
return parent::getFile();
}
/**
* @deprecated
* @since v1.1.8
*/
protected function handleChunkMerge()
{
$this->handleChunk();
}
/**
* Appends the new uploaded data to the final file.
*
* @throws ChunkSaveException
*/
protected function handleChunk()
{
// prepare the folder and file path
$this->createChunksFolderIfNeeded();
$file = $this->getChunkFilePath();
$this->handleChunkFile($file)
->tryToBuildFullFileFromChunks();
}
/**
* Checks if the current chunk is last.
*
* @return $this
*/
protected function tryToBuildFullFileFromChunks()
{
// build the last file because of the last chunk
if ($this->isLastChunk) {
$this->buildFullFileFromChunks();
}
return $this;
}
/**
* Appends the current uploaded file to chunk file.
*
* @param string $file Relative path to chunk
*
* @return $this
*
* @throws ChunkSaveException
*/
protected function handleChunkFile($file)
{
// delete the old chunk
if ($this->handler()->isFirstChunk() && $this->chunkDisk()->exists($file)) {
$this->chunkDisk()->delete($file);
}
// Append the data to the file
(new FileMerger($this->getChunkFullFilePath()))
->appendFile($this->file->getPathname())
->close();
return $this;
}
/**
* Builds the final file.
*/
protected function buildFullFileFromChunks()
{
// try to get local path
$finalPath = $this->getChunkFullFilePath();
// build the new UploadedFile
$this->fullChunkFile = $this->createFullChunkFile($finalPath);
}
/**
* Creates the UploadedFile object for given chunk file.
*
* @param string $finalPath
*
* @return UploadedFile
*/
protected function createFullChunkFile($finalPath)
{
// We must pass the true as test to force the upload file
// to use a standard copy method, not move uploaded file
$test = true;
$clientOriginalName = $this->file->getClientOriginalName();
$clientMimeType = $this->file->getClientMimeType();
$error = $this->file->getError();
// Passing a size as 4th (filesize) argument to the constructor is deprecated since Symfony 4.1.
if (SymfonyKernel::VERSION_ID >= 40100) {
return new UploadedFile($finalPath, $clientOriginalName, $clientMimeType, $error, $test);
}
$fileSize = filesize($finalPath);
return new UploadedFile($finalPath, $clientOriginalName, $clientMimeType, $fileSize, $error, $test);
}
/**
* Returns the current chunk storage.
*
* @return ChunkStorage
*/
public function chunkStorage()
{
return $this->chunkStorage;
}
/**
* Returns the disk adapter for the chunk.
*
* @return \Illuminate\Filesystem\FilesystemAdapter
*/
public function chunkDisk()
{
return $this->chunkStorage()->disk();
}
/**
* Crates the chunks folder if doesn't exists. Uses recursive create.
*/
protected function createChunksFolderIfNeeded()
{
$path = $this->getChunkDirectory(true);
// creates the chunks dir
if (!file_exists($path)) {
mkdir($path, 0777, true);
}
}
}