FileFixerUpper.java

net.minecraft.util.filefix.FileFixerUpper

信息

  • 全限定名:net.minecraft.util.filefix.FileFixerUpper
  • 类型:public class
  • 包:net.minecraft.util.filefix
  • 源码路径:src/main/java/net/minecraft/util/filefix/FileFixerUpper.java
  • 起始行号:L50
  • 职责:

    TODO

字段/常量

  • FILE_FIXER_INTRODUCTION_VERSION

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L51
    • 说明:

      TODO

  • GSON

    • 类型: Gson
    • 修饰符: private static final
    • 源码定位: L52
    • 说明:

      TODO

  • FILE_FIX_DIRECTORY_NAME

    • 类型: String
    • 修饰符: private static final
    • 源码定位: L53
    • 说明:

      TODO

  • NEW_WORLD_TEMP_NAME

    • 类型: String
    • 修饰符: private static final
    • 源码定位: L54
    • 说明:

      TODO

  • UPGRADE_IN_PROGRESS_NAME

    • 类型: String
    • 修饰符: private static final
    • 源码定位: L55
    • 说明:

      TODO

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L56
    • 说明:

      TODO

  • dataFixer

    • 类型: Result
    • 修饰符: private final
    • 源码定位: L57
    • 说明:

      TODO

  • fileFixes

    • 类型: List<FileFix>
    • 修饰符: private final
    • 源码定位: L58
    • 说明:

      TODO

  • latestFileFixerVersion

    • 类型: int
    • 修饰符: private final
    • 源码定位: L59
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.util.filefix.FileFixerUpper.Builder

    • 类型: class
    • 修饰符: public static
    • 源码定位: L435
    • 说明:

      TODO

  • net.minecraft.util.filefix.FileFixerUpper.UpgradeInProgress

    • 类型: record
    • 修饰符: public
    • 源码定位: L491
    • 说明:

      TODO

构造器

public FileFixerUpper(Result dataFixer, List<FileFix> fileFixes, int latestFileFixerVersion) @ L61

  • 构造器名:FileFixerUpper
  • 源码定位:L61
  • 修饰符:public

参数:

  • dataFixer: Result
  • fileFixes: List
  • latestFileFixerVersion: int

说明:

TODO

方法

下面的方法块按源码顺序生成。

public static int worldVersionToFileFixerVersion(int levelDataVersion) @ L67

  • 方法名:worldVersionToFileFixerVersion
  • 源码定位:L67
  • 返回类型:int
  • 修饰符:public static

参数:

  • levelDataVersion: int

说明:

TODO

public boolean requiresFileFixing(int levelDataVersion) @ L71

  • 方法名:requiresFileFixing
  • 源码定位:L71
  • 返回类型:boolean
  • 修饰符:public

参数:

  • levelDataVersion: int

说明:

TODO

public Dynamic<?> fix(LevelStorageSource.LevelStorageAccess worldAccess, Dynamic<?> levelDataTag, UpgradeProgress upgradeProgress) @ L75

  • 方法名:fix
  • 源码定位:L75
  • 返回类型:Dynamic<?>
  • 修饰符:public

参数:

  • worldAccess: LevelStorageSource.LevelStorageAccess
  • levelDataTag: Dynamic<?>
  • upgradeProgress: UpgradeProgress

说明:

TODO

public Dynamic<?> fix(LevelStorageSource.LevelStorageAccess worldAccess, Dynamic<?> levelDataTag, UpgradeProgress upgradeProgress, int toVersion) @ L79

  • 方法名:fix
  • 源码定位:L79
  • 返回类型:Dynamic<?>
  • 修饰符:public

参数:

  • worldAccess: LevelStorageSource.LevelStorageAccess
  • levelDataTag: Dynamic<?>
  • upgradeProgress: UpgradeProgress
  • toVersion: int

说明:

TODO

private List<FileMove> startOrContinueFileFixing(UpgradeProgress upgradeProgress, int toVersion, Path worldFolder, Path tempWorld, Path fileFixDirectory, int loadedVersion) @ L118

  • 方法名:startOrContinueFileFixing
  • 源码定位:L118
  • 返回类型:List
  • 修饰符:private

参数:

  • upgradeProgress: UpgradeProgress
  • toVersion: int
  • worldFolder: Path
  • tempWorld: Path
  • fileFixDirectory: Path
  • loadedVersion: int

说明:

TODO

private static void deleteDirectory(Path directory) @ L143

  • 方法名:deleteDirectory
  • 源码定位:L143
  • 返回类型:void
  • 修饰符:private static

参数:

  • directory: Path

说明:

TODO

private static void cleanup(Path fileFixDirectory) @ L168

  • 方法名:cleanup
  • 源码定位:L168
  • 返回类型:void
  • 修饰符:private static

参数:

  • fileFixDirectory: Path

说明:

TODO

private List<FileMove> applyFileFixersOnCow(UpgradeProgress upgradeProgress, int loadedVersion, int toVersion, Path worldFolder, Path fileFixDirectory, Path tempWorld) @ L176

  • 方法名:applyFileFixersOnCow
  • 源码定位:L176
  • 返回类型:List
  • 修饰符:private

参数:

  • upgradeProgress: UpgradeProgress
  • loadedVersion: int
  • toVersion: int
  • worldFolder: Path
  • fileFixDirectory: Path
  • tempWorld: Path

说明:

TODO

public void applyFileFixers(UpgradeProgress upgradeProgress, int loadedVersion, int toVersion, Path basePath) @ L194

  • 方法名:applyFileFixers
  • 源码定位:L194
  • 返回类型:void
  • 修饰符:public

参数:

  • upgradeProgress: UpgradeProgress
  • loadedVersion: int
  • toVersion: int
  • basePath: Path

说明:

TODO

private List<FileFix> getApplicableFixers(int fromVersion, int toVersion) @ L212

  • 方法名:getApplicableFixers
  • 源码定位:L212
  • 返回类型:List
  • 修饰符:private

参数:

  • fromVersion: int
  • toVersion: int

说明:

TODO

private void countFiles(List<FileFix> applicableFixers, UpgradeProgress upgradeProgress) @ L217

  • 方法名:countFiles
  • 源码定位:L217
  • 返回类型:void
  • 修饰符:private

参数:

  • applicableFixers: List
  • upgradeProgress: UpgradeProgress

说明:

TODO

private void writeUpdatedLevelData(Path worldFolder, int toVersion) @ L228

  • 方法名:writeUpdatedLevelData
  • 源码定位:L228
  • 返回类型:void
  • 修饰符:private

参数:

  • worldFolder: Path
  • toVersion: int

说明:

TODO

private static Dynamic<?> addVersionsToLevelData(Dynamic<?> fixed, int toVersion) @ L239

  • 方法名:addVersionsToLevelData
  • 源码定位:L239
  • 返回类型:Dynamic<?>
  • 修饰符:private static

参数:

  • fixed: Dynamic<?>
  • toVersion: int

说明:

TODO

protected static void swapInFixedWorld(LevelStorageSource.LevelStorageAccess worldAccess, List<FileMove> moves, Path fileFixDirectory, Path tempWorld) @ L245

  • 方法名:swapInFixedWorld
  • 源码定位:L245
  • 返回类型:void
  • 修饰符:protected static

参数:

  • worldAccess: LevelStorageSource.LevelStorageAccess
  • moves: List
  • fileFixDirectory: Path
  • tempWorld: Path

说明:

TODO

private static void writeMoves(List<FileMove> moves, Path fromDirectory, Path toDirectory, Path filePath) @ L375

  • 方法名:writeMoves
  • 源码定位:L375
  • 返回类型:void
  • 修饰符:private static

参数:

  • moves: List
  • fromDirectory: Path
  • toDirectory: Path
  • filePath: Path

说明:

TODO

private static List<FileMove> readMoves(Path fromDirectory, Path toDirectory, Path filePath) @ L381

  • 方法名:readMoves
  • 源码定位:L381
  • 返回类型:List
  • 修饰符:private static

参数:

  • fromDirectory: Path
  • toDirectory: Path
  • filePath: Path

说明:

TODO

public static FileSystemCapabilities detectFileSystemCapabilities(Path dir) @ L387

  • 方法名:detectFileSystemCapabilities
  • 源码定位:L387
  • 返回类型:FileSystemCapabilities
  • 修饰符:public static

参数:

  • dir: Path

说明:

TODO

private static boolean supportsAtomicMove(Path dir) @ L391

  • 方法名:supportsAtomicMove
  • 源码定位:L391
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • dir: Path

说明:

TODO

private static boolean supportsHardLinks(Path dir) @ L413

  • 方法名:supportsHardLinks
  • 源码定位:L413
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • dir: Path

说明:

TODO

代码

public class FileFixerUpper {
    private static final int FILE_FIXER_INTRODUCTION_VERSION = 4772;
    private static final Gson GSON = new Gson().newBuilder().setPrettyPrinting().create();
    private static final String FILE_FIX_DIRECTORY_NAME = "filefix";
    private static final String NEW_WORLD_TEMP_NAME = "new_world";
    private static final String UPGRADE_IN_PROGRESS_NAME = "upgrade_in_progress.json";
    private static final Logger LOGGER = LogUtils.getLogger();
    private final Result dataFixer;
    private final List<FileFix> fileFixes;
    private final int latestFileFixerVersion;
 
    public FileFixerUpper(Result dataFixer, List<FileFix> fileFixes, int latestFileFixerVersion) {
        this.dataFixer = dataFixer;
        this.fileFixes = List.copyOf(fileFixes);
        this.latestFileFixerVersion = latestFileFixerVersion;
    }
 
    public static int worldVersionToFileFixerVersion(int levelDataVersion) {
        return levelDataVersion < 4772 ? 0 : levelDataVersion;
    }
 
    public boolean requiresFileFixing(int levelDataVersion) {
        return worldVersionToFileFixerVersion(levelDataVersion) < this.latestFileFixerVersion;
    }
 
    public Dynamic<?> fix(LevelStorageSource.LevelStorageAccess worldAccess, Dynamic<?> levelDataTag, UpgradeProgress upgradeProgress) throws FileFixException {
        return this.fix(worldAccess, levelDataTag, upgradeProgress, SharedConstants.getCurrentVersion().dataVersion().version());
    }
 
    @VisibleForTesting
    public Dynamic<?> fix(LevelStorageSource.LevelStorageAccess worldAccess, Dynamic<?> levelDataTag, UpgradeProgress upgradeProgress, int toVersion) throws FileFixException {
        int loadedVersion = NbtUtils.getDataVersion(levelDataTag);
        if (this.requiresFileFixing(loadedVersion)) {
            LOGGER.info("Starting upgrade for world \"{}\"", worldAccess.getLevelId());
            Path worldFolder = worldAccess.getLevelDirectory().path();
            Path fileFixDirectory = worldFolder.resolve("filefix");
            Path tempWorld = fileFixDirectory.resolve("new_world");
 
            List<FileMove> moves;
            try {
                moves = this.startOrContinueFileFixing(upgradeProgress, toVersion, worldFolder, tempWorld, fileFixDirectory, loadedVersion);
            } catch (IOException var12) {
                throw new AbortedFileFixException(var12);
            }
 
            try {
                swapInFixedWorld(worldAccess, moves, fileFixDirectory, tempWorld);
            } catch (AbortedFileFixException var13) {
                if (var13.notRevertedMoves().isEmpty()) {
                    cleanup(fileFixDirectory);
                }
 
                throw var13;
            }
 
            try {
                levelDataTag = worldAccess.getUnfixedDataTag(false);
            } catch (IOException var11) {
                throw new UncheckedIOException(var11);
            }
 
            loadedVersion = NbtUtils.getDataVersion(levelDataTag);
        }
 
        Dynamic<?> fixedLevelDataTag = DataFixTypes.LEVEL.updateToCurrentVersion(this.dataFixer.fixer(), levelDataTag, loadedVersion);
        return addVersionsToLevelData(fixedLevelDataTag, toVersion);
    }
 
    private List<FileMove> startOrContinueFileFixing(
        UpgradeProgress upgradeProgress, int toVersion, Path worldFolder, Path tempWorld, Path fileFixDirectory, int loadedVersion
    ) throws IOException {
        Path upgradeInProgressFile = fileFixDirectory.resolve("upgrade_in_progress.json");
        List<FileMove> moves;
        if (Files.exists(upgradeInProgressFile)) {
            LOGGER.warn("Found previously interrupted world upgrade, attempting to continue it");
            moves = readMoves(worldFolder, tempWorld, upgradeInProgressFile);
        } else {
            if (Files.exists(fileFixDirectory)) {
                deleteDirectory(fileFixDirectory);
            }
 
            try {
                Files.createDirectory(fileFixDirectory);
                moves = this.applyFileFixersOnCow(upgradeProgress, loadedVersion, toVersion, worldFolder, fileFixDirectory, tempWorld);
            } catch (Exception var10) {
                cleanup(fileFixDirectory);
                throw var10;
            }
        }
 
        return moves;
    }
 
    private static void deleteDirectory(Path directory) throws IOException {
        Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
            public FileVisitResult visitFile(Path realPath, BasicFileAttributes attrs) {
                try {
                    Files.deleteIfExists(realPath);
                } catch (IOException var4) {
                }
 
                return FileVisitResult.CONTINUE;
            }
 
            public FileVisitResult postVisitDirectory(Path realPath, @Nullable IOException e) {
                try {
                    Files.deleteIfExists(realPath);
                } catch (IOException var4) {
                }
 
                return FileVisitResult.CONTINUE;
            }
        });
        if (Files.exists(directory)) {
            PathUtils.deleteDirectory(directory);
        }
    }
 
    private static void cleanup(Path fileFixDirectory) {
        try {
            deleteDirectory(fileFixDirectory);
        } catch (Exception var2) {
            LOGGER.error("Failed to clean up", (Throwable)var2);
        }
    }
 
    private List<FileMove> applyFileFixersOnCow(
        UpgradeProgress upgradeProgress, int loadedVersion, int toVersion, Path worldFolder, Path fileFixDirectory, Path tempWorld
    ) throws IOException {
        List var9;
        try (CopyOnWriteFileSystem fs = CopyOnWriteFileSystem.create(
                worldFolder.getFileName().toString(), worldFolder, fileFixDirectory.resolve("cow"), fileFixDirectory::equals
            )) {
            this.applyFileFixers(upgradeProgress, loadedVersion, toVersion, fs.rootPath());
            CopyOnWriteFileSystem.Moves moves = fs.collectMoveOperations(tempWorld);
            Files.createDirectory(tempWorld);
            CopyOnWriteFileSystem.createDirectories(moves.directories());
            CopyOnWriteFileSystem.moveFiles(moves.copiedFiles());
            var9 = moves.preexistingFiles();
        }
 
        return var9;
    }
 
    @VisibleForTesting
    public void applyFileFixers(UpgradeProgress upgradeProgress, int loadedVersion, int toVersion, Path basePath) throws IOException {
        List<FileFix> applicableFixers = this.getApplicableFixers(loadedVersion, toVersion);
        upgradeProgress.setType(UpgradeProgress.Type.FILES);
        this.countFiles(applicableFixers, upgradeProgress);
        upgradeProgress.setStatus(UpgradeProgress.Status.UPGRADING);
        upgradeProgress.setApplicableFixerAmount(applicableFixers.size());
 
        for (FileFix fileFix : applicableFixers) {
            upgradeProgress.incrementRunningFileFixer();
            fileFix.runFixOperations(basePath, upgradeProgress);
        }
 
        this.writeUpdatedLevelData(basePath, toVersion);
        Files.deleteIfExists(basePath.resolve("level.dat_old"));
        Files.deleteIfExists(basePath.resolve("session.lock"));
    }
 
    private List<FileFix> getApplicableFixers(int fromVersion, int toVersion) {
        int fileFixerFromVersion = worldVersionToFileFixerVersion(fromVersion);
        return this.fileFixes.stream().filter(fileFix -> fileFix.getVersion() > fileFixerFromVersion && fileFix.getVersion() <= toVersion).toList();
    }
 
    private void countFiles(List<FileFix> applicableFixers, UpgradeProgress upgradeProgress) {
        upgradeProgress.setStatus(UpgradeProgress.Status.COUNTING);
        int totalFiles = 0;
 
        for (FileFix fileFix : applicableFixers) {
            totalFiles += fileFix.countFileOperations();
        }
 
        upgradeProgress.addTotalFileFixOperations(totalFiles);
    }
 
    private void writeUpdatedLevelData(Path worldFolder, int toVersion) throws IOException {
        Path levelDatPath = worldFolder.resolve(LevelResource.LEVEL_DATA_FILE.id());
        CompoundTag unfixedLevelDat = NbtIo.readCompressed(levelDatPath, NbtAccounter.defaultQuota());
        CompoundTag unfixedDataTag = unfixedLevelDat.getCompoundOrEmpty("Data");
        int dataVersion = NbtUtils.getDataVersion(unfixedDataTag);
        Dynamic<?> fixed = DataFixTypes.LEVEL.update(this.dataFixer.fixer(), new Dynamic<>(NbtOps.INSTANCE, unfixedDataTag), dataVersion, toVersion);
        fixed = addVersionsToLevelData(fixed, toVersion);
        Dynamic<?> dynamic = fixed.emptyMap().set("Data", fixed);
        NbtIo.writeCompressed((CompoundTag)dynamic.convert(NbtOps.INSTANCE).getValue(), levelDatPath);
    }
 
    private static Dynamic<?> addVersionsToLevelData(Dynamic<?> fixed, int toVersion) {
        fixed = NbtUtils.addDataVersion(fixed, toVersion);
        fixed = PrimaryLevelData.writeLastPlayed(fixed);
        return PrimaryLevelData.writeVersionTag(fixed);
    }
 
    @VisibleForTesting
    protected static void swapInFixedWorld(LevelStorageSource.LevelStorageAccess worldAccess, List<FileMove> moves, Path fileFixDirectory, Path tempWorld) throws FileFixException {
        Path worldFolder = worldAccess.getLevelDirectory().path();
        Path savesDirectory = worldFolder.getParent();
        String worldName = worldFolder.getFileName().toString();
 
        Path tempWorldTopLevel;
        Path oldWorldFolder;
        FileSystemCapabilities fileSystemCapabilities;
        try {
            fileSystemCapabilities = detectFileSystemCapabilities(fileFixDirectory);
            tempWorldTopLevel = savesDirectory.resolve(FileUtil.findAvailableName(savesDirectory, worldName + " upgraded", ""));
            oldWorldFolder = savesDirectory.resolve(FileUtil.findAvailableName(savesDirectory, worldName + " OUTDATED", ""));
        } catch (Exception var21) {
            throw new AbortedFileFixException(var21);
        }
 
        if (!fileSystemCapabilities.atomicMove()) {
            throw new AtomicMoveNotSupportedFileFixException(fileSystemCapabilities);
        } else {
            CopyOption[] moveOptions = fileSystemCapabilities.getMoveOptions();
            LOGGER.info("File system capabilities: {}", fileSystemCapabilities);
            Path movesFile = fileFixDirectory.resolve("upgrade_in_progress.json");
 
            try {
                if (fileSystemCapabilities.hardLinks()) {
                    CopyOnWriteFileSystem.hardLinkFiles(moves);
                } else {
                    writeMoves(moves, worldFolder, tempWorld, movesFile);
                }
            } catch (Exception var20) {
                throw new AbortedFileFixException(var20, List.of(), fileSystemCapabilities);
            }
 
            LOGGER.info("Applying file structure changes for world \"{}\"", worldAccess.getLevelId());
            if (fileSystemCapabilities.hardLinks()) {
                try {
                    LOGGER.info("Moving new hardlinked world to top level");
                    Files.move(tempWorld, tempWorldTopLevel, moveOptions);
                } catch (Exception var19) {
                    LOGGER.error("Encountered error trying to move world folder:", (Throwable)var19);
                    throw new AbortedFileFixException(var19, List.of(), fileSystemCapabilities);
                }
            } else {
                try {
                    LOGGER.info("Moving files into new file structure");
                    CopyOnWriteFileSystem.moveFilesWithRetry(moves, moveOptions);
                    LOGGER.info("Moving new world to top level");
                    Files.move(tempWorld, tempWorldTopLevel, moveOptions);
                } catch (Exception var23) {
                    LOGGER.error("Encountered error while trying to create new world folder:", (Throwable)var23);
                    List<FileMove> failedMoves = CopyOnWriteFileSystem.tryRevertMoves(moves, moveOptions);
                    if (failedMoves.isEmpty()) {
                        try {
                            Files.deleteIfExists(movesFile);
                        } catch (IOException var15) {
                            LOGGER.warn("Failed to delete {}", movesFile, var23);
                        }
                    }
 
                    throw new AbortedFileFixException(var23, failedMoves, fileSystemCapabilities);
                }
 
                LOGGER.info("Complete move");
 
                try {
                    Files.deleteIfExists(movesFile);
                } catch (IOException var18) {
                    LOGGER.warn("Failed to delete {}", movesFile, var18);
                }
            }
 
            LOGGER.info("Start cleanup");
 
            try {
                Files.deleteIfExists(worldFolder.resolve("level.dat"));
                Files.deleteIfExists(worldFolder.resolve("level.dat_old"));
            } catch (Exception var17) {
                LOGGER.warn("Failed to delete outdated level.dat files: ", (Throwable)var17);
            }
 
            MutableBoolean succeeded = new MutableBoolean();
 
            try {
                worldAccess.releaseTemporarilyAndRun(() -> {
                    LOGGER.info("Moving out old world folder");
 
                    try {
                        Files.move(worldFolder, oldWorldFolder, moveOptions);
                    } catch (Exception var10x) {
                        LOGGER.warn("Failed to move outdated world folder out of the way; will try to delete instead: ", (Throwable)var10x);
 
                        try {
                            deleteDirectory(worldFolder);
                        } catch (Exception var9x) {
                            LOGGER.warn("Failed to delete outdated world folder: ", (Throwable)var10x);
                            throw new FailedCleanupFileFixException(var10x, tempWorldTopLevel.getFileName().toString(), fileSystemCapabilities);
                        }
                    }
 
                    LOGGER.info("Moving in new world folder");
 
                    try {
                        Files.move(tempWorldTopLevel, worldFolder, moveOptions);
                    } catch (Exception var8x) {
                        LOGGER.warn("Failed to move in new world folder: ", (Throwable)var8x);
                        throw new FailedCleanupFileFixException(var8x, tempWorldTopLevel.getFileName().toString(), fileSystemCapabilities);
                    }
 
                    succeeded.setTrue();
                });
            } catch (IOException var22) {
                Path newWorldFolder = succeeded.isTrue() ? worldFolder : tempWorldTopLevel;
                throw new FailedCleanupFileFixException(var22, newWorldFolder.getFileName().toString(), fileSystemCapabilities);
            }
 
            LOGGER.info("Done applying file structure changes for world \"{}\". Cleaning up outdated data...", worldAccess.getLevelId());
 
            try {
                if (Files.exists(oldWorldFolder)) {
                    deleteDirectory(oldWorldFolder);
                }
            } catch (Exception var16) {
                LOGGER.warn("Failed to clean up old world folder", (Throwable)var16);
            }
 
            LOGGER.info("Upgrade done for world \"{}\"", worldAccess.getLevelId());
        }
    }
 
    private static void writeMoves(List<FileMove> moves, Path fromDirectory, Path toDirectory, Path filePath) throws IOException {
        Codec<FileFixerUpper.UpgradeInProgress> codec = FileFixerUpper.UpgradeInProgress.codec(fromDirectory, toDirectory);
        JsonElement json = codec.encodeStart(JsonOps.INSTANCE, new FileFixerUpper.UpgradeInProgress(moves)).getOrThrow();
        Files.writeString(filePath, GSON.toJson(json), StandardOpenOption.DSYNC, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
    }
 
    private static List<FileMove> readMoves(Path fromDirectory, Path toDirectory, Path filePath) throws IOException {
        JsonObject json = GSON.fromJson(Files.readString(filePath), JsonObject.class);
        Codec<FileFixerUpper.UpgradeInProgress> codec = FileFixerUpper.UpgradeInProgress.codec(fromDirectory, toDirectory);
        return codec.decode(JsonOps.INSTANCE, json).getOrThrow().getFirst().moves;
    }
 
    public static FileSystemCapabilities detectFileSystemCapabilities(Path dir) throws IOException {
        return new FileSystemCapabilities(supportsAtomicMove(dir), supportsHardLinks(dir));
    }
 
    private static boolean supportsAtomicMove(Path dir) throws IOException {
        Path sourceFile = dir.resolve(UUID.randomUUID().toString());
        Path targetFile = dir.resolve(UUID.randomUUID().toString());
 
        boolean var4;
        try {
            Files.createFile(sourceFile);
 
            try {
                Files.move(sourceFile, targetFile, StandardCopyOption.ATOMIC_MOVE);
                return true;
            } catch (AtomicMoveNotSupportedException var8) {
                var4 = false;
            }
        } finally {
            Files.deleteIfExists(sourceFile);
            Files.deleteIfExists(targetFile);
        }
 
        return var4;
    }
 
    private static boolean supportsHardLinks(Path dir) throws IOException {
        Path sourceFile = dir.resolve(UUID.randomUUID().toString());
        Path targetFile = dir.resolve(UUID.randomUUID().toString());
 
        boolean var4;
        try {
            Files.createFile(sourceFile);
 
            try {
                Files.createLink(targetFile, sourceFile);
                return true;
            } catch (Exception var8) {
                var4 = false;
            }
        } finally {
            Files.deleteIfExists(sourceFile);
            Files.deleteIfExists(targetFile);
        }
 
        return var4;
    }
 
    public static class Builder {
        public final List<FileFix> fileFixes = new ArrayList<>();
        private final int currentVersion;
        private int latestFileFixerVersion;
        private final List<Schema> knownSchemas = new ArrayList<>();
 
        public Builder(int currentVersion) {
            this.currentVersion = currentVersion;
        }
 
        public void addFixer(FileFix fileFix) {
            if (!this.knownSchemas.contains(fileFix.getSchema())) {
                throw new IllegalArgumentException("Tried to add file fixer with unknown schema. Add it through FileFixerUpper#addSchema instead");
            } else {
                int fileFixVersion = fileFix.getVersion();
                if (fileFix.getVersion() > this.currentVersion) {
                    throw new IllegalArgumentException(
                        String.format(
                            Locale.ROOT,
                            "Tried to add too recent file fix for version: %s. The data version of the game is: %s",
                            fileFixVersion,
                            this.currentVersion
                        )
                    );
                } else {
                    if (!this.fileFixes.isEmpty()) {
                        FileFix last = this.fileFixes.getLast();
                        if (last.getVersion() > fileFixVersion) {
                            throw new IllegalArgumentException(
                                String.format(
                                    Locale.ROOT,
                                    "Tried to add too recent file fix for version: %s. The most recent file fix version is %s",
                                    fileFixVersion,
                                    last.getVersion()
                                )
                            );
                        }
                    }
 
                    this.fileFixes.add(fileFix);
                }
            }
        }
 
        public Schema addSchema(DataFixerBuilder fixerUpper, int version, BiFunction<Integer, Schema, Schema> factory) {
            this.latestFileFixerVersion = Math.max(version, this.latestFileFixerVersion);
            Schema schema = fixerUpper.addSchema(version, factory);
            this.knownSchemas.add(schema);
            return schema;
        }
 
        public FileFixerUpper build(Result dataFixer) {
            return new FileFixerUpper(dataFixer, this.fileFixes, this.latestFileFixerVersion);
        }
    }
 
    public record UpgradeInProgress(List<FileMove> moves) {
        public static Codec<FileFixerUpper.UpgradeInProgress> codec(Path fromDirectory, Path toDirectory) {
            return RecordCodecBuilder.create(
                i -> i.group(FileMove.moveCodec(fromDirectory, toDirectory).listOf().fieldOf("moves").forGetter(FileFixerUpper.UpgradeInProgress::moves))
                    .apply(i, FileFixerUpper.UpgradeInProgress::new)
            );
        }
    }
}

引用的其他类

  • SharedConstants

    • 引用位置: 方法调用
    • 关联成员: SharedConstants.getCurrentVersion()
  • NbtAccounter

    • 引用位置: 方法调用
    • 关联成员: NbtAccounter.defaultQuota()
  • NbtIo

    • 引用位置: 方法调用
    • 关联成员: NbtIo.readCompressed(), NbtIo.writeCompressed()
  • NbtUtils

    • 引用位置: 方法调用
    • 关联成员: NbtUtils.addDataVersion(), NbtUtils.getDataVersion()
  • FileUtil

    • 引用位置: 方法调用
    • 关联成员: FileUtil.findAvailableName()
  • AbortedFileFixException

    • 引用位置: 构造调用
    • 关联成员: AbortedFileFixException()
  • AtomicMoveNotSupportedFileFixException

    • 引用位置: 构造调用
    • 关联成员: AtomicMoveNotSupportedFileFixException()
  • FailedCleanupFileFixException

    • 引用位置: 构造调用
    • 关联成员: FailedCleanupFileFixException()
  • FileFix

    • 引用位置: 参数/字段/返回值
  • FileSystemCapabilities

    • 引用位置: 构造调用/返回值
    • 关联成员: FileSystemCapabilities()
  • CopyOnWriteFileSystem

    • 引用位置: 方法调用
    • 关联成员: CopyOnWriteFileSystem.create(), CopyOnWriteFileSystem.createDirectories(), CopyOnWriteFileSystem.hardLinkFiles(), CopyOnWriteFileSystem.moveFiles(), CopyOnWriteFileSystem.moveFilesWithRetry(), CopyOnWriteFileSystem.tryRevertMoves()
  • FileMove

    • 引用位置: 参数/方法调用/返回值
    • 关联成员: FileMove.moveCodec()
  • UpgradeProgress

    • 引用位置: 参数
  • ResolvableProfile

    • 引用位置: 参数/返回值
  • LevelStorageSource

    • 引用位置: 参数
  • PrimaryLevelData

    • 引用位置: 方法调用
    • 关联成员: PrimaryLevelData.writeLastPlayed(), PrimaryLevelData.writeVersionTag()