diff --git a/src/utils/Filesystem.php b/src/utils/Filesystem.php index b899cb8f5..531becc78 100644 --- a/src/utils/Filesystem.php +++ b/src/utils/Filesystem.php @@ -23,8 +23,11 @@ declare(strict_types=1); namespace pocketmine\utils; +use function copy; +use function dirname; use function fclose; use function fflush; +use function file_exists; use function flock; use function fopen; use function ftruncate; @@ -33,6 +36,7 @@ use function getmypid; use function is_dir; use function is_file; use function ltrim; +use function mkdir; use function preg_match; use function realpath; use function rmdir; @@ -88,6 +92,53 @@ final class Filesystem{ } } + /** + * Recursively copies a directory to a new location. The parent directories for the destination must exist. + */ + public static function recursiveCopy(string $origin, string $destination) : void{ + if(!is_dir($origin)){ + throw new \RuntimeException("$origin does not exist, or is not a directory"); + } + if(!is_dir($destination)){ + if(file_exists($destination)){ + throw new \RuntimeException("$destination already exists, and is not a directory"); + } + if(!is_dir(dirname($destination))){ + //if the parent dir doesn't exist, the user most likely made a mistake + throw new \RuntimeException("The parent directory of $destination does not exist, or is not a directory"); + } + if(!@mkdir($destination) && !is_dir($destination)){ + throw new \RuntimeException("Failed to create output directory $destination (permission denied?)"); + } + } + self::recursiveCopyInternal($origin, $destination); + } + + private static function recursiveCopyInternal(string $origin, string $destination) : void{ + if(is_dir($origin)){ + if(!is_dir($destination)){ + if(file_exists($destination)){ + throw new \RuntimeException("Path $destination does not exist, or is not a directory"); + } + mkdir($destination); //TODO: access permissions? + } + $objects = scandir($origin, SCANDIR_SORT_NONE); + if($objects === false) throw new AssumptionFailedError("scandir() shouldn't return false when is_dir() returns true"); + foreach($objects as $object){ + if($object === "." || $object === ".."){ + continue; + } + self::recursiveCopyInternal($origin . "/" . $object, $destination . "/" . $object); + } + }else{ + $dirName = dirname($destination); + if(!is_dir($dirName)){ //the destination folder should already exist + throw new AssumptionFailedError("The destination folder should have been created in the parent call"); + } + copy($origin, $destination); + } + } + public static function addCleanedPath(string $path, string $replacement) : void{ self::$cleanedPaths[$path] = $replacement; uksort(self::$cleanedPaths, function(string $str1, string $str2) : int{ diff --git a/src/world/format/io/FormatConverter.php b/src/world/format/io/FormatConverter.php index b8d113082..2dfc5b624 100644 --- a/src/world/format/io/FormatConverter.php +++ b/src/world/format/io/FormatConverter.php @@ -91,8 +91,17 @@ class FormatConverter{ $new->close(); $this->logger->info("Backing up pre-conversion world to " . $this->backupPath); - rename($path, $this->backupPath); - rename($new->getPath(), $path); + if(!@rename($path, $this->backupPath)){ + $this->logger->warning("Moving old world files for backup failed, attempting copy instead. This might take a long time."); + Filesystem::recursiveCopy($path, $this->backupPath); + Filesystem::recursiveUnlink($path); + } + if(!@rename($new->getPath(), $path)){ + //we don't expect this to happen because worlds/ should most likely be all on the same FS, but just in case... + $this->logger->debug("Relocation of new world files to location failed, attempting copy and delete instead"); + Filesystem::recursiveCopy($new->getPath(), $path); + Filesystem::recursiveUnlink($new->getPath()); + } $this->logger->info("Conversion completed"); /**