hytale-server/com/hypixel/hytale/builtin/asseteditor/AssetEditorPlugin.java

1872 lines
93 KiB
Java

package com.hypixel.hytale.builtin.asseteditor;
import com.hypixel.hytale.assetstore.AssetMap;
import com.hypixel.hytale.assetstore.AssetPack;
import com.hypixel.hytale.assetstore.AssetRegistry;
import com.hypixel.hytale.assetstore.AssetStore;
import com.hypixel.hytale.assetstore.AssetUpdateQuery;
import com.hypixel.hytale.assetstore.event.AssetMonitorEvent;
import com.hypixel.hytale.assetstore.event.AssetStoreMonitorEvent;
import com.hypixel.hytale.assetstore.event.RegisterAssetStoreEvent;
import com.hypixel.hytale.assetstore.event.RemoveAssetStoreEvent;
import com.hypixel.hytale.assetstore.map.JsonAssetWithMap;
import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetStoreTypeHandler;
import com.hypixel.hytale.builtin.asseteditor.assettypehandler.AssetTypeHandler;
import com.hypixel.hytale.builtin.asseteditor.assettypehandler.CommonAssetTypeHandler;
import com.hypixel.hytale.builtin.asseteditor.assettypehandler.JsonTypeHandler;
import com.hypixel.hytale.builtin.asseteditor.data.AssetUndoRedoInfo;
import com.hypixel.hytale.builtin.asseteditor.data.ModifiedAsset;
import com.hypixel.hytale.builtin.asseteditor.datasource.DataSource;
import com.hypixel.hytale.builtin.asseteditor.datasource.StandardDataSource;
import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorAssetCreatedEvent;
import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorClientDisconnectEvent;
import com.hypixel.hytale.builtin.asseteditor.event.AssetEditorSelectAssetEvent;
import com.hypixel.hytale.builtin.asseteditor.util.AssetPathUtil;
import com.hypixel.hytale.builtin.asseteditor.util.AssetStoreUtil;
import com.hypixel.hytale.builtin.asseteditor.util.BsonTransformationUtil;
import com.hypixel.hytale.codec.EmptyExtraInfo;
import com.hypixel.hytale.codec.schema.SchemaContext;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.common.plugin.AuthorInfo;
import com.hypixel.hytale.common.plugin.PluginIdentifier;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.common.semver.Semver;
import com.hypixel.hytale.common.util.ArrayUtil;
import com.hypixel.hytale.common.util.FormatUtil;
import com.hypixel.hytale.common.util.PathUtil;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.event.IEventDispatcher;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.protocol.Packet;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAsset;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetListUpdate;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetPackSetup;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorAssetUpdated;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorCapabilities;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorDeleteAssetPack;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorEditorType;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetFinalize;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetInitialize;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportAssetPart;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportComplete;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorExportDeleteAssets;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchAssetReply;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFetchJsonAssetWithParentsReply;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorFileEntry;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorJsonAssetUpdated;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorLastModifiedAssets;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorPopupNotificationType;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRebuildCaches;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorRequestChildrenListReply;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorSetupSchemas;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUndoRedoReply;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetEditorUpdateAssetPack;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetInfo;
import com.hypixel.hytale.protocol.packets.asseteditor.AssetPackManifest;
import com.hypixel.hytale.protocol.packets.asseteditor.JsonUpdateCommand;
import com.hypixel.hytale.protocol.packets.asseteditor.JsonUpdateType;
import com.hypixel.hytale.protocol.packets.asseteditor.SchemaFile;
import com.hypixel.hytale.protocol.packets.asseteditor.TimestampedAssetReference;
import com.hypixel.hytale.protocol.packets.assets.UpdateTranslations;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.Options;
import com.hypixel.hytale.server.core.asset.AssetModule;
import com.hypixel.hytale.server.core.asset.AssetPackRegisterEvent;
import com.hypixel.hytale.server.core.asset.AssetPackUnregisterEvent;
import com.hypixel.hytale.server.core.asset.AssetRegistryLoader;
import com.hypixel.hytale.server.core.asset.common.events.CommonAssetMonitorEvent;
import com.hypixel.hytale.server.core.io.PacketHandler;
import com.hypixel.hytale.server.core.io.ServerManager;
import com.hypixel.hytale.server.core.io.handlers.InitialPacketHandler;
import com.hypixel.hytale.server.core.modules.i18n.I18nModule;
import com.hypixel.hytale.server.core.modules.i18n.event.MessagesUpdated;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.plugin.PluginManager;
import com.hypixel.hytale.server.core.plugin.PluginState;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.BsonUtil;
import com.hypixel.hytale.server.core.util.io.FileUtil;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
public class AssetEditorPlugin extends JavaPlugin {
private static AssetEditorPlugin instance;
private final StampedLock globalEditLock = new StampedLock();
private final Map<UUID, Set<EditorClient>> uuidToEditorClients = new ConcurrentHashMap<>();
private final Map<EditorClient, AssetPath> clientOpenAssetPathMapping = new ConcurrentHashMap<>();
private final Set<EditorClient> clientsSubscribedToModifiedAssetsChanges = ConcurrentHashMap.newKeySet();
@Nonnull
private Map<String, Schema> schemas = new Object2ObjectOpenHashMap();
private AssetEditorSetupSchemas setupSchemasPacket;
private final StampedLock initLock = new StampedLock();
private final Set<EditorClient> initQueue = new HashSet<>();
@Nonnull
private AssetEditorPlugin.InitState initState = AssetEditorPlugin.InitState.NOT_INITIALIZED;
@Nullable
private ScheduledFuture<?> scheduledReinitFuture;
private final Map<String, DataSource> assetPackDataSources = new ConcurrentHashMap<>();
private final AssetTypeRegistry assetTypeRegistry = new AssetTypeRegistry();
private final UndoRedoManager undoRedoManager = new UndoRedoManager();
private ScheduledFuture<?> pingClientsTask;
public static AssetEditorPlugin get() {
return instance;
}
public AssetEditorPlugin(@Nonnull JavaPluginInit init) {
super(init);
}
@NullableDecl
DataSource registerDataSourceForPack(AssetPack assetPack) {
PluginManifest manifest = assetPack.getManifest();
if (manifest == null) {
this.getLogger().at(Level.SEVERE).log("Could not load asset pack manifest for " + assetPack.getName());
return null;
} else {
StandardDataSource dataSource = new StandardDataSource(assetPack.getName(), assetPack.getRoot(), assetPack.isImmutable(), manifest);
this.assetPackDataSources.put(assetPack.getName(), dataSource);
return dataSource;
}
}
@Override
protected void setup() {
instance = this;
for (AssetPack assetPack : AssetModule.get().getAssetPacks()) {
this.registerDataSourceForPack(assetPack);
}
ServerManager.get().registerSubPacketHandlers(AssetEditorGamePacketHandler::new);
InitialPacketHandler.EDITOR_PACKET_HANDLER_SUPPLIER = AssetEditorPacketHandler::new;
for (AssetStore<?, ?, ?> assetStore : AssetRegistry.getStoreMap().values()) {
if (assetStore.getPath() != null && !assetStore.getPath().startsWith("../")) {
this.assetTypeRegistry.registerAssetType(new AssetStoreTypeHandler(assetStore));
}
}
this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Texture", "Texture.png", ".png", AssetEditorEditorType.Texture));
this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Model", "Model.png", ".blockymodel", AssetEditorEditorType.JsonSource));
this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Animation", "Animation.png", ".blockyanim", AssetEditorEditorType.JsonSource));
this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Sound", null, ".ogg", AssetEditorEditorType.None));
this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("UI", null, ".ui", AssetEditorEditorType.Text));
this.assetTypeRegistry.registerAssetType(new CommonAssetTypeHandler("Language", null, ".lang", AssetEditorEditorType.Text));
this.getEventRegistry().register(RegisterAssetStoreEvent.class, this::onRegisterAssetStore);
this.getEventRegistry().register(RemoveAssetStoreEvent.class, this::onUnregisterAssetStore);
this.getEventRegistry().register(AssetPackRegisterEvent.class, this::onRegisterAssetPack);
this.getEventRegistry().register(AssetPackUnregisterEvent.class, this::onUnregisterAssetPack);
this.getEventRegistry().register(AssetStoreMonitorEvent.class, this::onAssetMonitor);
this.getEventRegistry().register(CommonAssetMonitorEvent.class, this::onAssetMonitor);
this.getEventRegistry().register(MessagesUpdated.class, this::onI18nMessagesUpdated);
AssetSpecificFunctionality.setup();
}
@Override
protected void start() {
for (DataSource dataSource : this.assetPackDataSources.values()) {
dataSource.start();
}
this.pingClientsTask = HytaleServer.SCHEDULED_EXECUTOR.scheduleAtFixedRate(this::sendPingPackets, 1L, 1L, PacketHandler.PingInfo.PING_FREQUENCY_UNIT);
}
@Override
protected void shutdown() {
InitialPacketHandler.EDITOR_PACKET_HANDLER_SUPPLIER = null;
String message = HytaleServer.get().isShuttingDown() ? "Server is shutting down!" : "Asset editor was disabled!";
for (Set<EditorClient> clients : this.uuidToEditorClients.values()) {
for (EditorClient client : clients) {
client.getPacketHandler().disconnect(message);
}
}
this.pingClientsTask.cancel(false);
for (DataSource dataSource : this.assetPackDataSources.values()) {
dataSource.shutdown();
}
}
public DataSource getDataSourceForPath(AssetPath path) {
return this.getDataSourceForPack(path.packId());
}
public DataSource getDataSourceForPack(String assetPack) {
return this.assetPackDataSources.get(assetPack);
}
public Collection<DataSource> getDataSources() {
return this.assetPackDataSources.values();
}
public AssetTypeRegistry getAssetTypeRegistry() {
return this.assetTypeRegistry;
}
public Schema getSchema(String id) {
return this.schemas.get(id);
}
public Map<EditorClient, AssetPath> getClientOpenAssetPathMapping() {
return this.clientOpenAssetPathMapping;
}
public Set<EditorClient> getEditorClients(UUID uuid) {
return this.uuidToEditorClients.get(uuid);
}
private void sendPingPackets() {
for (Set<EditorClient> clients : this.uuidToEditorClients.values()) {
for (EditorClient client : clients) {
try {
client.getPacketHandler().sendPing();
} catch (Exception var6) {
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(var6)).log("Failed to send ping to " + client);
client.getPacketHandler().disconnect("Exception when sending ping packet!");
}
}
}
}
@Nonnull
private List<EditorClient> getClientsWithOpenAssetPath(AssetPath path) {
if (this.clientOpenAssetPathMapping.isEmpty()) {
return Collections.emptyList();
} else {
List<EditorClient> list = new ObjectArrayList();
for (Entry<EditorClient, AssetPath> entry : this.clientOpenAssetPathMapping.entrySet()) {
if (entry.getValue().equals(path)) {
list.add(entry.getKey());
}
}
return list;
}
}
public AssetPath getOpenAssetPath(EditorClient editorClient) {
return this.clientOpenAssetPathMapping.get(editorClient);
}
private void onRegisterAssetPack(AssetPackRegisterEvent event) {
if (!this.assetPackDataSources.containsKey(event.getAssetPack().getName())) {
DataSource dataSource = this.registerDataSourceForPack(event.getAssetPack());
if (dataSource != null) {
if (this.getState() == PluginState.ENABLED) {
dataSource.start();
}
AssetTree tempAssetTree = dataSource.loadAssetTree(this.assetTypeRegistry.getRegisteredAssetTypeHandlers().values());
long globalEditStamp = this.globalEditLock.writeLock();
try {
dataSource.getAssetTree().replaceAssetTree(tempAssetTree);
} finally {
this.globalEditLock.unlockWrite(globalEditStamp);
}
this.broadcastPackAddedOrUpdated(event.getAssetPack().getName(), dataSource.getManifest());
for (Set<EditorClient> clients : this.uuidToEditorClients.values()) {
for (EditorClient client : clients) {
dataSource.getAssetTree().sendPackets(client);
}
}
}
}
}
private void onUnregisterAssetPack(AssetPackUnregisterEvent event) {
if (this.assetPackDataSources.containsKey(event.getAssetPack().getName())) {
DataSource dataSource = this.assetPackDataSources.remove(event.getAssetPack().getName());
dataSource.shutdown();
for (Set<EditorClient> clients : this.uuidToEditorClients.values()) {
for (EditorClient client : clients) {
client.getPacketHandler().write(new AssetEditorDeleteAssetPack(event.getAssetPack().getName()));
}
}
}
}
private void onI18nMessagesUpdated(@Nonnull MessagesUpdated event) {
if (!this.clientOpenAssetPathMapping.isEmpty()) {
I18nModule i18nModule = I18nModule.get();
Map<String, Map<String, String>> changed = event.getChangedMessages();
Map<String, Map<String, String>> removed = event.getRemovedMessages();
Map<String, UpdateTranslations[]> updatePackets = new Object2ObjectOpenHashMap();
for (EditorClient client : this.clientOpenAssetPathMapping.keySet()) {
String languageKey = client.getLanguage();
UpdateTranslations[] packets = updatePackets.get(languageKey);
if (packets == null) {
packets = i18nModule.getUpdatePacketsForChanges(languageKey, changed, removed);
updatePackets.put(languageKey, packets);
}
if (packets.length != 0) {
client.getPacketHandler().write(packets);
}
}
}
}
private void onRegisterAssetStore(@Nonnull RegisterAssetStoreEvent event) {
AssetStore<?, ? extends JsonAssetWithMap<?, ? extends AssetMap<?, ?>>, ? extends AssetMap<?, ? extends JsonAssetWithMap<?, ?>>> assetStore = (AssetStore<?, ? extends JsonAssetWithMap<?, ? extends AssetMap<?, ?>>, ? extends AssetMap<?, ? extends JsonAssetWithMap<?, ?>>>)event.getAssetStore();
if (assetStore.getPath() != null && !assetStore.getPath().startsWith("../")) {
this.assetTypeRegistry.registerAssetType(new AssetStoreTypeHandler(assetStore));
long stamp = this.initLock.readLock();
try {
if (this.initState != AssetEditorPlugin.InitState.NOT_INITIALIZED) {
if (this.scheduledReinitFuture != null) {
this.scheduledReinitFuture.cancel(false);
}
this.scheduledReinitFuture = HytaleServer.SCHEDULED_EXECUTOR.schedule(this::tryReinitializeAssetEditor, 1L, TimeUnit.SECONDS);
}
} finally {
this.initLock.unlockRead(stamp);
}
}
}
private void onUnregisterAssetStore(@Nonnull RemoveAssetStoreEvent event) {
AssetStore<?, ? extends JsonAssetWithMap<?, ? extends AssetMap<?, ?>>, ? extends AssetMap<?, ? extends JsonAssetWithMap<?, ?>>> assetStore = (AssetStore<?, ? extends JsonAssetWithMap<?, ? extends AssetMap<?, ?>>, ? extends AssetMap<?, ? extends JsonAssetWithMap<?, ?>>>)event.getAssetStore();
if (assetStore.getPath() != null && !assetStore.getPath().startsWith("../")) {
this.assetTypeRegistry.unregisterAssetType(new AssetStoreTypeHandler(assetStore));
long stamp = this.initLock.readLock();
try {
if (this.initState != AssetEditorPlugin.InitState.NOT_INITIALIZED) {
if (this.scheduledReinitFuture != null) {
this.scheduledReinitFuture.cancel(false);
}
this.scheduledReinitFuture = HytaleServer.SCHEDULED_EXECUTOR.schedule(this::tryReinitializeAssetEditor, 1L, TimeUnit.SECONDS);
}
} finally {
this.initLock.unlockRead(stamp);
}
}
}
private void tryReinitializeAssetEditor() {
long stamp = this.initLock.writeLock();
try {
switch (this.initState) {
case INITIALIZING:
this.scheduledReinitFuture = HytaleServer.SCHEDULED_EXECUTOR.schedule(this::tryReinitializeAssetEditor, 1L, TimeUnit.SECONDS);
break;
case INITIALIZED:
this.initState = AssetEditorPlugin.InitState.INITIALIZING;
this.scheduledReinitFuture = null;
this.getLogger().at(Level.INFO).log("Starting asset editor re-initialization");
ForkJoinPool.commonPool().execute(() -> this.initializeAssetEditor(true));
}
} finally {
this.initLock.unlockWrite(stamp);
}
}
private void onAssetMonitor(@Nonnull AssetMonitorEvent<Void> event) {
AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate();
packet.pack = event.getAssetPack();
ObjectArrayList<AssetEditorFileEntry> newFiles = new ObjectArrayList();
DataSource dataSource = this.getDataSourceForPack(event.getAssetPack());
if (dataSource != null) {
if (!event.getRemovedFilesAndDirectories().isEmpty()) {
ObjectArrayList<AssetEditorFileEntry> deletions = new ObjectArrayList();
for (Path path : event.getRemovedFilesAndDirectories()) {
Path relativePath = PathUtil.relativizePretty(dataSource.getRootPath(), path);
AssetEditorFileEntry assetFile = dataSource.getAssetTree().removeAsset(relativePath);
if (assetFile != null) {
deletions.add(assetFile);
}
}
packet.deletions = (AssetEditorFileEntry[])deletions.toArray(AssetEditorFileEntry[]::new);
}
if (!event.getRemovedFilesToUnload().isEmpty()) {
event.getRemovedFilesToUnload().removeIf(p -> {
Path relativePathx = PathUtil.relativizePretty(dataSource.getRootPath(), p);
if (!dataSource.shouldReloadAssetFromDisk(relativePathx)) {
this.getLogger().at(Level.INFO).log("Skipping reloading %s from file monitor event because there is changes made via the asset editor", p);
return true;
} else {
long globalEditStamp = this.globalEditLock.writeLock();
try {
this.undoRedoManager.clearUndoRedoStack(new AssetPath(event.getAssetPack(), relativePathx));
} finally {
this.globalEditLock.unlockWrite(globalEditStamp);
}
return false;
}
});
}
if (!event.getCreatedOrModifiedDirectories().isEmpty()) {
for (Path assetFile : event.getCreatedOrModifiedDirectories()) {
Path relativePath = PathUtil.relativizePretty(dataSource.getRootPath(), assetFile);
AssetEditorFileEntry addedAsset = dataSource.getAssetTree().ensureAsset(relativePath, true);
if (addedAsset != null) {
newFiles.add(addedAsset);
}
}
}
if (!event.getCreatedOrModifiedFilesToLoad().isEmpty()) {
event.getCreatedOrModifiedFilesToLoad()
.removeIf(
pathx -> {
Path relativePathx = PathUtil.relativizePretty(dataSource.getRootPath(), pathx);
AssetEditorFileEntry addedAssetx = dataSource.getAssetTree().ensureAsset(relativePathx, false);
if (addedAssetx != null) {
newFiles.add(addedAssetx);
return false;
} else if (!dataSource.shouldReloadAssetFromDisk(relativePathx)) {
this.getLogger()
.at(Level.INFO)
.log("Skipping reloading %s from file monitor event because there is changes made via the asset editor", pathx);
return true;
} else {
AssetPath assetPath = new AssetPath(event.getAssetPack(), relativePathx);
long globalEditStamp = this.globalEditLock.writeLock();
try {
this.undoRedoManager.clearUndoRedoStack(assetPath);
} finally {
this.globalEditLock.unlockWrite(globalEditStamp);
}
List<EditorClient> clientsWithOpenAssetPath = this.getClientsWithOpenAssetPath(assetPath);
if (!clientsWithOpenAssetPath.isEmpty()) {
AssetEditorAssetUpdated updatePacket = new AssetEditorAssetUpdated(assetPath.toPacket(), dataSource.getAssetBytes(relativePathx));
for (EditorClient editorClient : clientsWithOpenAssetPath) {
editorClient.getPacketHandler().write(updatePacket);
}
}
return false;
}
}
);
if (!newFiles.isEmpty()) {
packet.additions = (AssetEditorFileEntry[])newFiles.toArray(AssetEditorFileEntry[]::new);
}
}
if (!newFiles.isEmpty()) {
packet.additions = (AssetEditorFileEntry[])newFiles.toArray(AssetEditorFileEntry[]::new);
}
if (packet.deletions != null || packet.additions != null) {
this.sendPacketToAllEditorUsers(packet);
}
}
}
public void handleInitializeEditor(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType());
assert playerRefComponent != null;
String username = playerRefComponent.getUsername();
this.getLogger().at(Level.INFO).log("%s is attempting to initialize asset editor", username);
long stamp = this.initLock.writeLock();
try {
if (this.initState == AssetEditorPlugin.InitState.NOT_INITIALIZED) {
this.initState = AssetEditorPlugin.InitState.INITIALIZING;
ForkJoinPool.commonPool().execute(() -> this.initializeAssetEditor(false));
this.getLogger().at(Level.INFO).log("%s starting asset editor initialization", username);
}
} finally {
this.initLock.unlockWrite(stamp);
}
}
public void handleInitializeClient(@Nonnull EditorClient editorClient) {
this.getLogger().at(Level.INFO).log("Initializing %s", editorClient.getUsername());
this.uuidToEditorClients.computeIfAbsent(editorClient.getUuid(), k -> ConcurrentHashMap.newKeySet()).add(editorClient);
this.clientOpenAssetPathMapping.put(editorClient, new AssetPath("", Path.of("")));
I18nModule.get().sendTranslations(editorClient.getPacketHandler(), editorClient.getLanguage());
long stamp = this.initLock.writeLock();
try {
switch (this.initState) {
case NOT_INITIALIZED:
this.initState = AssetEditorPlugin.InitState.INITIALIZING;
this.initQueue.add(editorClient);
ForkJoinPool.commonPool().execute(() -> this.initializeAssetEditor(false));
this.getLogger().at(Level.INFO).log("%s starting asset editor initialization", editorClient.getUsername());
return;
case INITIALIZING:
this.getLogger().at(Level.INFO).log("%s waiting for asset editor initialization to complete", editorClient.getUsername());
this.initQueue.add(editorClient);
return;
case INITIALIZED:
}
} finally {
this.initLock.unlockWrite(stamp);
}
this.initializeClient(editorClient);
}
private void initializeAssetEditor(boolean updateLoadedAssets) {
long start = System.nanoTime();
Map<String, Schema> schemas = AssetRegistryLoader.generateSchemas(new SchemaContext(), new BsonDocument());
schemas.remove("NPCRole.json");
schemas.remove("other.json");
AssetEditorSetupSchemas setupSchemasPacket = new AssetEditorSetupSchemas(new SchemaFile[schemas.size()]);
int i = 0;
for (Schema schema : schemas.values()) {
String bytes = Schema.CODEC.encode(schema, EmptyExtraInfo.EMPTY).asDocument().toJson();
setupSchemasPacket.schemas[i++] = new SchemaFile(bytes);
}
for (DataSource dataSource : this.assetPackDataSources.values()) {
AssetTree tempAssetTree = dataSource.loadAssetTree(this.assetTypeRegistry.getRegisteredAssetTypeHandlers().values());
long globalEditStamp = this.globalEditLock.writeLock();
try {
dataSource.getAssetTree().replaceAssetTree(tempAssetTree);
this.assetTypeRegistry.setupPacket();
if (updateLoadedAssets) {
dataSource.updateRuntimeAssets();
}
} finally {
this.globalEditLock.unlockWrite(globalEditStamp);
}
}
long globalEditStamp = this.globalEditLock.writeLock();
try {
this.schemas = schemas;
this.setupSchemasPacket = setupSchemasPacket;
this.assetTypeRegistry.setupPacket();
} finally {
this.globalEditLock.unlockWrite(globalEditStamp);
}
long var31 = this.initLock.writeLock();
try {
this.initState = AssetEditorPlugin.InitState.INITIALIZED;
this.getLogger().at(Level.INFO).log("Asset editor initialization complete! Took: %s", FormatUtil.nanosToString(System.nanoTime() - start));
for (EditorClient editorClient : this.clientOpenAssetPathMapping.keySet()) {
this.initializeClient(editorClient);
}
this.initQueue.clear();
} finally {
this.initLock.unlockWrite(var31);
}
}
private void initializeClient(@Nonnull EditorClient editorClient) {
DataSource defaultDataSource = this.assetPackDataSources.get("Hytale:Hytale");
boolean canDiscard = false;
boolean canEditAssets = editorClient.hasPermission("hytale.editor.asset");
boolean canEditAssetPacks = editorClient.hasPermission("hytale.editor.packs.edit");
boolean canCreateAssetPacks = editorClient.hasPermission("hytale.editor.packs.create");
boolean canDeleteAssetPacks = editorClient.hasPermission("hytale.editor.packs.delete");
editorClient.getPacketHandler().write(new AssetEditorCapabilities(false, canEditAssets, canCreateAssetPacks, canEditAssetPacks, canDeleteAssetPacks));
editorClient.getPacketHandler().write(this.setupSchemasPacket);
this.assetTypeRegistry.sendPacket(editorClient);
AssetEditorAssetPackSetup packSetupPacket = new AssetEditorAssetPackSetup();
packSetupPacket.packs = new Object2ObjectOpenHashMap();
for (Entry<String, DataSource> dataSourceEntry : this.assetPackDataSources.entrySet()) {
DataSource dataSource = dataSourceEntry.getValue();
PluginManifest manifest = dataSource.getManifest();
packSetupPacket.packs.put(dataSourceEntry.getKey(), toManifestPacket(manifest));
}
editorClient.getPacketHandler().write(packSetupPacket);
for (DataSource dataSource : this.assetPackDataSources.values()) {
dataSource.getAssetTree().sendPackets(editorClient);
}
this.getLogger().at(Level.INFO).log("Done Initializing %s", editorClient.getUsername());
}
public void handleEditorClientDisconnected(@Nonnull EditorClient editorClient, PacketHandler.DisconnectReason disconnectReason) {
IEventDispatcher<AssetEditorClientDisconnectEvent, AssetEditorClientDisconnectEvent> dispatch = HytaleServer.get()
.getEventBus()
.dispatchFor(AssetEditorClientDisconnectEvent.class);
if (dispatch.hasListener()) {
dispatch.dispatch(new AssetEditorClientDisconnectEvent(editorClient, disconnectReason));
}
this.uuidToEditorClients.compute(editorClient.getUuid(), (uuid, clients) -> {
if (clients == null) {
return null;
} else {
clients.remove(editorClient);
return clients.isEmpty() ? null : clients;
}
});
this.clientOpenAssetPathMapping.remove(editorClient);
this.clientsSubscribedToModifiedAssetsChanges.remove(editorClient);
}
public void handleDeleteAssetPack(@Nonnull EditorClient editorClient, @Nonnull String packId) {
if (packId.equalsIgnoreCase("Hytale:Hytale")) {
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else {
DataSource dataSource = this.getDataSourceForPack(packId);
if (dataSource == null) {
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else {
AssetModule.get().unregisterPack(packId);
Path targetPath;
try {
targetPath = dataSource.getRootPath().toRealPath();
} catch (IOException var11) {
throw new RuntimeException("Failed to resolve the real path for asset pack directory while deleting asset pack '" + packId + "'.", var11);
}
boolean isInModsDirectory = false;
try {
if (targetPath.startsWith(PluginManager.MODS_PATH.toRealPath())) {
isInModsDirectory = true;
}
} catch (IOException var10) {
}
if (!isInModsDirectory) {
for (Path modsPath : Options.getOptionSet().valuesOf(Options.MODS_DIRECTORIES)) {
try {
if (targetPath.startsWith(modsPath.toRealPath())) {
isInModsDirectory = true;
break;
}
} catch (IOException var12) {
}
}
}
if (!isInModsDirectory) {
editorClient.sendPopupNotification(
AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.packOutsideDirectory")
);
} else {
try {
FileUtil.deleteDirectory(targetPath);
} catch (Exception var9) {
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(var9)).log("Failed to delete asset pack %s from disk", packId);
}
}
}
}
}
public void handleUpdateAssetPack(@Nonnull EditorClient editorClient, @Nonnull String packId, @Nonnull AssetPackManifest packetManifest) {
if (packId.equals("Hytale:Hytale")) {
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else {
DataSource dataSource = this.getDataSourceForPack(packId);
if (dataSource == null) {
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (dataSource.isImmutable()) {
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else {
PluginManifest manifest = dataSource.getManifest();
if (manifest == null) {
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.manifestNotFound"));
} else {
boolean didIdentifierChange = false;
if (packetManifest.name != null && !packetManifest.name.isEmpty() && !manifest.getName().equals(packetManifest.name)) {
manifest.setName(packetManifest.name);
didIdentifierChange = true;
}
if (packetManifest.group != null && !packetManifest.group.isEmpty() && !manifest.getGroup().equals(packetManifest.group)) {
manifest.setGroup(packetManifest.group);
didIdentifierChange = true;
}
if (packetManifest.description != null) {
manifest.setDescription(packetManifest.description);
}
if (packetManifest.website != null) {
manifest.setWebsite(packetManifest.website);
}
if (packetManifest.version != null && !packetManifest.version.isEmpty()) {
try {
manifest.setVersion(Semver.fromString(packetManifest.version));
} catch (IllegalArgumentException var14) {
((HytaleLogger.Api)this.getLogger().at(Level.WARNING).withCause(var14)).log("Invalid version format: %s", packetManifest.version);
editorClient.sendPopupNotification(
AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.invalidVersionFormat")
);
return;
}
}
if (packetManifest.authors != null) {
List<AuthorInfo> authors = new ObjectArrayList();
for (com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo packetAuthor : packetManifest.authors) {
AuthorInfo author = new AuthorInfo();
author.setName(packetAuthor.name);
author.setEmail(packetAuthor.email);
author.setUrl(packetAuthor.url);
authors.add(author);
}
manifest.setAuthors(authors);
}
Path manifestPath = dataSource.getRootPath().resolve("manifest.json");
try {
BsonUtil.writeSync(manifestPath, PluginManifest.CODEC, manifest, this.getLogger());
this.getLogger().at(Level.INFO).log("Saved manifest for pack %s", packId);
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Success, Message.translation("server.assetEditor.messages.manifestSaved"));
} catch (IOException var13) {
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(var13)).log("Failed to save manifest for pack %s", packId);
editorClient.sendPopupNotification(
AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.manifestSaveFailed")
);
}
this.broadcastPackAddedOrUpdated(packId, manifest);
if (didIdentifierChange) {
String newPackId = new PluginIdentifier(manifest).toString();
Path packPath = dataSource.getRootPath();
AssetModule assetModule = AssetModule.get();
assetModule.unregisterPack(packId);
assetModule.registerPack(newPackId, packPath, manifest);
}
}
}
}
}
public void handleCreateAssetPack(@Nonnull EditorClient editorClient, @Nonnull AssetPackManifest packetManifest, int requestToken) {
if (packetManifest.name == null || packetManifest.name.isEmpty()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packNameRequired"));
} else if (packetManifest.group != null && !packetManifest.group.isEmpty()) {
PluginManifest manifest = new PluginManifest();
manifest.setName(packetManifest.name);
manifest.setGroup(packetManifest.group);
if (packetManifest.description != null) {
manifest.setDescription(packetManifest.description);
}
if (packetManifest.website != null) {
manifest.setWebsite(packetManifest.website);
}
if (packetManifest.version != null && !packetManifest.version.isEmpty()) {
try {
manifest.setVersion(Semver.fromString(packetManifest.version));
} catch (IllegalArgumentException var12) {
((HytaleLogger.Api)this.getLogger().at(Level.WARNING).withCause(var12)).log("Invalid version format: %s", packetManifest.version);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.invalidVersionFormat"));
return;
}
}
if (packetManifest.authors != null) {
List<AuthorInfo> authors = new ObjectArrayList();
for (com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo packetAuthor : packetManifest.authors) {
AuthorInfo author = new AuthorInfo();
author.setName(packetAuthor.name);
author.setEmail(packetAuthor.email);
author.setUrl(packetAuthor.url);
authors.add(author);
}
manifest.setAuthors(authors);
}
String packId = new PluginIdentifier(manifest).toString();
if (this.assetPackDataSources.containsKey(packId)) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packAlreadyExists"));
} else {
Path modsPath = PluginManager.MODS_PATH;
String dirName = AssetPathUtil.removeInvalidFileNameChars(
packetManifest.group != null ? packetManifest.group + "." + packetManifest.name : packetManifest.name
);
Path normalized = Path.of(dirName).normalize();
if (AssetPathUtil.isInvalidFileName(normalized)) {
editorClient.sendFailureReply(requestToken, Messages.INVALID_FILENAME_MESSAGE);
} else {
Path packPath = modsPath.resolve(normalized).normalize();
if (!packPath.startsWith(modsPath)) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packOutsideDirectory"));
} else if (Files.exists(packPath)) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packAlreadyExistsAtPath"));
} else {
try {
Files.createDirectories(packPath);
Path manifestPath = packPath.resolve("manifest.json");
BsonUtil.writeSync(manifestPath, PluginManifest.CODEC, manifest, this.getLogger());
AssetModule.get().registerPack(packId, packPath, manifest);
editorClient.sendSuccessReply(requestToken, Message.translation("server.assetEditor.messages.packCreated"));
this.getLogger().at(Level.INFO).log("Created new pack: %s at %s", packId, packPath);
} catch (IOException var11) {
((HytaleLogger.Api)this.getLogger().at(Level.SEVERE).withCause(var11)).log("Failed to create pack %s", packId);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packCreationFailed"));
}
}
}
}
} else {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.packGroupRequired"));
}
}
private static AssetPackManifest toManifestPacket(@Nonnull PluginManifest manifest) {
AssetPackManifest packet = new AssetPackManifest();
packet.name = manifest.getName();
packet.description = manifest.getDescription() != null ? manifest.getDescription() : "";
packet.group = manifest.getGroup();
packet.version = manifest.getVersion() != null ? manifest.getVersion().toString() : "";
packet.website = manifest.getWebsite() != null ? manifest.getWebsite() : "";
List<com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo> authors = new ObjectArrayList();
for (AuthorInfo a : manifest.getAuthors()) {
com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo authorInfo = new com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo(
a.getName(), a.getEmail(), a.getUrl()
);
authors.add(authorInfo);
}
packet.authors = authors.toArray(new com.hypixel.hytale.protocol.packets.asseteditor.AuthorInfo[0]);
return packet;
}
private void broadcastPackAddedOrUpdated(String packId, PluginManifest manifest) {
AssetPackManifest manifestPacket = toManifestPacket(manifest);
for (Set<EditorClient> clients : this.uuidToEditorClients.values()) {
for (EditorClient client : clients) {
client.getPacketHandler().write(new AssetEditorUpdateAssetPack(packId, manifestPacket));
}
}
}
public void handleExportAssets(@Nonnull EditorClient editorClient, @Nonnull List<AssetPath> paths) {
ObjectArrayList<TimestampedAssetReference> exportedAssets = new ObjectArrayList();
for (AssetPath assetPath : paths) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
this.getLogger().at(Level.WARNING).log("%s has no valid data source", assetPath);
AssetEditorAsset asset = new AssetEditorAsset(null, assetPath.toPacket());
editorClient.getPacketHandler().write(new AssetEditorExportAssetInitialize(asset, null, 0, true));
} else if (!this.isValidPath(dataSource, assetPath)) {
this.getLogger().at(Level.WARNING).log("%s is an invalid path", assetPath);
AssetEditorAsset asset = new AssetEditorAsset(null, assetPath.toPacket());
editorClient.getPacketHandler().write(new AssetEditorExportAssetInitialize(asset, null, 0, true));
} else if (this.assetTypeRegistry.getAssetTypeHandlerForPath(assetPath.path()) == null) {
this.getLogger().at(Level.WARNING).log("%s is not a valid asset type", assetPath);
AssetEditorAsset asset = new AssetEditorAsset(null, assetPath.toPacket());
editorClient.getPacketHandler().write(new AssetEditorExportAssetInitialize(asset, null, 0, true));
} else if (!dataSource.doesAssetExist(assetPath.path())) {
editorClient.getPacketHandler().write(new AssetEditorExportDeleteAssets(new AssetEditorAsset[]{new AssetEditorAsset(null, assetPath.toPacket())}));
} else {
byte[] bytes = dataSource.getAssetBytes(assetPath.path());
if (bytes == null) {
this.getLogger().at(Level.WARNING).log("Tried to load %s for export but failed", assetPath);
editorClient.getPacketHandler().write(new AssetEditorExportAssetInitialize(new AssetEditorAsset(null, assetPath.toPacket()), null, 0, false));
} else {
byte[][] parts = ArrayUtil.split(bytes, 2621440);
Packet[] packets = new Packet[2 + parts.length];
packets[0] = new AssetEditorExportAssetInitialize(new AssetEditorAsset(null, assetPath.toPacket()), null, bytes.length, false);
for (int partIndex = 0; partIndex < parts.length; partIndex++) {
packets[1 + partIndex] = new AssetEditorExportAssetPart(parts[partIndex]);
}
packets[packets.length - 1] = new AssetEditorExportAssetFinalize();
editorClient.getPacketHandler().write(packets);
Instant timestamp = dataSource.getLastModificationTimestamp(assetPath.path());
exportedAssets.add(new TimestampedAssetReference(assetPath.toPacket(), timestamp != null ? timestamp.toString() : null));
}
}
}
editorClient.getPacketHandler()
.write(new AssetEditorExportComplete((TimestampedAssetReference[])exportedAssets.toArray(TimestampedAssetReference[]::new)));
}
public void handleSelectAsset(@Nonnull EditorClient editorClient, @Nullable AssetPath assetPath) {
if (assetPath != null) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
return;
}
}
String assetType = null;
String currentAssetType = null;
AssetPath currentPath = this.clientOpenAssetPathMapping.get(editorClient);
if (currentPath != null && !currentPath.equals(AssetPath.EMPTY_PATH)) {
AssetTypeHandler currentAssetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(currentPath.path(), editorClient, -1);
if (currentAssetTypeHandler != null) {
currentAssetType = currentAssetTypeHandler.getConfig().id;
}
}
if (assetPath != null) {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, -1);
if (assetTypeHandler == null) {
return;
}
assetType = assetTypeHandler.getConfig().id;
this.clientOpenAssetPathMapping.put(editorClient, assetPath);
} else {
this.clientOpenAssetPathMapping.put(editorClient, AssetPath.EMPTY_PATH);
}
IEventDispatcher<AssetEditorSelectAssetEvent, AssetEditorSelectAssetEvent> dispatch = HytaleServer.get()
.getEventBus()
.dispatchFor(AssetEditorSelectAssetEvent.class);
if (dispatch.hasListener()) {
dispatch.dispatch(new AssetEditorSelectAssetEvent(editorClient, assetType, assetPath, currentAssetType, currentPath));
}
}
public void handleFetchLastModifiedAssets(@Nonnull EditorClient editorClient) {
long stamp = this.globalEditLock.readLock();
try {
AssetEditorLastModifiedAssets packet = this.buildAssetEditorLastModifiedAssetsPacket();
editorClient.getPacketHandler().write(packet);
} finally {
this.globalEditLock.unlockRead(stamp);
}
}
public void handleAssetUpdate(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, @Nonnull byte[] data, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken);
if (assetTypeHandler != null) {
long stamp = this.globalEditLock.writeLock();
label79: {
try {
if (!dataSource.doesAssetExist(assetPath.path())) {
this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.doesntExist"));
return;
}
if (!assetTypeHandler.isValidData(data)) {
this.getLogger().at(Level.WARNING).log("Failed to validate data for %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createAsset.failed"));
return;
}
if (dataSource.updateAsset(assetPath.path(), data, editorClient)) {
this.updateAssetForConnectedClients(assetPath, data, editorClient);
this.sendModifiedAssetsUpdateToConnectedUsers();
editorClient.sendSuccessReply(requestToken);
assetTypeHandler.loadAsset(assetPath, dataSource.getFullPathToAssetData(assetPath.path()), data, editorClient);
break label79;
}
this.getLogger().at(Level.WARNING).log("Failed to update asset %s in data source!", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.failed"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
return;
}
this.getLogger().at(Level.INFO).log("Updated asset at %s", assetPath);
}
}
}
public void handleJsonAssetUpdate(
@Nonnull EditorClient editorClient,
AssetPath assetPath,
@Nonnull String assetType,
int assetIndex,
@Nonnull JsonUpdateCommand[] commands,
int requestToken
) {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.getAssetTypeHandler(assetType);
if (!(assetTypeHandler instanceof JsonTypeHandler)) {
this.getLogger().at(Level.WARNING).log("Invalid asset type %s", assetType);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.unknownAssetType").param("assetType", assetType));
} else {
DataSource dataSource;
if (assetIndex > -1 && assetTypeHandler instanceof AssetStoreTypeHandler) {
AssetStore assetStore = ((AssetStoreTypeHandler)assetTypeHandler).getAssetStore();
AssetMap assetMap = assetStore.getAssetMap();
String keyString = AssetStoreUtil.getIdFromIndex(assetStore, assetIndex);
Object key = assetStore.decodeStringKey(keyString);
Path storedPath = assetMap.getPath(key);
String storedAssetPack = assetMap.getAssetPack(key);
if (storedPath == null || storedAssetPack == null) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.unknownAssetIndex"));
return;
}
dataSource = this.getDataSourceForPack(storedAssetPack);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
return;
}
assetPath = new AssetPath(storedAssetPack, PathUtil.relativizePretty(dataSource.getRootPath(), storedPath));
} else {
dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
return;
}
}
if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else if (!assetPath.path().startsWith(assetTypeHandler.getRootPath())) {
this.getLogger().at(Level.WARNING).log("%s is not within valid asset directory", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.directoryOutsideRoot"));
} else {
String fileExtension = PathUtil.getFileExtension(assetPath.path());
if (!fileExtension.equalsIgnoreCase(assetTypeHandler.getConfig().fileExtension)) {
this.getLogger()
.at(Level.WARNING)
.log("File extension not matching. Expected %s, got %s", assetTypeHandler.getConfig().fileExtension, fileExtension);
this.getLogger()
.at(Level.WARNING)
.log("File extension not matching. Expected %s, got %s", assetTypeHandler.getConfig().fileExtension, fileExtension);
editorClient.sendFailureReply(
requestToken,
Message.translation("server.assetEditor.messages.fileExtensionMismatch").param("fileExtension", assetTypeHandler.getConfig().fileExtension)
);
} else {
long stamp = this.globalEditLock.writeLock();
try {
byte[] bytes = dataSource.getAssetBytes(assetPath.path());
if (bytes == null) {
this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.doesntExist"));
return;
}
AssetUpdateQuery.RebuildCacheBuilder rebuildCacheBuilder = AssetUpdateQuery.RebuildCache.builder();
BsonDocument asset;
try {
asset = this.applyCommandsToAsset(bytes, assetPath, commands, rebuildCacheBuilder);
String json = BsonUtil.toJson(asset) + "\n";
bytes = json.getBytes(StandardCharsets.UTF_8);
} catch (Exception var23) {
((HytaleLogger.Api)this.getLogger().at(Level.WARNING).withCause(var23)).log("Failed to apply commands to %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.failed"));
return;
}
if (dataSource.updateAsset(assetPath.path(), bytes, editorClient)) {
AssetUndoRedoInfo undoRedo = this.undoRedoManager.getOrCreateUndoRedoStack(assetPath);
undoRedo.redoStack.clear();
for (JsonUpdateCommand command : commands) {
undoRedo.undoStack.push(command);
}
this.updateJsonAssetForConnectedClients(assetPath, commands, editorClient);
editorClient.sendSuccessReply(requestToken);
this.sendModifiedAssetsUpdateToConnectedUsers();
((JsonTypeHandler)assetTypeHandler)
.loadAssetFromDocument(
assetPath,
dataSource.getFullPathToAssetData(assetPath.path()),
asset.clone(),
new AssetUpdateQuery(rebuildCacheBuilder.build()),
editorClient
);
return;
}
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.failed"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
}
}
}
}
public void handleUndo(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken);
if (assetTypeHandler != null) {
if (!(assetTypeHandler instanceof JsonTypeHandler)) {
this.getLogger().at(Level.WARNING).log("Undo can only be applied to an instance of JsonTypeHandler");
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.invalidAssetType"));
} else {
long stamp = this.globalEditLock.writeLock();
try {
AssetUndoRedoInfo undoRedo = this.undoRedoManager.getUndoRedoStack(assetPath);
if (undoRedo == null || undoRedo.undoStack.isEmpty()) {
this.getLogger().at(Level.INFO).log("Nothing to undo");
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.undo.empty"));
return;
}
JsonUpdateCommand command = undoRedo.undoStack.peek();
JsonUpdateCommand undoCommand = new JsonUpdateCommand();
undoCommand.rebuildCaches = command.rebuildCaches;
if (command.firstCreatedProperty != null) {
undoCommand.type = JsonUpdateType.RemoveProperty;
undoCommand.path = command.firstCreatedProperty;
} else {
undoCommand.type = command.type == JsonUpdateType.RemoveProperty ? JsonUpdateType.InsertProperty : JsonUpdateType.SetProperty;
undoCommand.path = command.path;
undoCommand.value = command.previousValue;
}
byte[] bytes = dataSource.getAssetBytes(assetPath.path());
if (bytes == null) {
this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.doesntExist"));
return;
}
AssetUpdateQuery.RebuildCacheBuilder rebuildCacheBuilder = AssetUpdateQuery.RebuildCache.builder();
BsonDocument asset;
try {
asset = this.applyCommandsToAsset(bytes, assetPath, new JsonUpdateCommand[]{undoCommand}, rebuildCacheBuilder);
String json = BsonUtil.toJson(asset) + "\n";
bytes = json.getBytes(StandardCharsets.UTF_8);
} catch (Exception var18) {
((HytaleLogger.Api)this.getLogger().at(Level.WARNING).withCause(var18)).log("Failed to undo for %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.undo.failed"));
return;
}
if (dataSource.updateAsset(assetPath.path(), bytes, editorClient)) {
undoRedo.undoStack.poll();
undoRedo.redoStack.push(command);
this.updateJsonAssetForConnectedClients(assetPath, new JsonUpdateCommand[]{undoCommand}, editorClient);
editorClient.getPacketHandler().write(new AssetEditorUndoRedoReply(requestToken, undoCommand));
this.sendModifiedAssetsUpdateToConnectedUsers();
((JsonTypeHandler)assetTypeHandler)
.loadAssetFromDocument(
assetPath,
dataSource.getFullPathToAssetData(assetPath.path()),
asset.clone(),
new AssetUpdateQuery(rebuildCacheBuilder.build()),
editorClient
);
return;
}
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.undo.failed"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
}
}
}
}
public void handleRedo(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken);
if (assetTypeHandler != null) {
if (!(assetTypeHandler instanceof JsonTypeHandler)) {
this.getLogger().at(Level.WARNING).log("Redo can only be applied to an instance of JsonTypeHandler");
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.invalidAssetType"));
} else {
long stamp = this.globalEditLock.writeLock();
try {
AssetUndoRedoInfo undoRedo = this.undoRedoManager.getUndoRedoStack(assetPath);
if (undoRedo == null || undoRedo.redoStack.isEmpty()) {
this.getLogger().at(Level.WARNING).log("Nothing to redo");
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.redo.empty"));
return;
}
byte[] bytes = dataSource.getAssetBytes(assetPath.path());
if (bytes == null) {
this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.update.doesntExist"));
return;
}
JsonUpdateCommand command = undoRedo.redoStack.peek();
AssetUpdateQuery.RebuildCacheBuilder rebuildCacheBuilder = AssetUpdateQuery.RebuildCache.builder();
BsonDocument asset;
try {
asset = this.applyCommandsToAsset(bytes, assetPath, new JsonUpdateCommand[]{command}, rebuildCacheBuilder);
String json = BsonUtil.toJson(asset) + "\n";
bytes = json.getBytes(StandardCharsets.UTF_8);
} catch (Exception var17) {
((HytaleLogger.Api)this.getLogger().at(Level.WARNING).withCause(var17)).log("Failed to redo for %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.redo.failed"));
return;
}
if (dataSource.updateAsset(assetPath.path(), bytes, editorClient)) {
undoRedo.redoStack.poll();
undoRedo.undoStack.push(command);
this.updateJsonAssetForConnectedClients(assetPath, new JsonUpdateCommand[]{command}, editorClient);
editorClient.getPacketHandler().write(new AssetEditorUndoRedoReply(requestToken, command));
this.sendModifiedAssetsUpdateToConnectedUsers();
((JsonTypeHandler)assetTypeHandler)
.loadAssetFromDocument(
assetPath,
dataSource.getFullPathToAssetData(assetPath.path()),
asset.clone(),
new AssetUpdateQuery(rebuildCacheBuilder.build()),
editorClient
);
return;
}
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.redo.failed"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
}
}
}
}
public void handleFetchAsset(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else if (this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken) != null) {
long stamp = this.globalEditLock.readLock();
try {
if (!dataSource.doesAssetExist(assetPath.path())) {
this.getLogger().at(Level.WARNING).log("%s is not a regular file", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.fetchAsset.doesntExist"));
return;
}
byte[] asset = dataSource.getAssetBytes(assetPath.path());
if (asset != null) {
this.getLogger().at(Level.INFO).log("Got '%s'", assetPath);
editorClient.getPacketHandler().write(new AssetEditorFetchAssetReply(requestToken, asset));
return;
}
this.getLogger().at(Level.INFO).log("Failed to get '%s'", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.fetchAsset.failed"));
} finally {
this.globalEditLock.unlockRead(stamp);
}
}
}
public void handleFetchJsonAssetWithParents(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, boolean isFromOpenedTab, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else if (this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken) != null) {
long stamp = this.globalEditLock.readLock();
try {
byte[] asset = dataSource.getAssetBytes(assetPath.path());
if (asset != null) {
this.getLogger().at(Level.INFO).log("Got '%s'", assetPath);
BsonDocument bson = BsonDocument.parse(new String(asset, StandardCharsets.UTF_8));
Object2ObjectOpenHashMap<com.hypixel.hytale.protocol.packets.asseteditor.AssetPath, String> assets = new Object2ObjectOpenHashMap();
assets.put(assetPath.toPacket(), BsonUtil.translateBsonToJson(bson).getAsJsonObject().toString());
editorClient.getPacketHandler().write(new AssetEditorFetchJsonAssetWithParentsReply(requestToken, assets));
return;
}
this.getLogger().at(Level.INFO).log("Failed to get '%s'", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.fetchAsset.failed"));
} finally {
this.globalEditLock.unlockRead(stamp);
}
}
}
public void handleRequestChildIds(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendPopupNotification(AssetEditorPopupNotificationType.Error, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.getAssetTypeHandlerForPath(assetPath.path());
if (!(assetTypeHandler instanceof AssetStoreTypeHandler)) {
this.getLogger().at(Level.WARNING).log("Invalid asset type for %s", assetPath);
editorClient.sendPopupNotification(
AssetEditorPopupNotificationType.Error, Message.translation("server.assetEditor.messages.requestChildIds.assetTypeMissing")
);
} else {
AssetStore assetStore = ((AssetStoreTypeHandler)assetTypeHandler).getAssetStore();
Object key = assetStore.decodeFilePathKey(assetPath.path());
Set children = assetStore.getAssetMap().getChildren(key);
HashSet<String> childrenIds = new HashSet<>();
if (children != null) {
for (Object child : children) {
if (assetStore.getAssetMap().getPath(child) != null) {
childrenIds.add(child.toString());
}
}
}
this.getLogger().at(Level.INFO).log("Children ids for '%s': %s", key.toString(), childrenIds);
editorClient.getPacketHandler().write(new AssetEditorRequestChildrenListReply(assetPath.toPacket(), childrenIds.toArray(String[]::new)));
}
}
}
public void handleDeleteAsset(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken);
if (assetTypeHandler != null) {
long stamp = this.globalEditLock.writeLock();
label68: {
try {
if (!dataSource.doesAssetExist(assetPath.path())) {
this.getLogger().at(Level.WARNING).log("%s does not exist", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.deleteAsset.alreadyDeleted"));
return;
}
if (dataSource.deleteAsset(assetPath.path(), editorClient)) {
this.undoRedoManager.clearUndoRedoStack(assetPath);
AssetEditorFileEntry entry = dataSource.getAssetTree().removeAsset(assetPath.path());
AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate(assetPath.packId(), null, new AssetEditorFileEntry[]{entry});
editorClient.sendSuccessReply(requestToken);
this.sendPacketToAllEditorUsersExcept(packet, editorClient);
this.sendModifiedAssetsUpdateToConnectedUsers();
assetTypeHandler.unloadAsset(assetPath);
break label68;
}
this.getLogger().at(Level.WARNING).log("Failed to delete %s from data source", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.failedToDeleteAsset"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
return;
}
this.getLogger().at(Level.INFO).log("Deleted asset %s", assetPath);
}
}
}
public void handleSubscribeToModifiedAssetsChanges(EditorClient editorClient) {
this.clientsSubscribedToModifiedAssetsChanges.add(editorClient);
}
public void handleUnsubscribeFromModifiedAssetsChanges(EditorClient editorClient) {
this.clientsSubscribedToModifiedAssetsChanges.remove(editorClient);
}
public void handleRenameAsset(@Nonnull EditorClient editorClient, @Nonnull AssetPath oldAssetPath, @Nonnull AssetPath newAssetPath, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(oldAssetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, oldAssetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else if (!this.isValidPath(dataSource, newAssetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(oldAssetPath.path(), editorClient, requestToken);
if (assetTypeHandler != null) {
String fileExtensionNew = PathUtil.getFileExtension(newAssetPath.path());
if (!fileExtensionNew.equalsIgnoreCase(assetTypeHandler.getConfig().fileExtension)) {
this.getLogger()
.at(Level.WARNING)
.log("File extension not matching. Expected %s, got %s", assetTypeHandler.getConfig().fileExtension, fileExtensionNew);
editorClient.sendFailureReply(
requestToken,
Message.translation("server.assetEditor.messages.fileExtensionMismatch").param("fileExtension", assetTypeHandler.getConfig().fileExtension)
);
} else if (!newAssetPath.path().startsWith(assetTypeHandler.getRootPath())) {
this.getLogger().at(Level.WARNING).log("%s is not within valid asset directory", newAssetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.directoryOutsideRoot"));
} else {
long stamp = this.globalEditLock.writeLock();
try {
if (dataSource.doesAssetExist(newAssetPath.path())) {
this.getLogger().at(Level.WARNING).log("%s already exists", newAssetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.renameAsset.alreadyExists"));
return;
}
byte[] oldAsset = dataSource.getAssetBytes(oldAssetPath.path());
if (oldAsset == null) {
this.getLogger().at(Level.WARNING).log("%s is not a regular file", oldAssetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.renameAsset.doesntExist"));
return;
}
if (dataSource.moveAsset(oldAssetPath.path(), newAssetPath.path(), editorClient)) {
AssetUndoRedoInfo undoRedo = this.undoRedoManager.clearUndoRedoStack(oldAssetPath);
if (undoRedo != null) {
this.undoRedoManager.putUndoRedoStack(newAssetPath, undoRedo);
}
this.getLogger().at(Level.WARNING).log("Moved %s to %s", oldAssetPath, newAssetPath);
AssetEditorFileEntry oldEntry = dataSource.getAssetTree().removeAsset(oldAssetPath.path());
AssetEditorFileEntry newEntry = dataSource.getAssetTree().ensureAsset(newAssetPath.path(), false);
AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate(
oldAssetPath.packId(), new AssetEditorFileEntry[]{newEntry}, new AssetEditorFileEntry[]{oldEntry}
);
this.sendPacketToAllEditorUsersExcept(packet, editorClient);
editorClient.sendSuccessReply(requestToken);
assetTypeHandler.unloadAsset(oldAssetPath);
assetTypeHandler.loadAsset(newAssetPath, dataSource.getFullPathToAssetData(newAssetPath.path()), oldAsset, editorClient);
return;
}
this.getLogger().at(Level.WARNING).log("Failed to move file %s to %s", oldAssetPath, newAssetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.renameAsset.failed"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
}
}
}
}
public void handleDeleteDirectory(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.directoryOutsideRoot"));
} else if (!this.getAssetTypeRegistry().isPathInAssetTypeFolder(assetPath.path())) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else {
long stamp = this.globalEditLock.writeLock();
try {
if (!dataSource.doesDirectoryExist(assetPath.path())) {
this.getLogger().at(Level.WARNING).log("Directory doesn't exist %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createDirectory.alreadyExists"));
return;
}
if (!dataSource.getAssetTree().isDirectoryEmpty(assetPath.path())) {
this.getLogger().at(Level.WARNING).log("%s must be empty", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.deleteDirectory.notEmpty"));
return;
}
if (dataSource.deleteDirectory(assetPath.path())) {
AssetEditorFileEntry entry = dataSource.getAssetTree().removeAsset(assetPath.path());
AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate(assetPath.packId(), null, new AssetEditorFileEntry[]{entry});
this.sendPacketToAllEditorUsersExcept(packet, editorClient);
editorClient.sendSuccessReply(requestToken);
this.getLogger().at(Level.INFO).log("Deleted directory %s", assetPath);
return;
}
this.getLogger().at(Level.WARNING).log("Directory %s could not be deleted!", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.deleteDirectory.failed"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
}
}
public void handleRenameDirectory(@Nonnull EditorClient editorClient, AssetPath path, AssetPath newPath, int requestToken) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.renameDirectory.unsupported"));
}
public void handleCreateDirectory(@Nonnull EditorClient editorClient, @Nonnull AssetPath assetPath, int requestToken) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createDirectory.noDataSource"));
} else if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createDirectory.noPath"));
} else {
long stamp = this.globalEditLock.writeLock();
try {
if (dataSource.doesDirectoryExist(assetPath.path())) {
this.getLogger().at(Level.WARNING).log("Directory already exists at %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createDirectory.alreadyExists"));
return;
}
Path parentDirectoryPath = assetPath.path().getParent();
if (!dataSource.doesDirectoryExist(parentDirectoryPath)) {
this.getLogger().at(Level.WARNING).log("Parent directory is missing for %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.parentDirectoryMissing"));
return;
}
if (dataSource.createDirectory(assetPath.path(), editorClient)) {
AssetEditorFileEntry entry = dataSource.getAssetTree().ensureAsset(assetPath.path(), true);
if (entry != null) {
AssetEditorAssetListUpdate packet = new AssetEditorAssetListUpdate(assetPath.packId(), new AssetEditorFileEntry[]{entry}, null);
this.sendPacketToAllEditorUsersExcept(packet, editorClient);
}
editorClient.sendSuccessReply(requestToken);
this.getLogger().at(Level.WARNING).log("Created directory %s", assetPath);
return;
}
this.getLogger().at(Level.WARNING).log("Failed to create directory %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.failedToCreateDirectory"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
}
}
public void handleCreateAsset(
@Nonnull EditorClient editorClient,
@Nonnull AssetPath assetPath,
@Nonnull byte[] data,
@Nonnull AssetEditorRebuildCaches rebuildCaches,
String buttonId,
int requestToken
) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
if (dataSource == null) {
editorClient.sendFailureReply(requestToken, Messages.UNKNOWN_ASSETPACK_MESSAGE);
} else if (dataSource.isImmutable()) {
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.assetsReadOnly"));
} else if (!this.isValidPath(dataSource, assetPath)) {
editorClient.sendFailureReply(requestToken, Messages.OUTSIDE_ASSET_ROOT_MESSAGE);
} else {
AssetTypeHandler assetTypeHandler = this.assetTypeRegistry.tryGetAssetTypeHandler(assetPath.path(), editorClient, requestToken);
if (assetTypeHandler != null) {
long stamp = this.globalEditLock.writeLock();
try {
if (dataSource.doesAssetExist(assetPath.path())) {
this.getLogger().at(Level.WARNING).log("%s already exists", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createAsset.idAlreadyExists"));
return;
}
if (!assetTypeHandler.isValidData(data)) {
this.getLogger().at(Level.WARNING).log("Failed to validate data for %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createAsset.failed"));
return;
}
if (dataSource.createAsset(assetPath.path(), data, editorClient)) {
this.getLogger().at(Level.INFO).log("Created asset %s", assetPath);
AssetEditorFileEntry entry = dataSource.getAssetTree().ensureAsset(assetPath.path(), false);
if (entry != null) {
AssetEditorAssetListUpdate updatePacket = new AssetEditorAssetListUpdate(assetPath.packId(), new AssetEditorFileEntry[]{entry}, null);
this.sendPacketToAllEditorUsersExcept(updatePacket, editorClient);
}
this.sendModifiedAssetsUpdateToConnectedUsers();
AssetUpdateQuery.RebuildCache rebuildCache = new AssetUpdateQuery.RebuildCache(
rebuildCaches.blockTextures,
rebuildCaches.models,
rebuildCaches.modelTextures,
rebuildCaches.mapGeometry,
rebuildCaches.itemIcons,
assetPath.path().startsWith(AssetPathUtil.PATH_DIR_COMMON)
);
assetTypeHandler.loadAsset(
assetPath, dataSource.getFullPathToAssetData(assetPath.path()), data, new AssetUpdateQuery(rebuildCache), editorClient
);
IEventDispatcher<AssetEditorAssetCreatedEvent, AssetEditorAssetCreatedEvent> dispatch = HytaleServer.get()
.getEventBus()
.dispatchFor(AssetEditorAssetCreatedEvent.class, assetTypeHandler.getConfig().id);
if (dispatch.hasListener()) {
dispatch.dispatch(new AssetEditorAssetCreatedEvent(editorClient, assetTypeHandler.getConfig().id, assetPath.path(), data, buttonId));
}
editorClient.sendSuccessReply(requestToken);
return;
}
this.getLogger().at(Level.WARNING).log("Failed to create asset %s", assetPath);
editorClient.sendFailureReply(requestToken, Message.translation("server.assetEditor.messages.createAsset.failed"));
} finally {
this.globalEditLock.unlockWrite(stamp);
}
}
}
}
private BsonDocument applyCommandsToAsset(
@Nonnull byte[] bytes, AssetPath path, @Nonnull JsonUpdateCommand[] commands, @Nonnull AssetUpdateQuery.RebuildCacheBuilder rebuildCache
) {
BsonDocument asset = BsonDocument.parse(new String(bytes, StandardCharsets.UTF_8));
this.getLogger().at(Level.INFO).log("Applying commands to %s with %s", path, asset);
for (JsonUpdateCommand command : commands) {
switch (command.type) {
case SetProperty: {
BsonValue value = BsonDocument.parse(command.value).get("value");
this.getLogger().at(Level.INFO).log("Setting property %s to %s", String.join(".", command.path), value);
if (command.path.length > 0) {
BsonTransformationUtil.setProperty(asset, command.path, value);
} else {
asset = (BsonDocument)value;
}
break;
}
case InsertProperty: {
BsonValue value = BsonDocument.parse(command.value).get("value");
this.getLogger().at(Level.INFO).log("Inserting property %s with %s", String.join(".", command.path), value);
BsonTransformationUtil.insertProperty(asset, command.path, value);
break;
}
case RemoveProperty:
this.getLogger().at(Level.INFO).log("Removing property %s", String.join(".", command.path));
BsonTransformationUtil.removeProperty(asset, command.path);
}
}
this.getLogger().at(Level.INFO).log("Updated %s resulting: %s", path, asset);
for (JsonUpdateCommand command : commands) {
if (command.rebuildCaches != null) {
if (command.rebuildCaches.blockTextures) {
rebuildCache.setBlockTextures(true);
}
if (command.rebuildCaches.modelTextures) {
rebuildCache.setModelTextures(true);
}
if (command.rebuildCaches.models) {
rebuildCache.setModels(true);
}
if (command.rebuildCaches.mapGeometry) {
rebuildCache.setMapGeometry(true);
}
if (command.rebuildCaches.itemIcons) {
rebuildCache.setItemIcons(true);
}
}
}
return asset;
}
private void sendModifiedAssetsUpdateToConnectedUsers() {
if (!this.clientOpenAssetPathMapping.isEmpty()) {
if (!this.clientsSubscribedToModifiedAssetsChanges.isEmpty()) {
AssetEditorLastModifiedAssets lastModifiedAssetsPacket = this.buildAssetEditorLastModifiedAssetsPacket();
for (EditorClient p : this.clientsSubscribedToModifiedAssetsChanges) {
p.getPacketHandler().write(lastModifiedAssetsPacket);
}
}
}
}
private void sendPacketToAllEditorUsers(@Nonnull Packet packet) {
for (EditorClient editorClient : this.clientOpenAssetPathMapping.keySet()) {
editorClient.getPacketHandler().write(packet);
}
}
private void sendPacketToAllEditorUsersExcept(@Nonnull Packet packet, EditorClient ignoreEditorClient) {
for (EditorClient editorClient : this.clientOpenAssetPathMapping.keySet()) {
if (!editorClient.equals(ignoreEditorClient)) {
editorClient.getPacketHandler().write(packet);
}
}
}
private void updateAssetForConnectedClients(@Nonnull AssetPath assetPath) {
this.updateAssetForConnectedClients(assetPath, null);
}
private void updateAssetForConnectedClients(@Nonnull AssetPath assetPath, EditorClient ignoreEditorClient) {
DataSource dataSource = this.getDataSourceForPath(assetPath);
byte[] bytes = dataSource.getAssetBytes(assetPath.path());
this.updateAssetForConnectedClients(assetPath, bytes, ignoreEditorClient);
}
private void updateAssetForConnectedClients(@Nonnull AssetPath assetPath, byte[] bytes, EditorClient ignoreEditorClient) {
AssetEditorAssetUpdated updatePacket = new AssetEditorAssetUpdated(assetPath.toPacket(), bytes);
for (Entry<EditorClient, AssetPath> entry : this.clientOpenAssetPathMapping.entrySet()) {
if (!entry.getKey().equals(ignoreEditorClient) && assetPath.equals(entry.getValue())) {
entry.getKey().getPacketHandler().write(updatePacket);
}
}
}
private void updateJsonAssetForConnectedClients(@Nonnull AssetPath assetPath, JsonUpdateCommand[] commands) {
this.updateJsonAssetForConnectedClients(assetPath, commands, null);
}
private void updateJsonAssetForConnectedClients(@Nonnull AssetPath assetPath, JsonUpdateCommand[] commands, EditorClient ignoreEditorClient) {
AssetEditorJsonAssetUpdated updatePacket = new AssetEditorJsonAssetUpdated(assetPath.toPacket(), commands);
for (Entry<EditorClient, AssetPath> connectedPlayer : this.clientOpenAssetPathMapping.entrySet()) {
if (!connectedPlayer.getKey().equals(ignoreEditorClient) && assetPath.equals(connectedPlayer.getValue())) {
connectedPlayer.getKey().getPacketHandler().write(updatePacket);
}
}
}
@Nonnull
private AssetEditorLastModifiedAssets buildAssetEditorLastModifiedAssetsPacket() {
ArrayList<AssetInfo> allAssets = new ArrayList<>();
for (Entry<String, DataSource> dataSource : this.assetPackDataSources.entrySet()) {
if (dataSource.getValue() instanceof StandardDataSource standardDataSource) {
for (ModifiedAsset assetInfo : standardDataSource.getRecentlyModifiedAssets().values()) {
allAssets.add(assetInfo.toAssetInfoPacket(dataSource.getKey()));
}
}
}
return new AssetEditorLastModifiedAssets(allAssets.toArray(new AssetInfo[0]));
}
boolean isValidPath(@Nonnull DataSource dataSource, @Nonnull AssetPath assetPath) {
String assetPathString = PathUtil.toUnixPathString(assetPath.path());
Path rootPath = dataSource.getRootPath();
Path absolutePath = rootPath.resolve(assetPathString).toAbsolutePath().normalize();
if (!absolutePath.startsWith(rootPath)) {
return false;
} else {
String relativePath = PathUtil.toUnixPathString(rootPath.relativize(absolutePath));
return relativePath.equals(assetPathString);
}
}
static {
new SchemaContext();
}
public static class AssetToDiscard {
public final AssetPath path;
@Nullable
public final Instant lastModificationDate;
public AssetToDiscard(AssetPath path, @Nullable String lastModificationDate) {
this.path = path;
if (lastModificationDate != null) {
this.lastModificationDate = Instant.parse(lastModificationDate);
} else {
this.lastModificationDate = null;
}
}
}
static enum DiscardResult {
FAILED,
SUCCEEDED,
SUCCEEDED_COMMON_ASSETS_CHANGED;
}
static enum InitState {
NOT_INITIALIZED,
INITIALIZING,
INITIALIZED;
}
}