Integration
11 months ago
Vendors
11 months ago
.htaccess
11 months ago
Destination.php
11 months ago
DestinationDiskUsage.php
11 months ago
DestinationFile.php
11 months ago
DestinationWrapper.php
11 months ago
ScanDirIterator.php
11 months ago
ScanDirIteratorFile.php
11 months ago
Tree.php
11 months ago
index.html
11 months ago
web.config
11 months ago
Tree.php
143 lines
| 1 | <?php |
| 2 | |
| 3 | namespace JetBackup\Destination; |
| 4 | |
| 5 | use JetBackup\Exception\DestinationException; |
| 6 | use JetBackup\Exception\IOVanishedException; |
| 7 | use JetBackup\Factory; |
| 8 | use JetBackup\JetBackup; |
| 9 | use JetBackup\Queue\QueueItem; |
| 10 | use SleekDB\Exceptions\InvalidArgumentException; |
| 11 | use SleekDB\Exceptions\IOException; |
| 12 | |
| 13 | if (!defined( '__JETBACKUP__')) die('Direct access is not allowed'); |
| 14 | |
| 15 | class Tree { |
| 16 | |
| 17 | const TREE_FILE = '%s' . JetBackup::SEP . '%d_tree.ini'; |
| 18 | |
| 19 | private Destination $_destination; |
| 20 | private QueueItem $_queue_item; |
| 21 | private string $_source; |
| 22 | private string $_tree_file; |
| 23 | |
| 24 | /** |
| 25 | * @param Destination $destination |
| 26 | * @param QueueItem $item |
| 27 | * @param string $source |
| 28 | */ |
| 29 | public function __construct(Destination $destination, QueueItem $item, string $source) { |
| 30 | $this->_destination = $destination; |
| 31 | $this->_queue_item = $item; |
| 32 | $this->_source = $source; |
| 33 | $this->_tree_file = sprintf(self::TREE_FILE, $source, $destination->getId()); |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * @return void |
| 38 | * @throws DestinationException |
| 39 | * @throws IOVanishedException |
| 40 | * @throws IOException |
| 41 | * @throws InvalidArgumentException |
| 42 | */ |
| 43 | private function _build():void { |
| 44 | |
| 45 | if(file_exists($this->_tree_file)) return; |
| 46 | |
| 47 | $excludes = [ |
| 48 | '*' . JetBackup::SEP . '.htaccess', |
| 49 | '*' . JetBackup::SEP . 'web.config', |
| 50 | '*' . JetBackup::SEP . 'index.html', |
| 51 | JetBackup::SEP . 'tree.php_done', |
| 52 | JetBackup::SEP . '*_tree.ini', |
| 53 | JetBackup::SEP . 'tmp', |
| 54 | JetBackup::SEP . 'tmp' . JetBackup::SEP . '*', |
| 55 | JetBackup::SEP . 'logs', |
| 56 | JetBackup::SEP . 'logs' . JetBackup::SEP . '*', |
| 57 | JetBackup::SEP . basename($this->_tree_file), |
| 58 | ]; |
| 59 | |
| 60 | $fd = fopen($this->_tree_file, 'w'); |
| 61 | if(!$fd) throw new DestinationException("Cannot open tree file: $this->_tree_file"); |
| 62 | |
| 63 | // We must build the tree with directories first, we must create the directory before uploading files to that directory |
| 64 | $scan = new ScanDirIterator($this->_source); |
| 65 | |
| 66 | $total_size = 0; |
| 67 | while($entry = $scan->next()) { |
| 68 | $filename = substr($entry->getFullPath(), strlen($this->_source)); |
| 69 | foreach($excludes as $exclude) if(fnmatch($exclude, $filename)) continue 2; |
| 70 | fwrite($fd, $filename . PHP_EOL); |
| 71 | if(!$entry->isDir()) $total_size += $entry->getSize(); |
| 72 | } |
| 73 | |
| 74 | fclose($fd); |
| 75 | |
| 76 | $progress = $this->_queue_item->getProgress(); |
| 77 | $progress->setTotalSubItems($total_size); |
| 78 | $progress->setCurrentSubItem(0); |
| 79 | $this->_queue_item->save(); |
| 80 | |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * @param callable $callback |
| 85 | * |
| 86 | * @return void |
| 87 | * @throws DestinationException |
| 88 | * @throws IOVanishedException |
| 89 | */ |
| 90 | public function process(callable $callback) { |
| 91 | |
| 92 | $this->_build(); |
| 93 | |
| 94 | $fp = fopen($this->_tree_file, 'r+'); |
| 95 | if (!$fp) throw new DestinationException("Cannot open tree file: $this->_tree_file"); |
| 96 | |
| 97 | fseek($fp, 0, SEEK_END); |
| 98 | $fileSize = ftell($fp); // Get the initial size of the file |
| 99 | $position = $fileSize; |
| 100 | $buffer = ''; |
| 101 | $_chunk_size = $this->_destination->getChunkSizeBytes() ?: Factory::getSettingsPerformance()->getReadChunkSizeBytes(); |
| 102 | |
| 103 | while ($position > 0) { |
| 104 | |
| 105 | $readSize = min($position, $_chunk_size); |
| 106 | $position -= $readSize; // Adjust position for the next read |
| 107 | fseek($fp, $position); |
| 108 | $chunk = fread($fp, $readSize) . $buffer; // Prepend previously buffered data |
| 109 | |
| 110 | while (($eolPos = strrpos($chunk, PHP_EOL)) !== false) { |
| 111 | $lineStartPos = $eolPos + strlen(PHP_EOL); |
| 112 | $line = substr($chunk, $lineStartPos); |
| 113 | $chunk = substr($chunk, 0, $eolPos); |
| 114 | |
| 115 | if (!empty($line)/* && basename($line) != basename(self::TREE_FILE)*/) call_user_func($callback, $line); |
| 116 | |
| 117 | // Update the position for truncating the file |
| 118 | $newSize = $position + $eolPos + strlen(PHP_EOL); |
| 119 | ftruncate($fp, $newSize); |
| 120 | fflush($fp); // Ensure changes are written |
| 121 | fseek($fp, $newSize); // Set the pointer for the next operation |
| 122 | } |
| 123 | |
| 124 | $buffer = $chunk; // Save any remaining data not followed by PHP_EOL |
| 125 | |
| 126 | if ($position === 0 && !empty($buffer)) { |
| 127 | // If there's buffered data left when we reach the start of the file, process it |
| 128 | call_user_func($callback, $buffer); |
| 129 | ftruncate($fp, 0); // Truncate the file if the entire content has been processed |
| 130 | } |
| 131 | |
| 132 | |
| 133 | } |
| 134 | |
| 135 | fclose($fp); |
| 136 | |
| 137 | // At this point, if tree file is empty we know we finished with the current destination |
| 138 | // tree upload, we need this if using multiple uploads to multiple destinations. |
| 139 | |
| 140 | // Do not delete tree file here |
| 141 | //unlink($_tree_file); |
| 142 | } |
| 143 | } |