331 lines
12 KiB
Java
331 lines
12 KiB
Java
package com.hypixel.hytale.builtin.asseteditor;
|
|
|
|
import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler;
|
|
import com.hypixel.hytale.builtin.asseteditor.data.AssetState;
|
|
import com.hypixel.hytale.builtin.asseteditor.data.ModifiedAsset;
|
|
import com.hypixel.hytale.builtin.asseteditor.util.AssetPathUtil;
|
|
import com.hypixel.hytale.common.util.FormatUtil;
|
|
import com.hypixel.hytale.common.util.ListUtil;
|
|
import com.hypixel.hytale.common.util.PathUtil;
|
|
import com.hypixel.hytale.logger.HytaleLogger;
|
|
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetListSetup;
|
|
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFileEntry;
|
|
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFileTree;
|
|
import com.hypixel.hytale.server.core.asset.common.CommonAssetModule;
|
|
import com.hypixel.hytale.server.core.util.io.FileUtil;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.io.IOException;
|
|
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.Comparator;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.locks.StampedLock;
|
|
import java.util.logging.Level;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
public class AssetTree {
|
|
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
|
|
private final StampedLock lock = new StampedLock();
|
|
private final Path rootPath;
|
|
private final String packKey;
|
|
private final boolean isReadOnly;
|
|
private final boolean canBeDeleted;
|
|
List<AssetEditorFileEntry> serverAssets = new ObjectArrayList();
|
|
List<AssetEditorFileEntry> commonAssets = new ObjectArrayList();
|
|
|
|
public AssetTree(Path rootPath, String packKey, boolean isReadOnly, boolean canBeDeleted) {
|
|
this.rootPath = rootPath;
|
|
this.packKey = packKey;
|
|
this.isReadOnly = isReadOnly;
|
|
this.canBeDeleted = canBeDeleted;
|
|
}
|
|
|
|
public AssetTree(Path rootPath, String packKey, boolean isReadOnly, boolean canBeDeleted, @Nonnull Collection<AssetTypeHandler> assetTypes) {
|
|
this.rootPath = rootPath;
|
|
this.packKey = packKey;
|
|
this.isReadOnly = isReadOnly;
|
|
this.canBeDeleted = canBeDeleted;
|
|
this.load(assetTypes);
|
|
}
|
|
|
|
public void replaceAssetTree(@Nonnull AssetTree assetTree) {
|
|
long stamp = this.lock.writeLock();
|
|
|
|
try {
|
|
this.serverAssets = assetTree.serverAssets;
|
|
this.commonAssets = assetTree.commonAssets;
|
|
} finally {
|
|
this.lock.unlockWrite(stamp);
|
|
}
|
|
}
|
|
|
|
public void sendPackets(@Nonnull EditorClient editorClient) {
|
|
long stamp = this.lock.readLock();
|
|
|
|
try {
|
|
editorClient.getPacketHandler()
|
|
.write(
|
|
new AssetEditorAssetListSetup(
|
|
this.packKey, this.isReadOnly, this.canBeDeleted, AssetEditorFileTree.Server, this.serverAssets.toArray(AssetEditorFileEntry[]::new)
|
|
)
|
|
);
|
|
editorClient.getPacketHandler()
|
|
.write(
|
|
new AssetEditorAssetListSetup(
|
|
this.packKey, this.isReadOnly, this.canBeDeleted, AssetEditorFileTree.Common, this.commonAssets.toArray(AssetEditorFileEntry[]::new)
|
|
)
|
|
);
|
|
} finally {
|
|
this.lock.unlockRead(stamp);
|
|
}
|
|
}
|
|
|
|
public boolean isDirectoryEmpty(@Nonnull Path path) {
|
|
String pathString = PathUtil.toUnixPathString(path);
|
|
long stamp = this.lock.readLock();
|
|
|
|
boolean hasFile;
|
|
try {
|
|
List<AssetEditorFileEntry> assets = this.getAssetListForPath(path);
|
|
int index = ListUtil.binarySearch(assets, o -> o.path, pathString, String::compareTo);
|
|
if (index < 0) {
|
|
return true;
|
|
}
|
|
|
|
if (!assets.get(index).isDirectory) {
|
|
return false;
|
|
}
|
|
|
|
int fileIndex = index + 1;
|
|
if (fileIndex < assets.size()) {
|
|
hasFile = assets.get(fileIndex).path.startsWith(pathString + "/");
|
|
return !hasFile;
|
|
}
|
|
|
|
hasFile = false;
|
|
} finally {
|
|
this.lock.unlockRead(stamp);
|
|
}
|
|
|
|
return hasFile;
|
|
}
|
|
|
|
@Nullable
|
|
public AssetEditorFileEntry ensureAsset(@Nonnull Path path, boolean isDirectory) {
|
|
String pathString = PathUtil.toUnixPathString(path);
|
|
long stamp = this.lock.writeLock();
|
|
|
|
Object insertionPoint;
|
|
try {
|
|
List<AssetEditorFileEntry> assets = this.getAssetListForPath(path);
|
|
int index = ListUtil.binarySearch(assets, o -> o.path, pathString, String::compareTo);
|
|
if (index < 0) {
|
|
int insertionPointx = -(index + 1);
|
|
if (path.getNameCount() > 1) {
|
|
Path parentPath = path.getName(0);
|
|
|
|
for (int i = 1; i < path.getNameCount() - 1; i++) {
|
|
parentPath = parentPath.resolve(path.getName(i));
|
|
String name = PathUtil.toUnixPathString(parentPath);
|
|
if (insertionPointx <= 0 || !assets.get(insertionPointx - 1).path.startsWith(name)) {
|
|
assets.add(insertionPointx++, new AssetEditorFileEntry(name, true));
|
|
}
|
|
}
|
|
}
|
|
|
|
AssetEditorFileEntry entry = new AssetEditorFileEntry(pathString, isDirectory);
|
|
assets.add(insertionPointx, entry);
|
|
return entry;
|
|
}
|
|
|
|
insertionPoint = null;
|
|
} finally {
|
|
this.lock.unlockWrite(stamp);
|
|
}
|
|
|
|
return (AssetEditorFileEntry)insertionPoint;
|
|
}
|
|
|
|
@Nullable
|
|
public AssetEditorFileEntry getAssetFile(@Nonnull Path path) {
|
|
String pathString = PathUtil.toUnixPathString(path);
|
|
long stamp = this.lock.readLock();
|
|
|
|
AssetEditorFileEntry var7;
|
|
try {
|
|
List<AssetEditorFileEntry> assets = this.getAssetListForPath(path);
|
|
int index = ListUtil.binarySearch(assets, o -> o.path, pathString, String::compareTo);
|
|
var7 = index >= 0 ? assets.get(index) : null;
|
|
} finally {
|
|
this.lock.unlockRead(stamp);
|
|
}
|
|
|
|
return var7;
|
|
}
|
|
|
|
@Nullable
|
|
public AssetEditorFileEntry removeAsset(@Nonnull Path path) {
|
|
String pathString = PathUtil.toUnixPathString(path);
|
|
long stamp = this.lock.writeLock();
|
|
|
|
AssetEditorFileEntry entry;
|
|
try {
|
|
List<AssetEditorFileEntry> assets = this.getAssetListForPath(path);
|
|
int index = ListUtil.binarySearch(assets, o -> o.path, pathString, String::compareTo);
|
|
if (index >= 0) {
|
|
entry = assets.remove(index);
|
|
if (entry.isDirectory) {
|
|
String pathPrefix = pathString + "/";
|
|
int removeCount = 0;
|
|
|
|
for (int i = index; i < assets.size(); i++) {
|
|
AssetEditorFileEntry asset = assets.get(i);
|
|
if (!asset.path.startsWith(pathPrefix)) {
|
|
break;
|
|
}
|
|
|
|
removeCount++;
|
|
}
|
|
|
|
for (int i = 0; i < removeCount; i++) {
|
|
assets.remove(index);
|
|
}
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
entry = null;
|
|
} finally {
|
|
this.lock.unlockWrite(stamp);
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
public void applyAssetChanges(@Nonnull Map<Path, ModifiedAsset> createdDirectories, @Nonnull Map<Path, ModifiedAsset> modifiedAssets) {
|
|
for (ModifiedAsset dir : createdDirectories.values()) {
|
|
this.ensureAsset(dir.path, true);
|
|
}
|
|
|
|
for (ModifiedAsset file : modifiedAssets.values()) {
|
|
if (file.state == AssetState.NEW) {
|
|
this.ensureAsset(file.path, false);
|
|
} else if (file.state == AssetState.DELETED) {
|
|
this.removeAsset(file.oldPath != null ? file.oldPath : file.path);
|
|
} else if (file.oldPath != null) {
|
|
this.removeAsset(file.oldPath);
|
|
this.ensureAsset(file.path, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<AssetEditorFileEntry> getAssetListForPath(@Nonnull Path path) {
|
|
if (path.getNameCount() > 0) {
|
|
String firstName = path.getName(0).toString();
|
|
if (firstName.equals("..")) {
|
|
try {
|
|
firstName = path.getName(2).toString();
|
|
} catch (IllegalArgumentException var4) {
|
|
}
|
|
}
|
|
|
|
if ("Server".equals(firstName)) {
|
|
return this.serverAssets;
|
|
}
|
|
|
|
if ("Common".equals(firstName)) {
|
|
return this.commonAssets;
|
|
}
|
|
}
|
|
|
|
throw new IllegalArgumentException("Invalid path " + path);
|
|
}
|
|
|
|
private void load(@Nonnull Collection<AssetTypeHandler> assetTypes) {
|
|
try {
|
|
long start = System.nanoTime();
|
|
loadServerAssets(this.rootPath, assetTypes, this.serverAssets);
|
|
LOGGER.at(Level.INFO).log("Loaded Server/ asset tree! Took: %s", FormatUtil.nanosToString(System.nanoTime() - start));
|
|
} catch (IOException var5) {
|
|
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var5)).log("Failed to load server asset tree!");
|
|
}
|
|
|
|
try {
|
|
long start = System.nanoTime();
|
|
walkFileTree(this.rootPath, this.rootPath.resolve("Common"), this.commonAssets);
|
|
LOGGER.at(Level.INFO).log("Loaded Common/ asset tree! Took: %s", FormatUtil.nanosToString(System.nanoTime() - start));
|
|
} catch (IOException var4) {
|
|
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Failed to load common asset tree!");
|
|
}
|
|
|
|
long start = System.nanoTime();
|
|
this.serverAssets.sort(Comparator.comparing(o -> o.path));
|
|
this.commonAssets.sort(Comparator.comparing(o -> o.path));
|
|
LOGGER.at(Level.INFO).log("Sorted asset tree! Took: %s", FormatUtil.nanosToString(System.nanoTime() - start));
|
|
}
|
|
|
|
private static void loadServerAssets(@Nonnull Path root, @Nonnull Collection<AssetTypeHandler> assetTypes, @Nonnull List<AssetEditorFileEntry> files) throws IOException {
|
|
Set<String> assetTypePaths = new HashSet<>();
|
|
Set<String> subPaths = new HashSet<>();
|
|
|
|
for (AssetTypeHandler assetTypeHandler : assetTypes) {
|
|
if (assetTypeHandler.getRootPath().startsWith(AssetPathUtil.PATH_DIR_SERVER)) {
|
|
String assetTypePath = assetTypeHandler.getConfig().path;
|
|
if (assetTypePaths.add(assetTypePath)) {
|
|
Path path = Path.of(assetTypePath);
|
|
Path subpath = AssetPathUtil.PATH_DIR_SERVER;
|
|
|
|
for (int i = 1; i < path.getNameCount() - 1; i++) {
|
|
subpath = subpath.resolve(path.getName(i));
|
|
String name = PathUtil.toUnixPathString(subpath);
|
|
if (subPaths.add(name)) {
|
|
files.add(new AssetEditorFileEntry(name, true));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (String path : assetTypePaths) {
|
|
Path dirPath = root.resolve(path);
|
|
walkFileTree(root, dirPath, files);
|
|
String name = PathUtil.toUnixPathString(root.relativize(dirPath));
|
|
files.add(new AssetEditorFileEntry(name, true));
|
|
}
|
|
}
|
|
|
|
private static void walkFileTree(@Nonnull final Path root, @Nonnull final Path dirPath, @Nonnull final List<AssetEditorFileEntry> files) throws IOException {
|
|
if (Files.isDirectory(dirPath)) {
|
|
Files.walkFileTree(dirPath, FileUtil.DEFAULT_WALK_TREE_OPTIONS_SET, Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
|
|
public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException {
|
|
if (path.equals(dirPath)) {
|
|
return FileVisitResult.CONTINUE;
|
|
} else {
|
|
files.add(new AssetEditorFileEntry(PathUtil.toUnixPathString(root.relativize(path)), true));
|
|
return super.preVisitDirectory(path, attrs);
|
|
}
|
|
}
|
|
|
|
@Nonnull
|
|
public FileVisitResult visitFile(@Nonnull Path path, @Nonnull BasicFileAttributes attrs) {
|
|
if (CommonAssetModule.IGNORED_FILES.contains(path.getFileName())) {
|
|
return FileVisitResult.CONTINUE;
|
|
} else {
|
|
files.add(new AssetEditorFileEntry(PathUtil.toUnixPathString(root.relativize(path)), false));
|
|
return FileVisitResult.CONTINUE;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|