19declare(strict_types=1);
45 private const CHUNK_DIRECTORY =
'irss_chunked_uploads';
68 $this->chunk_filesystem =
$DIC->filesystem()->temp();
70 $this->chunk_language =
$DIC->language();
71 $this->chunk_user_id =
$DIC->user()->getId();
81 $body =
$DIC->http()->request()->getParsedBody();
92 $this->is_chunked =
true;
95 $this->chunk_index = (
int) (
$body[
'dzchunkindex'] ?? 0);
96 $this->chunk_byte_offset = (
int) (
$body[
'dzchunkbyteoffset'] ?? 0);
97 $this->chunk_total_size = (
int) (
$body[
'dztotalfilesize'] ?? 0);
117 $result = end($upload_results);
119 return $this->
chunkFailed(
'the request carried no upload result');
121 if (!$result->isOK()) {
122 $this->
logChunkFailure(
'rejected while processing: ' . $result->getStatus()->getMessage());
134 if (!$this->chunk_filesystem->has($part_file)) {
135 $this->chunk_filesystem->write($part_file,
'');
142 clearstatcache(
true, $part_path);
143 $buffered = filesize($part_path);
144 if ($buffered !== $this->chunk_byte_offset) {
146 "chunk starts at byte $this->chunk_byte_offset but $buffered bytes are buffered"
152 if (($this->chunk_index + 1) < $this->amount_of_chunks) {
161 clearstatcache(
true, $part_path);
162 $assembled_size = filesize($part_path);
163 if ($assembled_size !== $this->chunk_total_size) {
165 "assembled $assembled_size bytes, the upload announced $this->chunk_total_size"
174 $file_name = basename($result->getName());
175 $assembly_directory = dirname($part_path) .
'/file';
176 $assembled_path = $assembly_directory .
'/' . $file_name;
178 if (!is_dir($assembly_directory) && !mkdir($assembly_directory, 0700,
true) && !is_dir($assembly_directory)) {
179 return $this->
chunkFailed(
"could not create the directory '$assembly_directory'");
181 if (!rename($part_path, $assembled_path)) {
182 return $this->
chunkFailed(
"could not move the assembled file to '$assembled_path'");
185 $identifier = $store($assembled_path, $file_name);
186 if ($identifier ===
null) {
187 return $this->
chunkFailed(
'the assembled file was not stored');
198 }
catch (\Throwable $exception) {
199 return $this->
chunkFailed($exception::class .
': ' . $exception->getMessage());
209 $message !==
'' ?
$message : $this->chunk_language->txt(
'rids_appended_failed')
219 return self::CHUNK_DIRECTORY .
'/' . $this->chunk_user_id .
'/' .
$this->uuid;
224 $stream = $this->chunk_filesystem->readStream($path_in_temp_filesystem);
225 $uri = $stream->getMetadata()[
'uri'];
228 return (
string) $uri;
231 private function appendToFile(
string $source_path,
string $target_path): void
233 $source = fopen($source_path,
'rb');
234 $target = fopen($target_path,
'ab');
237 if ($source ===
false ||
$target ===
false) {
238 throw new \RuntimeException(
"Could not append '$source_path' to '$target_path'.");
240 stream_copy_to_stream($source,
$target);
242 if ($source !==
false) {
259 if ($this->chunk_filesystem->hasDir($this->getChunkDirectory())) {
262 }
catch (\Throwable) {
282 $this->chunk_logger->warning(sprintf(
283 'chunked upload %s of user %d failed on chunk %d of %d: %s',
285 $this->chunk_user_id,
286 $this->chunk_index + 1,
287 $this->amount_of_chunks,
Class BasicHandlerResult.
static getLogger(string $a_component_id)
Get component logger.
The filesystem interface provides the public interface for the Filesystem service API consumer.
The filename sanitizer verifies and fixes file name endings.
const CLEAN_FILE_SUFFIX
This file suffix will be used to sanitize not whitelisted file names.
initChunkedUploadBuffer()
trait ChunkedUploadBuffer
Reassembles an upload the dropzone sent in chunks.
logChunkFailure(string $reason)
readChunkInformation()
The dropzone announces a chunked upload with these fields in the body of every single chunk request.
chunkFailed(string $reason)
Every way a chunked upload can fail ends up here.
getFileIdentifierParameterName()
Provided by the UploadHandler both users of this trait implement.
appendToFile(string $source_path, string $target_path)
assembleChunk(array $upload_results, callable $store)
Buffers the chunk of the current request and, once it was the last one, hands the reassembled file to...
failedResult(string $message='')
ilLanguage $chunk_language
getLocalPath(string $path_in_temp_filesystem)
getChunkDirectory()
Chunks are kept apart per user: the uuid identifying an upload is chosen by the client and must not a...
discardChunks(BasicHandlerResult $result)
Removes everything buffered for the current upload, whether it completed or failed.
Filesystem $chunk_filesystem