/** * Merge all chunks to single file * * @param string $destination final file location * * * @throws FileLockException * @throws FileOpenException * @throws \Exception * * @return bool indicates if file was saved */ public function save($destination) { $fh = fopen($destination, 'wb'); if (!$fh) { throw new FileOpenException('failed to open destination file: ' . $destination); } if (!flock($fh, LOCK_EX | LOCK_NB, $blocked)) { // @codeCoverageIgnoreStart if ($blocked) { // Concurrent request has requested a lock. // File is being processed at the moment. // Warning: lock is not checked in windows. return false; } // @codeCoverageIgnoreEnd throw new FileLockException('failed to lock file: ' . $destination); } $totalChunks = $this->request->getTotalChunks(); try { $preProcessChunk = $this->config->getPreprocessCallback(); for ($i = 1; $i <= $totalChunks; $i++) { $file = $this->getChunkPath($i); $chunk = fopen($file, "rb"); if (!$chunk) { throw new FileOpenException('failed to open chunk: ' . $file); } if ($preProcessChunk !== null) { call_user_func($preProcessChunk, $chunk); } stream_copy_to_stream($chunk, $fh); fclose($chunk); } } catch (\Exception $e) { flock($fh, LOCK_UN); fclose($fh); throw $e; } if ($this->config->getDeleteChunksOnSave()) { $this->deleteChunks(); } flock($fh, LOCK_UN); fclose($fh); return true; }