[FEATURE][EXPERIMENTAL] Full support UpdatesProvider

This commit is contained in:
Gravita 2025-05-30 16:22:31 +07:00
parent 1e4e1c5837
commit e664c579a5
11 changed files with 152 additions and 47 deletions

View file

@ -27,6 +27,7 @@
import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.CommonHelper;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.SecurityHelper;
@ -305,6 +306,12 @@ public void checkCertificateExpired() {
}
}
public Path createTempDirectory(String name) throws IOException {
var path = tmpDir.resolve(String.format("launchserver-%s-%s", name, SecurityHelper.randomStringToken()));
Files.createDirectories(path);
return path;
}
private LauncherBinary binary() {
LaunchServerLauncherExeInit event = new LaunchServerLauncherExeInit(this, null);
modulesManager.invokeEvent(event);

View file

@ -9,7 +9,7 @@
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
@ -153,6 +153,14 @@ public void upload(String updateName, Map<String, Path> files, boolean deleteAft
}
}
@Override
public OutputStream upload(String updateName, String file) throws IOException {
var path = resolveUpdateName(updateName);
var target = path.resolve(file);
IOHelper.createParentDirs(target);
return new FileOutputStream(target.toFile());
}
@Override
public Map<String, Path> download(String updateName, List<String> files) {
var path = resolveUpdateName(updateName);
@ -163,6 +171,39 @@ public Map<String, Path> download(String updateName, List<String> files) {
return map;
}
@Override
public void download(String updateName, Map<String, Path> files) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files.entrySet()) {
var source = path.resolve(e.getKey());
var target = e.getValue();
IOHelper.copy(source, target);
}
}
@Override
public InputStream download(String updateName, String path) throws IOException {
return new FileInputStream(resolveUpdateName(updateName).resolve(path).toFile());
}
@Override
public void move(Map<UpdateNameAndFile, UpdateNameAndFile> files) throws IOException {
for(var e : files.entrySet()) {
var source = resolveUpdateName(e.getKey().updateName()).resolve(e.getKey().path());
var target = resolveUpdateName(e.getValue().updateName()).resolve(e.getValue().path());
IOHelper.move(source, target);
}
}
@Override
public void copy(Map<UpdateNameAndFile, UpdateNameAndFile> files) throws IOException {
for(var e : files.entrySet()) {
var source = resolveUpdateName(e.getKey().updateName()).resolve(e.getKey().path());
var target = resolveUpdateName(e.getValue().updateName()).resolve(e.getValue().path());
IOHelper.copy(source, target);
}
}
@Override
public void delete(String updateName, List<String> files) throws IOException {
var path = resolveUpdateName(updateName);

View file

@ -1,12 +1,22 @@
package pro.gravit.launchserver.auth.updates;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.hasher.HashedEntry;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.ProviderMap;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -38,7 +48,55 @@ public void sync() throws IOException {
public abstract void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException;
public abstract Map<String, Path> download(String updateName, List<String> files);
public abstract OutputStream upload(String updateName, String file) throws IOException;
public void upload(String updateName, Path dir, boolean deleteAfterUpload) throws IOException {
if(!Files.isDirectory(dir)) {
throw new UnsupportedEncodingException(String.format("%s is not a directory", dir));
}
Map<String, Path> map = new HashMap<>();
IOHelper.walk(dir, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
map.put(dir.relativize(file).toString(), file);
return FileVisitResult.CONTINUE;
}
}, true);
upload(updateName, map, deleteAfterUpload);
}
public abstract Map<String, Path> download(String updateName, List<String> files) throws IOException;
public abstract void download(String updateName, Map<String, Path> files) throws IOException;
public abstract InputStream download(String updateName, String path) throws IOException;
public abstract void move(Map<UpdateNameAndFile, UpdateNameAndFile> files) throws IOException;
public void move(String updateName, String newUpdateName) throws IOException {
create(newUpdateName);
var updatesDir = getUpdatesDir(updateName);
Map<UpdateNameAndFile, UpdateNameAndFile> map = new HashMap<>();
updatesDir.walk("/", (path, name, entry) -> {
map.put(UpdateNameAndFile.of(updateName, path), UpdateNameAndFile.of(newUpdateName, path));
return HashedDir.WalkAction.CONTINUE;
});
move(map);
delete(updateName);
}
public abstract void copy(Map<UpdateNameAndFile, UpdateNameAndFile> files) throws IOException;
public void copy(String updateName, String newUpdateName) throws IOException {
create(newUpdateName);
var updatesDir = getUpdatesDir(updateName);
Map<UpdateNameAndFile, UpdateNameAndFile> map = new HashMap<>();
updatesDir.walk("/", (path, name, entry) -> {
map.put(UpdateNameAndFile.of(updateName, path), UpdateNameAndFile.of(newUpdateName, path));
return HashedDir.WalkAction.CONTINUE;
});
copy(map);
}
public abstract void delete(String updateName, List<String> files) throws IOException;
@ -49,4 +107,10 @@ public void sync() throws IOException {
public void close() {
}
public record UpdateNameAndFile(String updateName, String path) {
public static UpdateNameAndFile of(String updateName, String path) {
return new UpdateNameAndFile(updateName, path);
}
}
}

View file

@ -44,7 +44,8 @@ public void invoke(String... args) throws Exception {
String versionName = args[0];
String dirName = IOHelper.verifyFileName(args.length > 1 ? args[1] : "assets");
String type = args.length > 2 ? args[2] : "mojang";
Path assetDir = server.updatesDir.resolve(dirName);
Path assetDir = server.createTempDirectory("assets");
var updatesDir = server.config.updatesProvider.getUpdatesDir(dirName);
// Create asset dir
if (Files.notExists(assetDir)) {
@ -91,14 +92,8 @@ public void invoke(String... args) throws Exception {
hash = hash.substring(0, 2) + "/" + hash;
var size = value.get("size").getAsLong();
var path = "objects/" + hash;
var target = assetDir.resolve(path);
if (Files.exists(target)) {
long fileSize = Files.size(target);
if (fileSize != size) {
logger.warn("File {} corrupted. Size {}, expected {}", target, size, fileSize);
} else {
continue;
}
if (updatesDir.findRecursive(path).isFound()) {
continue;
}
toDownload.add(new Downloader.SizedFile(hash, path, size));
}
@ -112,6 +107,8 @@ public void invoke(String... args) throws Exception {
server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
}
server.config.updatesProvider.upload(dirName, assetDir, true);
// Finished
server.syncUpdatesDir(Collections.singleton(dirName));
logger.info("Asset successfully downloaded: '{}'", dirName);

View file

@ -42,7 +42,7 @@ public void invoke(String... args) throws IOException, CommandException {
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]);
Path clientDir = server.updatesDir.resolve(dirName);
Path clientDir = server.createTempDirectory("client");
boolean isMirrorClientDownload = false;
if (args.length > 2) {
@ -97,6 +97,7 @@ public void invoke(String... args) throws IOException, CommandException {
}
}
server.config.profileProvider.addProfile(clientProfile);
server.config.updatesProvider.upload(dirName, clientDir, true);
// Finished
server.syncProfilesDir();

View file

@ -19,6 +19,8 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public final class IndexAssetCommand extends Command {
public static final String INDEXES_DIR = "indexes";
@ -54,24 +56,21 @@ public void invoke(String... args) throws Exception {
String inputAssetDirName = IOHelper.verifyFileName(args[0]);
String indexFileName = IOHelper.verifyFileName(args[1]);
String outputAssetDirName = IOHelper.verifyFileName(args[2]);
Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName);
Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName);
if (outputAssetDir.equals(inputAssetDir))
throw new CommandException("Unindexed and indexed asset dirs can't be same");
// Create new asset dir
logger.info("Creating indexed asset dir: '{}'", outputAssetDirName);
Files.createDirectory(outputAssetDir);
Path inputAssetDir = Path.of(inputAssetDirName);
Map<String, Path> uploadMap = new HashMap<>();
// Index objects
JsonObject objects = new JsonObject();
logger.info("Indexing objects");
IOHelper.walk(inputAssetDir, new IndexAssetVisitor(objects, inputAssetDir, outputAssetDir), false);
IOHelper.walk(inputAssetDir, new IndexAssetVisitor(objects, inputAssetDir, uploadMap), false);
server.config.updatesProvider.upload(outputAssetDirName, uploadMap, false);
// Write index file
logger.info("Writing asset index file: '{}'", indexFileName);
try (BufferedWriter writer = IOHelper.newWriter(resolveIndexFile(outputAssetDir, indexFileName))) {
var indexFile = resolveIndexFile(Path.of(""), indexFileName);
try (BufferedWriter writer = IOHelper.newWriter(server.config.updatesProvider.upload(outputAssetDirName, indexFile.toString()))) {
JsonObject result = new JsonObject();
result.add("objects", objects);
writer.write(Launcher.gsonManager.gson.toJson(result));
@ -95,12 +94,12 @@ public IndexObject(long size, String hash) {
private final class IndexAssetVisitor extends SimpleFileVisitor<Path> {
private final JsonObject objects;
private final Path inputAssetDir;
private final Path outputAssetDir;
private final Map<String, Path> uploadMap;
private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Path outputAssetDir) {
private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Map<String, Path> uploadMap) {
this.objects = objects;
this.inputAssetDir = inputAssetDir;
this.outputAssetDir = outputAssetDir;
this.uploadMap = uploadMap;
}
@Override
@ -112,7 +111,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
String digest = SecurityHelper.toHex(SecurityHelper.digest(DigestAlgorithm.SHA1, file));
IndexObject obj = new IndexObject(attrs.size(), digest);
objects.add(name, Launcher.gsonManager.gson.toJsonTree(obj));
IOHelper.copy(file, resolveObjectFile(outputAssetDir, digest));
uploadMap.put(resolveObjectFile(Path.of(""), digest).toString(), file);
// Continue visiting
return super.visitFile(file, attrs);

View file

@ -14,6 +14,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public final class UnindexAssetCommand extends Command {
@ -39,10 +40,8 @@ public void invoke(String... args) throws Exception {
String inputAssetDirName = IOHelper.verifyFileName(args[0]);
String indexFileName = IOHelper.verifyFileName(args[1]);
String outputAssetDirName = IOHelper.verifyFileName(args[2]);
Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName);
Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName);
if (outputAssetDir.equals(inputAssetDir))
throw new CommandException("Indexed and unindexed asset dirs can't be same");
var updatesDir = server.config.updatesProvider.getUpdatesDir(inputAssetDirName);
Path outputAssetDir = Path.of(outputAssetDirName);
// Create new asset dir
logger.info("Creating unindexed asset dir: '{}'", outputAssetDirName);
@ -51,7 +50,8 @@ public void invoke(String... args) throws Exception {
// Read JSON file
JsonObject objects;
logger.info("Reading asset index file: '{}'", indexFileName);
try (BufferedReader reader = IOHelper.newReader(IndexAssetCommand.resolveIndexFile(inputAssetDir, indexFileName))) {
Path indexFilePath = IndexAssetCommand.resolveIndexFile(Path.of(""), indexFileName);
try (BufferedReader reader = IOHelper.newReader(server.config.updatesProvider.download(inputAssetDirName, indexFilePath.toString()))) {
objects = JsonParser.parseReader(reader).getAsJsonObject().get("objects").getAsJsonObject();
}
@ -63,12 +63,12 @@ public void invoke(String... args) throws Exception {
// Copy hashed file to target
String hash = member.getValue().getAsJsonObject().get("hash").getAsString();
Path source = IndexAssetCommand.resolveObjectFile(inputAssetDir, hash);
IOHelper.copy(source, outputAssetDir.resolve(name));
Path source = IndexAssetCommand.resolveObjectFile(Path.of(""), hash);
server.config.updatesProvider.download(inputAssetDirName, Map.of(source.toString(), outputAssetDir.resolve(name)));
}
// Finished
server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
logger.info("Asset successfully unindexed: '{}'", inputAssetDirName);
logger.info("Asset successfully unindexed: '{}'", outputAssetDir.toAbsolutePath().toString());
}
}

View file

@ -48,17 +48,7 @@ public void invoke(String... args) throws Exception {
profile.getServers().getFirst().name = args[1];
}
logger.info("Copy {} to {}", profile.getDir(), args[1]);
var src = server.updatesDir.resolve(profile.getDir());
var dest = server.updatesDir.resolve(args[1]);
try (Stream<Path> stream = Files.walk(src)) {
stream.forEach(source -> {
try {
IOHelper.copy(source, dest.resolve(src.relativize(source)));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
server.config.updatesProvider.copy(profile.getDir(), args[1]);
builder.setDir(args[1]);
profile = builder.createClientProfile();
server.config.profileProvider.addProfile(profile);

View file

@ -7,6 +7,8 @@
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.MakeProfileHelper;
import java.nio.file.Path;
public class MakeProfileCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
@ -28,7 +30,7 @@ public String getUsageDescription() {
public void invoke(String... args) throws Exception {
verifyArgs(args, 3);
ClientProfile.Version version = parseClientVersion(args[1]);
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(server.updatesDir.resolve(args[2]), version);
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(Path.of(args[2]), version);
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.info("Detected option {}", option);
}

View file

@ -295,6 +295,10 @@ public FindRecursiveResult(HashedDir parent, HashedEntry entry, String name) {
this.entry = entry;
this.name = name;
}
public boolean isFound() {
return entry != null;
}
}
public static final class Diff {

@ -1 +1 @@
Subproject commit 5d45e024375779aef4f6dafff6ea5efba8c250f0
Subproject commit 287d0b83b3c71fc7146e2b1cba7bc1760629ade1