From 4bf40df770e2e2e268449852e6e16b02fa9a485d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sun, 31 May 2020 15:10:28 +0100 Subject: [PATCH] tools: added a script to remove garbage from region files --- tools/compact-regions.php | 178 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 tools/compact-regions.php diff --git a/tools/compact-regions.php b/tools/compact-regions.php new file mode 100644 index 000000000..90d3f8ee8 --- /dev/null +++ b/tools/compact-regions.php @@ -0,0 +1,178 @@ + $files + */ +function find_regions_recursive(string $dir, array &$files) : void{ + foreach(scandir($dir, SCANDIR_SORT_NONE) 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){ + $oldRegion = new RegionLoader($file); + try{ + $oldRegion->open(); + }catch(CorruptedRegionException $e){ + $logger->error("Damaged region in file $file (" . $e->getMessage() . "), skipping"); + $corruptedFiles[] = $file; + $doneCount++; + continue; + } + + $newFile = $file . ".compacted"; + $newRegion = new RegionLoader($newFile); + $newRegion->open(); + + $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)); +}