优化世界管理页面 (#3711)
* update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update
This commit is contained in:
@@ -518,6 +518,14 @@ public class DefaultGameRepository implements GameRepository {
|
||||
return new ModManager(this, version);
|
||||
}
|
||||
|
||||
public Path getSavesDirectory(String id) {
|
||||
return getRunDirectory(id).toPath().resolve("saves");
|
||||
}
|
||||
|
||||
public Path getBackupsDirectory(String id) {
|
||||
return getRunDirectory(id).toPath().resolve("backups");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
|
||||
@@ -23,14 +23,15 @@ import com.github.steveice10.opennbt.tag.builtin.LongTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.Unzipper;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
import org.jackhuang.hmcl.util.io.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -48,6 +49,7 @@ public class World {
|
||||
private String gameVersion;
|
||||
private long lastPlayed;
|
||||
private Image icon;
|
||||
private boolean isLocked;
|
||||
|
||||
public World(Path file) throws IOException {
|
||||
this.file = file;
|
||||
@@ -64,6 +66,7 @@ public class World {
|
||||
fileName = FileUtils.getName(file);
|
||||
Path levelDat = file.resolve("level.dat");
|
||||
getWorldName(levelDat);
|
||||
isLocked = isLocked(getSessionLockFile());
|
||||
|
||||
Path iconFile = file.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
@@ -93,6 +96,10 @@ public class World {
|
||||
return file.resolve("level.dat");
|
||||
}
|
||||
|
||||
public Path getSessionLockFile() {
|
||||
return file.resolve("session.lock");
|
||||
}
|
||||
|
||||
public long getLastPlayed() {
|
||||
return lastPlayed;
|
||||
}
|
||||
@@ -105,6 +112,10 @@ public class World {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return isLocked;
|
||||
}
|
||||
|
||||
private void loadFromZipImpl(Path root) throws IOException {
|
||||
Path levelDat = root.resolve("level.dat");
|
||||
if (!Files.exists(levelDat))
|
||||
@@ -125,6 +136,7 @@ public class World {
|
||||
}
|
||||
|
||||
private void loadFromZip() throws IOException {
|
||||
isLocked = false;
|
||||
try (FileSystem fs = CompressingUtils.readonly(file).setAutoDetectEncoding(true).build()) {
|
||||
Path cur = fs.getPath("/level.dat");
|
||||
if (Files.isRegularFile(cur)) {
|
||||
@@ -237,6 +249,26 @@ public class World {
|
||||
return parseLevelDat(getLevelDatFile());
|
||||
}
|
||||
|
||||
public FileChannel lock() throws WorldLockedException {
|
||||
Path lockFile = getSessionLockFile();
|
||||
FileChannel channel = null;
|
||||
try {
|
||||
channel = FileChannel.open(lockFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||
channel.write(ByteBuffer.wrap("\u2603".getBytes(StandardCharsets.UTF_8)));
|
||||
channel.force(true);
|
||||
FileLock fileLock = channel.tryLock();
|
||||
if (fileLock != null) {
|
||||
return channel;
|
||||
} else {
|
||||
IOUtils.closeQuietly(channel);
|
||||
throw new WorldLockedException("The world " + getFile() + " has been locked");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
IOUtils.closeQuietly(channel);
|
||||
throw new WorldLockedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeLevelDat(CompoundTag nbt) throws IOException {
|
||||
if (!Files.isDirectory(file))
|
||||
throw new IOException("Not a valid world directory");
|
||||
@@ -258,12 +290,25 @@ public class World {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isLocked(Path sessionLockFile) {
|
||||
try (FileChannel fileChannel = FileChannel.open(sessionLockFile, StandardOpenOption.WRITE)) {
|
||||
return fileChannel.tryLock() == null;
|
||||
} catch (AccessDeniedException accessDeniedException) {
|
||||
return true;
|
||||
} catch (NoSuchFileException noSuchFileException) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to open the lock file " + sessionLockFile, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Stream<World> getWorlds(Path savesDir) {
|
||||
try {
|
||||
if (Files.exists(savesDir)) {
|
||||
return Files.list(savesDir).flatMap(world -> {
|
||||
try {
|
||||
return Stream.of(new World(world));
|
||||
return Stream.of(new World(world.toAbsolutePath()));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read world " + world, e);
|
||||
return Stream.empty();
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class WorldLockedException extends IOException {
|
||||
public WorldLockedException() {
|
||||
}
|
||||
|
||||
public WorldLockedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public WorldLockedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public WorldLockedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user