/* * Copyright 2014 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.persistence.internal; import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.internal.EngineEntityManager; import org.terasology.entitySystem.entity.internal.OwnershipHelper; import org.terasology.logic.location.LocationComponent; import org.terasology.math.AABB; import org.terasology.math.geom.Vector3i; import org.terasology.module.ModuleEnvironment; import org.terasology.network.ClientComponent; import org.terasology.persistence.ChunkStore; import org.terasology.persistence.PlayerStore; import org.terasology.persistence.StorageManager; import org.terasology.persistence.serializers.PrefabSerializer; import org.terasology.protobuf.EntityData; import org.terasology.world.biomes.BiomeManager; import org.terasology.world.block.BlockManager; import org.terasology.world.chunks.Chunk; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.zip.GZIPInputStream; /** * An abstract implementation of {@link StorageManager} that is able * to read from a data store. * */ public abstract class AbstractStorageManager implements StorageManager { private static final Logger logger = LoggerFactory.getLogger(AbstractStorageManager.class); private final StoragePathProvider storagePathProvider; private final BlockManager blockManager; private final BiomeManager biomeManager; private final ModuleEnvironment environment; private final EngineEntityManager entityManager; private final PrefabSerializer prefabSerializer; private final OwnershipHelper helper; private boolean storeChunksInZips = true; public AbstractStorageManager(Path savePath, ModuleEnvironment environment, EngineEntityManager entityManager, BlockManager blockManager, BiomeManager biomeManager, boolean storeChunksInZips) { this.entityManager = entityManager; this.environment = environment; this.storeChunksInZips = storeChunksInZips; this.prefabSerializer = new PrefabSerializer(entityManager.getComponentLibrary(), entityManager.getTypeSerializerLibrary()); this.blockManager = blockManager; this.biomeManager = biomeManager; this.storagePathProvider = new StoragePathProvider(savePath); this.helper = new OwnershipHelper(entityManager.getComponentLibrary()); } @Override public void loadGlobalStore() throws IOException { Path globalDataFile = storagePathProvider.getGlobalEntityStorePath(); if (Files.isRegularFile(globalDataFile)) { try (InputStream in = new BufferedInputStream(Files.newInputStream(globalDataFile))) { EntityData.GlobalStore store = EntityData.GlobalStore.parseFrom(in); GlobalStoreLoader loader = new GlobalStoreLoader(environment, entityManager, prefabSerializer); loader.load(store); } } } @Override public PlayerStore loadPlayerStore(String playerId) { EntityData.PlayerStore store = loadPlayerStoreData(playerId); if (store != null) { return new PlayerStoreInternal(playerId, store, entityManager); } return new PlayerStoreInternal(playerId, entityManager); } @Override public ChunkStore loadChunkStore(Vector3i chunkPos) { byte[] chunkData = loadCompressedChunk(chunkPos); ChunkStore store = null; if (chunkData != null) { ByteArrayInputStream bais = new ByteArrayInputStream(chunkData); try (GZIPInputStream gzipIn = new GZIPInputStream(bais)) { EntityData.ChunkStore storeData = EntityData.ChunkStore.parseFrom(gzipIn); store = new ChunkStoreInternal(storeData, entityManager, blockManager, biomeManager); } catch (IOException e) { logger.error("Failed to read existing saved chunk {}", chunkPos); } } return store; } protected byte[] loadChunkZip(Vector3i chunkPos) { byte[] chunkData = null; Vector3i chunkZipPos = storagePathProvider.getChunkZipPosition(chunkPos); Path chunkPath = storagePathProvider.getChunkZipPath(chunkZipPos); if (Files.isRegularFile(chunkPath)) { try (FileSystem chunkZip = FileSystems.newFileSystem(chunkPath, null)) { Path targetChunk = chunkZip.getPath(storagePathProvider.getChunkFilename(chunkPos)); if (Files.isRegularFile(targetChunk)) { chunkData = Files.readAllBytes(targetChunk); } } catch (IOException e) { logger.error("Failed to load chunk zip {}", chunkPath, e); } } return chunkData; } @Override public void update() { } public boolean isStoreChunksInZips() { return storeChunksInZips; } /** * For tests only */ void setStoreChunksInZips(boolean storeChunksInZips) { this.storeChunksInZips = storeChunksInZips; } protected byte[] loadCompressedChunk(Vector3i chunkPos) { if (isStoreChunksInZips()) { return loadChunkZip(chunkPos); } else { Path chunkPath = storagePathProvider.getChunkPath(chunkPos); if (Files.isRegularFile(chunkPath)) { try { return Files.readAllBytes(chunkPath); } catch (IOException e) { logger.error("Failed to load chunk {}", chunkPos, e); } } } return null; } protected EntityData.PlayerStore loadPlayerStoreData(String playerId) { Path storePath = storagePathProvider.getPlayerFilePath(playerId); if (Files.isRegularFile(storePath)) { try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(storePath))) { return EntityData.PlayerStore.parseFrom(inputStream); } catch (IOException e) { logger.error("Failed to load player data for {}", playerId, e); } } return null; } protected Collection<EntityRef> getEntitiesOfChunk(Chunk chunk) { List<EntityRef> entitiesToStore = Lists.newArrayList(); AABB aabb = chunk.getAABB(); for (EntityRef entity : getEntityManager().getEntitiesWith(LocationComponent.class)) { if (!entity.getOwner().exists() && !entity.isAlwaysRelevant() && !entity.hasComponent(ClientComponent.class)) { LocationComponent loc = entity.getComponent(LocationComponent.class); if (loc != null) { if (aabb.contains(loc.getWorldPosition())) { entitiesToStore.add(entity); } } } } return entitiesToStore; } protected void deactivateOrDestroyEntityRecursive(EntityRef entity) { if (entity.isActive()) { for (EntityRef ownedEntity : helper.listOwnedEntities(entity)) { if (!ownedEntity.isAlwaysRelevant()) { if (!ownedEntity.isPersistent()) { // TODO check if destroy is recursive ownedEntity.destroy(); } else { deactivateOrDestroyEntityRecursive(ownedEntity); } } } getEntityManager().deactivateForStorage(entity); } } protected StoragePathProvider getStoragePathProvider() { return storagePathProvider; } protected ModuleEnvironment getEnvironment() { return environment; } protected EngineEntityManager getEntityManager() { return entityManager; } protected PrefabSerializer getPrefabSerializer() { return prefabSerializer; } }