0
0
mirror of https://github.com/pmmp/PocketMine-MP.git synced 2024-11-23 11:36:14 +00:00
PocketMine-MP/tools/compact-regions.php

183 lines
4.8 KiB
PHP

<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\tools\compact_regions;
use pocketmine\world\format\io\exception\CorruptedChunkException;
use pocketmine\world\format\io\region\CorruptedRegionException;
use pocketmine\world\format\io\region\RegionLoader;
use function array_sum;
use function arsort;
use function clearstatcache;
use function count;
use function defined;
use function dirname;
use function file_exists;
use function filesize;
use function in_array;
use function is_dir;
use function is_file;
use function number_format;
use function pathinfo;
use function rename;
use function round;
use function scandir;
use function unlink;
use const PATHINFO_EXTENSION;
use const PHP_BINARY;
use const SCANDIR_SORT_NONE;
use const SORT_NUMERIC;
require dirname(__DIR__) . '/vendor/autoload.php';
const SUPPORTED_EXTENSIONS = [
"mcr",
"mca",
"mcapm"
];
/**
* @param int[] $files
* @phpstan-param array<string, int> $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 === "." || $file === ".."){
continue;
}
$fullPath = $dir . "/" . $file;
if(
in_array(pathinfo($fullPath, PATHINFO_EXTENSION), SUPPORTED_EXTENSIONS, true) &&
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__ . " <path to region folder or file>\n";
return 1;
}
$logger = \GlobalLogger::get();
/** @phpstan-var array<string, int> $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));
}