$files */ function find_regions_recursive(string $dir, array &$files) : void{ $dirFiles = scandir($dir, SCANDIR_SORT_NONE); if($dirFiles === false){ return; } foreach($dirFiles as $file){ if($file === "." or $file === ".."){ continue; } $fullPath = $dir . "/" . $file; if( in_array(pathinfo($fullPath, PATHINFO_EXTENSION), SUPPORTED_EXTENSIONS, true) and is_file($fullPath) ){ $files[$fullPath] = filesize($fullPath); }elseif(is_dir($fullPath)){ find_regions_recursive($fullPath, $files); } } } /** * @param string[] $argv */ function main(array $argv) : int{ if(!isset($argv[1])){ echo "Usage: " . PHP_BINARY . " " . __FILE__ . " \n"; return 1; } $logger = \GlobalLogger::get(); /** @phpstan-var array $files */ $files = []; if(is_file($argv[1])){ $files[$argv[1]] = filesize($argv[1]); }elseif(is_dir($argv[1])){ find_regions_recursive($argv[1], $files); } if(count($files) === 0){ echo "No supported files found\n"; return 1; } arsort($files, SORT_NUMERIC); $currentSize = array_sum($files); $logger->info("Discovered " . count($files) . " files totalling " . number_format($currentSize) . " bytes"); $logger->warning("Please DO NOT forcibly kill the compactor, or your files may be damaged."); $corruptedFiles = []; $doneCount = 0; $totalCount = count($files); foreach($files as $file => $size){ try{ $oldRegion = RegionLoader::loadExisting($file); }catch(CorruptedRegionException $e){ $logger->error("Damaged region in file $file (" . $e->getMessage() . "), skipping"); $corruptedFiles[] = $file; $doneCount++; continue; } $newFile = $file . ".compacted"; $newRegion = RegionLoader::createNew($newFile); $emptyRegion = true; $corruption = false; for($x = 0; $x < 32; $x++){ for($z = 0; $z < 32; $z++){ try{ $data = $oldRegion->readChunk($x, $z); }catch(CorruptedChunkException $e){ $logger->error("Damaged chunk $x $z in file $file (" . $e->getMessage() . "), skipping"); $corruption = true; continue; } if($data !== null){ $emptyRegion = false; $newRegion->writeChunk($x, $z, $data); } } } $oldRegion->close(); $newRegion->close(); if(!$corruption){ unlink($file); }else{ rename($file, $file . ".bak"); $corruptedFiles[] = $file . ".bak"; } if(!$emptyRegion){ rename($newFile, $file); }else{ unlink($newFile); } $doneCount++; $logger->info("Compacted region $file ($doneCount/$totalCount, " . round(($doneCount / $totalCount) * 100, 2) . "%)"); } clearstatcache(); $newSize = 0; foreach($files as $file => $oldSize){ $newSize += file_exists($file) ? filesize($file) : 0; } $diff = $currentSize - $newSize; $logger->info("Finished compaction of " . count($files) . " files. Freed " . number_format($diff) . " bytes of space (" . round(($diff / $currentSize) * 100, 2) . "% reduction)."); if(count($corruptedFiles) > 0){ $logger->error("The following backup files were not removed due to corruption detected:"); foreach($corruptedFiles as $file){ echo $file . "\n"; } return 1; } return 0; } if(!defined('pocketmine\_PHPSTAN_ANALYSIS')){ exit(main($argv)); }