Client
1 year ago
.htaccess
1 year ago
Box.php
1 year ago
Cache.php
1 year ago
ChunkedDownload.php
1 year ago
ChunkedUpload.php
1 year ago
DirIterator.php
1 year ago
index.html
1 year ago
web.config
1 year ago
DirIterator.php
194 lines
| 1 | <?php |
| 2 | /* |
| 3 | * |
| 4 | * JetBackup @ package |
| 5 | * Created By Idan Ben-Ezra |
| 6 | * |
| 7 | * Copyrights @ JetApps |
| 8 | * https://www.jetapps.com |
| 9 | * |
| 10 | **/ |
| 11 | namespace JetBackup\Destination\Vendors\Box; |
| 12 | |
| 13 | use Exception; |
| 14 | use JetBackup\Destination\DestinationFile; |
| 15 | use JetBackup\Destination\Integration\DestinationDirIterator; |
| 16 | use JetBackup\Destination\Integration\DestinationFile as iDestinationFile; |
| 17 | use JetBackup\Destination\Vendors\Box\Client\Client; |
| 18 | use JetBackup\Destination\Vendors\Box\Client\ClientException; |
| 19 | use JetBackup\Destination\Vendors\Box\Client\File; |
| 20 | use JetBackup\Destination\Vendors\Box\Client\ListFiles; |
| 21 | use JetBackup\Exception\IOException; |
| 22 | use JetBackup\Exception\JBException; |
| 23 | |
| 24 | defined( '__JETBACKUP__' ) or die( 'Restricted access' ); |
| 25 | |
| 26 | class DirIterator implements DestinationDirIterator { |
| 27 | |
| 28 | const CHUNK_LIMIT = 1000; |
| 29 | |
| 30 | private Box $_destination; |
| 31 | private string $_directory; |
| 32 | private ?ListFiles $_list=null; |
| 33 | private ?string $_parent_id=null; |
| 34 | /** @var File[] */ |
| 35 | private array $_files=[]; |
| 36 | private array $_subdirectories = []; // Queue of subdirectories |
| 37 | private array $_processedDirectories = []; |
| 38 | |
| 39 | /** |
| 40 | * @param Box $destination |
| 41 | * @param string $directory |
| 42 | * @param string|null $parent_id |
| 43 | * |
| 44 | * @throws IOException |
| 45 | * @throws JBException |
| 46 | */ |
| 47 | public function __construct(Box $destination, string $directory, ?string $parent_id=null) { |
| 48 | |
| 49 | $this->_destination = $destination; |
| 50 | $this->_directory = $directory; |
| 51 | if(!$this->_destination->getClient()) throw new IOException("Unable to retrieve Box service"); |
| 52 | $path = $this->_destination->getRealPath($this->_directory); |
| 53 | |
| 54 | if(!$parent_id) $parent_id = $this->_destination->getFolderId($path == '/' ? '' : $this->_directory); |
| 55 | if($parent_id === null) return; |
| 56 | $this->_parent_id = $parent_id; |
| 57 | $this->_destination->getLogController()->logDebug("[DestinationDirIterator] Processing directory: {$this->_directory} with Parent ID: {$this->_parent_id}"); |
| 58 | |
| 59 | $this->rewind(); |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * @param bool $rewind |
| 64 | * |
| 65 | * @return void |
| 66 | * @throws IOException |
| 67 | */ |
| 68 | private function _loadChunk(bool $rewind = false): void { |
| 69 | $this->_destination->getLogController()->logDebug("[_loadChunk] Processing directory: {$this->_directory} with Parent ID: {$this->_parent_id}"); |
| 70 | if (in_array($this->_directory, $this->_processedDirectories, true)) { |
| 71 | $this->_destination->getLogController()->logDebug("[_loadChunk] Skipping already processed directory: {$this->_directory}"); |
| 72 | return; |
| 73 | } |
| 74 | $this->_processedDirectories[] = $this->_directory; |
| 75 | |
| 76 | try { |
| 77 | $marker = ''; |
| 78 | if (!$rewind && $this->_list) { |
| 79 | $marker = $this->_list->getNextPageToken(); |
| 80 | if (!$marker) { |
| 81 | $this->_list = null; |
| 82 | return; |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | $this->_list = $this->_destination->_retries(function () use ($marker) { |
| 87 | return $this->_destination->getClient()->listFolder( |
| 88 | $this->_parent_id ?? Client::ROOT_FOLDER, |
| 89 | self::CHUNK_LIMIT, |
| 90 | $marker |
| 91 | ); |
| 92 | }, "Failed fetching list of files"); |
| 93 | |
| 94 | $this->_files = $this->_list ? $this->_list->getFiles() : []; |
| 95 | foreach ($this->_files as $file) { |
| 96 | if ($file->getMimeType() === Client::MIMITYPE_DIR) { |
| 97 | $subdirPath = rtrim($this->_directory, '/') . '/' . $file->getName(); |
| 98 | $subdirPath = preg_replace('#/+#', '/', $subdirPath); // Normalize slashes |
| 99 | $this->_subdirectories[] = [ |
| 100 | 'path' => $subdirPath, |
| 101 | 'id' => $file->getId(), |
| 102 | ]; |
| 103 | $this->_destination->getLogController()->logDebug("[_loadChunk] Enqueued subdirectory: $subdirPath with ID: {$file->getId()}"); |
| 104 | } |
| 105 | } |
| 106 | } catch (Exception $e) { |
| 107 | if ($e->getCode() === 404) { |
| 108 | $this->_files = []; |
| 109 | } else { |
| 110 | throw new IOException($e->getMessage(), $e->getCode(), $e); |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | |
| 116 | |
| 117 | /** |
| 118 | * @return void |
| 119 | * @throws IOException |
| 120 | */ |
| 121 | public function rewind(): void { |
| 122 | $this->_destination->getLogController()->logDebug("[rewind] Reloading current directory: {$this->_directory}"); |
| 123 | $this->_loadChunk(true); |
| 124 | } |
| 125 | |
| 126 | |
| 127 | /** |
| 128 | * @return bool |
| 129 | * @throws IOException |
| 130 | */ |
| 131 | public function hasNext(): bool { |
| 132 | if (!($this->_files && count($this->_files))) { |
| 133 | // If there are no more files in the current directory, move to the next subdirectory |
| 134 | if (!empty($this->_subdirectories)) { |
| 135 | $nextSubdir = array_shift($this->_subdirectories); |
| 136 | $this->_directory = $nextSubdir['path']; |
| 137 | $this->_parent_id = $nextSubdir['id']; |
| 138 | |
| 139 | // Debugging: Log subdirectory switch |
| 140 | $this->_destination->getLogController()->logDebug("[hasNext] Switching to subdirectory: {$this->_directory} with ID: {$this->_parent_id}"); |
| 141 | |
| 142 | // Reload chunk for the new subdirectory |
| 143 | $this->rewind(); |
| 144 | |
| 145 | return $this->hasNext(); // Reevaluate after switching |
| 146 | } |
| 147 | |
| 148 | return false; // No more files or subdirectories |
| 149 | } |
| 150 | |
| 151 | return true; |
| 152 | } |
| 153 | |
| 154 | |
| 155 | |
| 156 | |
| 157 | /** |
| 158 | * @return ?iDestinationFile |
| 159 | * @throws IOException |
| 160 | */ |
| 161 | public function getNext(): ?iDestinationFile { |
| 162 | if (!$this->hasNext()) return null; |
| 163 | |
| 164 | // If `_files` is empty but there are subdirectories, `hasNext` handles traversal |
| 165 | if (empty($this->_files)) { |
| 166 | return null; // Safeguard against infinite loop |
| 167 | } |
| 168 | |
| 169 | $nextFile = array_shift($this->_files); |
| 170 | |
| 171 | $path = rtrim($this->_directory, '/') . '/' . $nextFile->getName(); |
| 172 | $path = preg_replace('#/+#', '/', $path); // Normalize slashes |
| 173 | $basename = basename($path); |
| 174 | |
| 175 | $file = new DestinationFile(); |
| 176 | $file->setType($nextFile->getMimeType() === Client::MIMITYPE_DIR |
| 177 | ? iDestinationFile::TYPE_DIRECTORY |
| 178 | : iDestinationFile::TYPE_FILE); |
| 179 | $file->setName($basename); |
| 180 | $file->setPath($basename === $path ? '' : dirname($path)); |
| 181 | $file->setSize($nextFile->getMimeType() === Client::MIMITYPE_DIR |
| 182 | ? ($nextFile->getSize() ?: 4096) |
| 183 | : $nextFile->getSize()); |
| 184 | $file->setModifyTime($nextFile->getModificationTime()); |
| 185 | $file->setFileData(json_encode(['id' => $nextFile->getId()])); |
| 186 | |
| 187 | // Debugging: Log the file being processed |
| 188 | $this->_destination->getLogController()->logDebug("[getNext] Processing file: $basename with path: $path"); |
| 189 | |
| 190 | return $file; |
| 191 | } |
| 192 | |
| 193 | |
| 194 | } |