PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 3.8.4
WP STAGING – WordPress Backups, Restore, Migration & Clone v3.8.4
4.11.2 4.11.1 4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Backup / Service / Archiver.php
wp-staging / Backup / Service Last commit date
Compression 2 years ago Database 2 years ago AbstractServiceProvider.php 2 years ago Archiver.php 2 years ago BackupAssets.php 2 years ago BackupMetadataEditor.php 3 years ago BackupSigner.php 2 years ago BackupsFinder.php 2 years ago Extractor.php 2 years ago FileBackupService.php 2 years ago FileBackupServiceProvider.php 2 years ago ServiceInterface.php 2 years ago ZlibCompressor.php 2 years ago
Archiver.php
682 lines
1 <?php
2
3 // TODO PHP7.1; constant visibility
4
5 namespace WPStaging\Backup\Service;
6
7 use Exception;
8 use LogicException;
9 use RuntimeException;
10 use WPStaging\Backup\BackupFileIndex;
11 use WPStaging\Backup\BackupHeader;
12 use WPStaging\Backup\Dto\Job\JobBackupDataDto;
13 use WPStaging\Framework\Job\Dto\JobDataDto;
14 use WPStaging\Backup\Dto\Service\ArchiverDto;
15 use WPStaging\Backup\Entity\BackupMetadata;
16 use WPStaging\Backup\Exceptions\BackupSkipItemException;
17 use WPStaging\Framework\Job\Exception\DiskNotWritableException;
18 use WPStaging\Framework\Job\Exception\NotFinishedException;
19 use WPStaging\Backup\FileHeader;
20 use WPStaging\Core\WPStaging;
21 use WPStaging\Framework\Adapter\Directory;
22 use WPStaging\Framework\Adapter\PhpAdapter;
23 use WPStaging\Framework\Filesystem\PathIdentifier;
24 use WPStaging\Framework\Utils\Cache\BufferedCache;
25 use WPStaging\Framework\Traits\EndOfLinePlaceholderTrait;
26 use WPStaging\Vendor\lucatume\DI52\NotFoundException;
27
28 use function WPStaging\functions\debug_log;
29
30 /**
31 * This class is responsible for archiving files and creating backups.
32 */
33 class Archiver
34 {
35 use EndOfLinePlaceholderTrait;
36
37 /** @var string */
38 const BACKUP_DIR_NAME = 'backups';
39
40 /** @var bool */
41 const CREATE_BINARY_HEADER = true;
42
43 /** @var BufferedCache */
44 protected $tempBackupIndex;
45
46 /** @var BufferedCache */
47 protected $tempBackup;
48
49 /** @var ArchiverDto */
50 protected $archiverDto;
51
52 /** @var PathIdentifier */
53 protected $pathIdentifier;
54
55 /** @var int */
56 protected $archivedFileSize = 0;
57
58 /** @var JobDataDto */
59 protected $jobDataDto;
60
61 /** @var PhpAdapter */
62 protected $phpAdapter;
63
64 /** @var bool */
65 protected $isLocalBackup = false;
66
67 /** @var int */
68 protected $bytesWrittenInThisRequest = 0;
69
70 /** @var FileHeader */
71 protected $fileHeader;
72
73 /** @var BackupHeader */
74 protected $backupHeader;
75
76 /** @var BackupFileIndex */
77 protected $backupFileIndex;
78
79 public function __construct(
80 BufferedCache $cacheIndex,
81 BufferedCache $tempBackup,
82 PathIdentifier $pathIdentifier,
83 JobDataDto $jobDataDto,
84 ArchiverDto $archiverDto,
85 PhpAdapter $phpAdapter,
86 BackupFileIndex $backupFileIndex,
87 FileHeader $fileHeader,
88 BackupHeader $backupHeader
89 ) {
90 $this->jobDataDto = $jobDataDto;
91 $this->archiverDto = $archiverDto;
92 $this->tempBackupIndex = $cacheIndex;
93 $this->tempBackup = $tempBackup;
94 $this->pathIdentifier = $pathIdentifier;
95 $this->phpAdapter = $phpAdapter;
96 $this->backupFileIndex = $backupFileIndex;
97 $this->fileHeader = $fileHeader;
98 $this->backupHeader = $backupHeader;
99 }
100
101 /**
102 * @param bool $isCreateBinaryHeader
103 * @return void
104 */
105 public function createArchiveFile(bool $isCreateBinaryHeader = false)
106 {
107 $this->setupTmpBackupFile();
108
109 if ($isCreateBinaryHeader && !$this->tempBackup->isValid()) {
110 // Create temp file with binary header
111 $this->tempBackup->save($this->isBackupFormatV1() ? $this->backupHeader->getV1FormatHeader() : $this->backupHeader->getHeader() . "\n");
112 }
113 }
114
115 /**
116 * Setup temp backup file and temp files index file for the given job id,
117 * @return void
118 */
119 public function setupTmpBackupFile()
120 {
121 $this->tempBackup->setFilename('temp_wpstg_backup_' . $this->jobDataDto->getId());
122 $this->tempBackup->setLifetime(DAY_IN_SECONDS);
123
124 $tempBackupIndexFilePrefix = 'temp_backup_index_';
125 $this->tempBackupIndex->setFilename($tempBackupIndexFilePrefix . $this->jobDataDto->getId());
126 $this->tempBackupIndex->setLifetime(DAY_IN_SECONDS);
127 }
128
129 /**
130 * @var bool $isLocalBackup
131 */
132 public function setIsLocalBackup(bool $isLocalBackup)
133 {
134 $this->isLocalBackup = $isLocalBackup;
135 }
136
137 /**
138 * @return ArchiverDto
139 */
140 public function getDto(): ArchiverDto
141 {
142 return $this->archiverDto;
143 }
144
145 /**
146 * @return int
147 */
148 public function getBytesWrittenInThisRequest(): int
149 {
150 return $this->bytesWrittenInThisRequest;
151 }
152
153 /**
154 * @return BufferedCache
155 */
156 public function getTempBackupIndex(): BufferedCache
157 {
158 return $this->tempBackupIndex;
159 }
160
161 /**
162 * @return BufferedCache
163 */
164 public function getTempBackup(): BufferedCache
165 {
166 return $this->tempBackup;
167 }
168
169 /**
170 * @param string $fullFilePath
171 * @param string $indexPath
172 *
173 * `true` -> finished
174 * `false` -> not finished
175 *
176 * @throws DiskNotWritableException
177 * @throws RuntimeException
178 * @throws BackupSkipItemException Skip this file don't do anything
179 *
180 * @return bool
181 */
182 public function appendFileToBackup(string $fullFilePath, string $indexPath = ''): bool
183 {
184 // We can use evil '@' as we don't check is_file || file_exists to speed things up.
185 // Since in this case speed > anything else
186 // However if @ is not used, depending on if file exists or not this can throw E_WARNING.
187 $resource = @fopen($fullFilePath, 'rb');
188 if (!$resource) {
189 debug_log("appendFileToBackup(): Can't open file {$fullFilePath} for reading");
190 throw new BackupSkipItemException();
191 }
192
193 if (empty($indexPath)) {
194 $indexPath = $fullFilePath;
195 }
196
197 $indexPath = $this->replaceEOLsWithPlaceholders($indexPath);
198 $fileStats = fstat($resource);
199 $isInitiated = $this->initiateDtoByFilePath($fullFilePath, $fileStats);
200 $this->archiverDto->setIndexPath($indexPath);
201 $fileHeaderBytes = 0;
202 if ($isInitiated && !$this->isBackupFormatV1()) {
203 $fileHeaderBytes = $this->writeFileHeader($fullFilePath, $indexPath);
204 $this->archiverDto->setFileHeaderBytes($fileHeaderBytes);
205 }
206
207 $writtenBytesBefore = $this->archiverDto->getWrittenBytesTotal();
208 $writtenBytesTotal = $this->appendToArchiveFile($resource, $fullFilePath);
209 $newBytesWritten = $writtenBytesTotal + $fileHeaderBytes - $writtenBytesBefore;
210 $writtenBytesIncludingFileHeader = $writtenBytesTotal + $this->archiverDto->getFileHeaderBytes();
211
212 $retries = 0;
213
214 do {
215 if ($retries > 0) {
216 usleep($this->getDelayForRetry($retries));
217 }
218
219 $bytesAddedForIndex = $this->addIndex($writtenBytesIncludingFileHeader, $newBytesWritten);
220 $retries++;
221 } while ($bytesAddedForIndex === 0 && $retries < 3);
222
223 $this->archiverDto->setWrittenBytesTotal($writtenBytesTotal);
224
225 $this->bytesWrittenInThisRequest += $newBytesWritten;
226
227 $isFinished = $this->archiverDto->isFinished();
228
229 $this->archiverDto->resetIfFinished();
230
231 return $isFinished;
232 }
233
234 /**
235 * @param string $filePath
236 * @param array $fileStats
237 * @param bool
238 */
239 public function initiateDtoByFilePath(string $filePath, array $fileStats = []): bool
240 {
241 if (empty($filePath) || ($filePath === $this->archiverDto->getFilePath() && $fileStats['size'] === $this->archiverDto->getFileSize())) {
242 return false;
243 }
244
245 $this->archiverDto->setFilePath($filePath);
246 $this->archiverDto->setFileSize($fileStats['size']);
247 return true;
248 }
249
250 /**
251 * Combines index and archive file, renames / moves it to destination
252 *
253 * This function is called only once, so performance improvements has no impact here.
254 *
255 * @param int $backupSizeBeforeAddingIndex
256 * @param string $finalFileNameOnRename
257 * @param bool $isBackupPart
258 *
259 * @return string
260 */
261 public function generateBackupMetadata(int $backupSizeBeforeAddingIndex = 0, string $finalFileNameOnRename = '', bool $isBackupPart = false): string
262 {
263 clearstatcache();
264 $backupSizeAfterAddingIndex = filesize($this->tempBackup->getFilePath());
265
266 $backupMetadata = $this->archiverDto->getBackupMetadata();
267 $backupMetadata->setHeaderStart($backupSizeBeforeAddingIndex);
268 $backupMetadata->setHeaderEnd($backupSizeAfterAddingIndex);
269
270 if ($isBackupPart) {
271 $this->updateMultipartData($backupMetadata);
272 }
273
274 if ($this->jobDataDto instanceof JobBackupDataDto) {
275 /** @var JobBackupDataDto */
276 $jobDataDto = $this->jobDataDto;
277 $backupMetadata->setIndexPartSize($jobDataDto->getCategorySizes());
278 }
279
280 $this->tempBackup->append(json_encode($backupMetadata));
281 if (!$this->isBackupFormatV1()) {
282 $this->backupHeader->readFromPath($this->tempBackup->getFilePath());
283 $this->backupHeader->setMetadataStartOffset($backupSizeAfterAddingIndex);
284 $this->backupHeader->setMetadataEndOffset($backupSizeAfterAddingIndex);
285 $this->backupHeader->updateHeader($this->tempBackup->getFilePath());
286 }
287
288 return $this->renameBackup($finalFileNameOnRename);
289 }
290
291 /** @return int */
292 public function addFileIndex(): int
293 {
294 clearstatcache();
295 $indexResource = fopen($this->tempBackupIndex->getFilePath(), 'rb');
296
297 if (!$indexResource) {
298 debug_log('[Add File Index] Nothing to backup, no index resource! File Index: ' . $this->tempBackupIndex->getFilePath());
299 throw new NotFoundException('Nothing to backup, no index resource found!');
300 }
301
302 static $isFirstInsert = false;
303 $insertSeparator = '';
304 if ($isFirstInsert === false) {
305 $lastLine = $this->tempBackup->readLastLine();
306 if (!empty($lastLine) && preg_match('@^INSERT\sINTO\s@', $lastLine)) {
307 $isFirstInsert = true;
308 $insertSeparator = "\n--\n-- SQL DATA END\n--\n";
309 $this->tempBackup->append($insertSeparator);
310 $this->tempBackup->deleteBottomBytes(strlen(PHP_EOL));
311 }
312 }
313
314 $indexStats = fstat($indexResource);
315 $this->initiateDtoByFilePath($this->tempBackupIndex->getFilePath(), $indexStats);
316
317 $lastLine = $this->tempBackup->readLastLine();
318 $writtenBytes = $this->archiverDto->getWrittenBytesTotal();
319 if ($lastLine !== PHP_EOL && $writtenBytes === 0) {
320 $this->tempBackup->append(''); // ensure that file index start from new line. See https://github.com/wp-staging/wp-staging-pro/issues/2861
321 }
322
323 clearstatcache();
324 $backupSizeBeforeAddingIndex = filesize($this->tempBackup->getFilePath());
325 $backupIndexFileSize = filesize($this->tempBackupIndex->getFilePath());
326
327 // Write the index to the backup file, regardless of resource limits threshold
328 // @throws Exception
329 $writtenBytes = $this->appendToArchiveFile($indexResource, $this->tempBackupIndex->getFilePath());
330 $this->archiverDto->setWrittenBytesTotal($writtenBytes);
331
332 if ($writtenBytes === 0) {
333 $this->jobDataDto->setRetries($this->jobDataDto->getRetries() + 1);
334 } else {
335 $this->jobDataDto->setRetries(0);
336 }
337
338 // close the index file handle to make it deletable for Windows where PHP < 7.3
339 fclose($indexResource);
340
341 if ($this->jobDataDto->getRetries() > 3) {
342 $indexSize = $backupIndexFileSize === false ? 0 : size_format($backupIndexFileSize, 3);
343 debug_log(sprintf('[Add File Index] Failed to write files-index to backup file! Tmp Size: %s. Index Size: %s', size_format($backupSizeBeforeAddingIndex, 3), $indexSize));
344 throw new Exception(sprintf('Failed to write files-index to backup file! Tmp Size: %s. Index Size: %s', size_format($backupSizeBeforeAddingIndex, 3), $indexSize));
345 } elseif ($writtenBytes === 0) {
346 debug_log('[Add File Index] Failed to write any byte to files-index! Retrying...');
347 }
348
349 if (!$this->archiverDto->isFinished()) {
350 throw new NotFinishedException('File backup is not finished yet!');
351 }
352
353 $this->tempBackupIndex->delete();
354 $this->archiverDto->reset();
355
356 $backupSizeAfterAddingIndex = filesize($this->tempBackup->getFilePath());
357 if (!$this->isBackupFormatV1()) {
358 $this->backupHeader->setFilesIndexStartOffset($backupSizeBeforeAddingIndex);
359 $this->backupHeader->setFilesIndexEndOffset($backupSizeAfterAddingIndex);
360 $this->backupHeader->updateHeader($this->tempBackup->getFilePath());
361 }
362
363 $this->tempBackup->append(PHP_EOL);
364
365 return $backupSizeBeforeAddingIndex;
366 }
367
368 /**
369 * @return string
370 */
371 public function getDestinationPath(): string
372 {
373 $extension = "wpstg";
374
375 return sprintf(
376 '%s_%s_%s.%s',
377 parse_url(get_home_url())['host'],
378 current_time('Ymd-His'),
379 $this->jobDataDto->getId(),
380 $extension
381 );
382 }
383
384 /**
385 * @param string $renameFileTo
386 * @param bool $isLocalBackup
387 * @return string
388 */
389 public function getFinalPath(string $renameFileTo = '', bool $isLocalBackup = true): string
390 {
391 $backupsDirectory = $this->getFinalBackupParentDirectory($isLocalBackup);
392 if ($renameFileTo === '') {
393 $renameFileTo = $this->getDestinationPath();
394 }
395
396 return $backupsDirectory . $renameFileTo;
397 }
398
399 public function getFinalBackupParentDirectory(bool $isLocalBackup = true): string
400 {
401 if ($isLocalBackup) {
402 return WPStaging::make(BackupsFinder::class)->getBackupsDirectory();
403 }
404
405 return WPStaging::make(Directory::class)->getCacheDirectory();
406 }
407
408 /**
409 * @param string $filePath
410 * @param string $indexPath
411 * @return int
412 */
413 protected function writeFileHeader(string $filePath, string $indexPath): int
414 {
415 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($indexPath);
416 $this->fileHeader->readFile($filePath, $identifiablePath);
417
418 return $this->fileHeader->writeFileHeader($this->tempBackup);
419 }
420
421 /**
422 * Get delay in milliseconds for retry according to retry number
423 *
424 * @param int $retry
425 * @return float
426 */
427 protected function getDelayForRetry(int $retry): float
428 {
429 $delay = 0.1;
430 for ($i = 0; $i < $retry; $i++) {
431 $delay *= 2;
432 }
433
434 return $delay * 1000;
435 }
436
437 /**
438 * @param BackupMetadata $backupMetadata
439 * @return void
440 */
441 protected function updateMultipartData(BackupMetadata $backupMetadata)
442 {
443 // Used in Pro
444 }
445
446 /**
447 * @param JobBackupDataDto $jobBackupDataDto
448 * @return void
449 */
450 protected function incrementFileCountForMultipart(JobBackupDataDto $jobBackupDataDto)
451 {
452 // Used in Pro
453 }
454
455 /**
456 * @return void
457 */
458 protected function setIndexPositionCreated()
459 {
460 $this->archiverDto->setIndexPositionCreated(true);
461 }
462
463 /**
464 * @return bool
465 */
466 protected function isIndexPositionCreated(): bool
467 {
468 return $this->archiverDto->isIndexPositionCreated();
469 }
470
471 /**
472 * @throws RuntimeException
473 *
474 * @param string $renameFileTo
475 * @return string
476 */
477 private function renameBackup(string $renameFileTo = ''): string
478 {
479 if ($renameFileTo === '') {
480 $renameFileTo = $this->getDestinationPath();
481 }
482
483 $destination = trailingslashit(dirname($this->tempBackup->getFilePath())) . $renameFileTo;
484 if ($this->isLocalBackup) {
485 $destination = $this->getFinalPath($renameFileTo);
486 }
487
488 if (!rename($this->tempBackup->getFilePath(), $destination)) {
489 throw new RuntimeException('Failed to generate destination');
490 }
491
492 return $destination;
493 }
494
495 /**
496 * @param int $writtenBytesTotal
497 * @param int $newBytesAdded
498 * @return int
499 * @throws \WPStaging\Framework\Exceptions\IOException
500 * @throws LogicException
501 * @throws RuntimeException
502 */
503 private function addIndex(int $writtenBytesTotal, int $newBytesAdded = 0): int
504 {
505 clearstatcache();
506 if (file_exists($this->tempBackup->getFilePath())) {
507 $this->archivedFileSize = filesize($this->tempBackup->getFilePath());
508 }
509
510 $start = max($this->archivedFileSize - $writtenBytesTotal, 0);
511
512 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->archiverDto->getIndexPath());
513 // New Backup format
514 if ($this->isIndexPositionCreated() && !$this->isBackupFormatV1()) {
515 $this->addIndexPartSize($identifiablePath, $newBytesAdded);
516 return $newBytesAdded;
517 }
518
519 // Old backup format
520 if ($this->isIndexPositionCreated()) {
521 return $this->updateIndexInformationForAlreadyAddedIndex($writtenBytesTotal);
522 }
523
524 if ($this->isBackupFormatV1()) {
525 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->archiverDto->getIndexPath());
526 $backupFileIndex = $this->backupFileIndex->createIndex($identifiablePath, $start, $writtenBytesTotal, false);
527 $bytesWritten = $this->tempBackupIndex->append($backupFileIndex->getIndex());
528 } else {
529 $this->fileHeader->setStartOffset($start);
530 $bytesWritten = $this->fileHeader->writeIndexHeader($this->tempBackupIndex);
531 }
532
533 $this->archiverDto->setIndexPositionCreated(true);
534
535 $this->addIndexPartSize($identifiablePath, $writtenBytesTotal);
536
537 /**
538 * We require JobDataDto in the constructor because it is wired in the DI container
539 * to the current job DTO instance. However, here we need to make sure this DTO
540 * is the jobBackupDataDto.
541 */
542 if (!$this->phpAdapter->isCallable([$this->jobDataDto, 'setTotalFiles']) || !$this->phpAdapter->isCallable([$this->jobDataDto, 'getTotalFiles'])) {
543 debug_log('This method can only be called from the context of Backup');
544 throw new LogicException('This method can only be called from the context of Backup');
545 }
546
547 /** @var JobBackupDataDto $jobBackupDataDto */
548 $jobBackupDataDto = $this->jobDataDto;
549 $jobBackupDataDto->setTotalFiles($jobBackupDataDto->getTotalFiles() + 1);
550
551 $this->incrementFileCountForMultipart($jobBackupDataDto);
552
553 return $bytesWritten;
554 }
555
556 /**
557 * @param resource $resource
558 * @param string $filePath
559 *
560 * @return int Bytes written
561 * @throws DiskNotWritableException
562 * @throws RuntimeException
563 */
564 private function appendToArchiveFile($resource, string $filePath): int
565 {
566 try {
567 return $this->tempBackup->appendFile(
568 $resource,
569 $this->archiverDto->getWrittenBytesTotal()
570 );
571 } catch (DiskNotWritableException $e) {
572 debug_log('Failed to write to file: ' . $filePath);
573 // Re-throw for readability
574 throw $e;
575 }
576 }
577
578 /**
579 * @param string $identifiablePath
580 * @param int $newBytesWritten
581 *
582 * @return void
583 */
584 private function addIndexPartSize(string $identifiablePath, int $newBytesWritten)
585 {
586 // Early bail if jobDataDto is not instance of jobBackupDataDto
587 if (!$this->jobDataDto instanceof JobBackupDataDto) {
588 return;
589 }
590
591 /** @var JobBackupDataDto $jobDataDto */
592 $jobDataDto = $this->jobDataDto;
593
594 $collectPartsize = $jobDataDto->getCategorySizes();
595
596 $partName = 'unknownSize';
597 switch ($identifiablePath) {
598 case ($this->pathIdentifier::IDENTIFIER_WP_CONTENT === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_WP_CONTENT))):
599 $partName = 'wpcontentSize';
600 break;
601 case ($this->pathIdentifier::IDENTIFIER_PLUGINS === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_PLUGINS))):
602 $partName = 'pluginsSize';
603 break;
604 case ($this->pathIdentifier::IDENTIFIER_THEMES === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_THEMES))):
605 $partName = 'themesSize';
606 break;
607 case ($this->pathIdentifier::IDENTIFIER_MUPLUGINS === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_MUPLUGINS))):
608 $partName = 'mupluginsSize';
609 break;
610 case ($this->pathIdentifier::IDENTIFIER_UPLOADS === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_UPLOADS))):
611 $partName = 'uploadsSize';
612 if (substr($identifiablePath, -4) === '.sql') {
613 $partName = 'sqlSize';
614 }
615
616 break;
617 case ($this->pathIdentifier::IDENTIFIER_LANG === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_LANG))):
618 $partName = 'langSize';
619 break;
620 case ($this->pathIdentifier::IDENTIFIER_ABSPATH === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_ABSPATH))):
621 $partName = 'wpRootSize';
622 break;
623 }
624
625 // TODO: This should never happen. Log this when we have our own Logger, see https://github.com/wp-staging/wp-staging-pro/pull/2440#discussion_r1247951548
626 if (!isset($collectPartsize[$partName])) {
627 $collectPartsize[$partName] = 0;
628 }
629
630 $collectPartsize[$partName] += $newBytesWritten;
631 $jobDataDto->setCategorySizes($collectPartsize);
632 }
633
634 /**
635 * Used in v1 Backup Format
636 * At the moment this is used when processing adding of big file which is not done in a single request
637 * @param int $writtenBytesTotal
638 * @return int
639 * @throws RuntimeException
640 */
641 private function updateIndexInformationForAlreadyAddedIndex(int $writtenBytesTotal): int
642 {
643 $lastLine = $this->tempBackupIndex->readLines(1, null, BufferedCache::POSITION_BOTTOM);
644 if (!is_array($lastLine)) {
645 debug_log('Failed to read backup metadata file index information. Error: The last line is no array. Last line: ' . $lastLine);
646 throw new RuntimeException('Failed to read backup metadata file index information. Error: The last line is no array.');
647 }
648
649 $lastLine = array_filter($lastLine, [$this->backupFileIndex, 'isIndexLine']);
650
651 if (count($lastLine) !== 1) {
652 debug_log('Failed to read backup metadata file index information. Error: The last line is not an array or element with countable interface. Last line: ' . print_r($lastLine, 1));
653 throw new RuntimeException('Failed to read backup metadata file index information. Error: The last line is not an array or element with countable interface.');
654 }
655
656 $lastLine = array_shift($lastLine);
657
658 $backupFileIndex = $this->backupFileIndex->readIndex($lastLine);
659 $writtenPreviously = $backupFileIndex->bytesEnd;
660
661 $this->tempBackupIndex->deleteBottomBytes(strlen($lastLine));
662
663 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->archiverDto->getIndexPath());
664 $backupFileIndex = $this->backupFileIndex->createIndex($identifiablePath, $backupFileIndex->bytesStart, $writtenBytesTotal, false);
665 $bytesWritten = $this->tempBackupIndex->append($backupFileIndex->getIndex());
666
667 $this->setIndexPositionCreated();
668
669 // We only need to increment newly added bytes
670 $this->addIndexPartSize($identifiablePath, $writtenBytesTotal - (int)$writtenPreviously);
671
672 return $bytesWritten;
673 }
674
675 private function isBackupFormatV1(): bool
676 {
677 /** @var JobBackupDataDto */
678 $jobDataDto = $this->jobDataDto;
679 return $jobDataDto->getIsBackupFormatV1();
680 }
681 }
682