/*
* This file is part of Spoutcraft.
*
* Copyright (c) 2011 SpoutcraftDev <http://spoutcraft.org/>
* Spoutcraft is licensed under the GNU Lesser General Public License.
*
* Spoutcraft is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Spoutcraft is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.spoutcraft.client.precache;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.newdawn.slick.opengl.Texture;
import net.minecraft.src.Minecraft;
import net.minecraft.src.GuiDownloadTerrain;
import org.bukkit.ChatColor;
import org.spoutcraft.api.block.design.GenericBlockDesign;
import org.spoutcraft.api.io.SpoutInputStream;
import org.spoutcraft.api.material.CustomBlock;
import org.spoutcraft.api.material.MaterialData;
import org.spoutcraft.client.SpoutClient;
import org.spoutcraft.client.gui.precache.GuiPrecache;
import org.spoutcraft.client.io.CustomTextureManager;
import org.spoutcraft.client.io.FileUtil;
import org.spoutcraft.client.packet.PacketRequestPrecache;
public class PrecacheManager {
public static boolean spoutDebug = false;
public static int toDownload, downloaded = 0;
/**
* PrecacheTuple - Holds Plugin Name, Version, and CRC
* Boolean - Is this cached or not?
*/
public static HashMap<PrecacheTuple, Boolean> plugins = new HashMap<PrecacheTuple, Boolean>();
/**
* Adds a plugin tuple to the hashmap, and checks if it's cached and valid
* @param plugin
*/
public static void addPlugin(PrecacheTuple plugin) {
// Grab precache file
File target = getPluginPreCacheFile(plugin);
// Does it exist locally?
if (target.exists()) {
// Is the CRC the same as the one sent from SpoutPlugin?
if (plugin.getCrc() == FileUtil.getCRC(target, new byte[(int) target.length()])) {
// It's cached, continue on
plugins.put(plugin, true);
return;
}
}
// Either it doesn't exist or CRC failed, either or it isn't cached.
File temp = new File(FileUtil.getCacheDir(), plugin.getPlugin());
if (temp.exists() && temp.isDirectory()) {
FileUtil.deleteDirectory(temp);
}
plugins.put(plugin, false);
toDownload++;
}
/**
* Resets the plugins. Useful to clear out previous entries when starting a new login sequence.
*/
public static void reset() {
plugins.clear();
}
/**
* Returns the tuple that matches to parameters
* @param plugin
* @param version
*/
public static PrecacheTuple getPrecacheTuple(String plugin, String version) {
for (Entry entry : plugins.entrySet()) {
PrecacheTuple tuple = (PrecacheTuple)entry.getKey();
if (tuple.getPlugin().equalsIgnoreCase(plugin) && tuple.getVersion().equalsIgnoreCase(version)) {
return tuple;
}
}
return null;
}
/**
* Sets the given plugin precache tuple to a cached status.
* @param plugin
*/
public static void setCached(PrecacheTuple plugin) {
plugins.put(plugin, true);
}
/**
* Checks if there is a plugin precache still needing to be cached.
* @return true if a plugin needs to be cached, false is everything is already cached.
*/
public static boolean hasNextCache() {
for (boolean cached : plugins.values()) {
if (!cached) {
return true;
}
}
return false;
}
/**
* Gets the next item to be cached
* @return The PrecacheTuple to cache or null if not found
*/
public static PrecacheTuple getNextToCache() {
// Loop through the list of plugins
for (Entry entry : plugins.entrySet()) {
// Get the status of the entry (cached or not)
if ((Boolean) entry.getValue()) {
// It's cached, continue
continue;
}
// It wasn't cached so return this entry to be cached
return (PrecacheTuple) entry.getKey();
}
// Nothing is left to cache, return null
return null;
}
/**
* Starts the next cache
*/
public static void doNextCache() {
// Check if there is any thing left to be cached
if (!hasNextCache()) {
// Nothing is left to cache, proceed to load the cache
loadPrecache();
return;
}
final PrecacheTuple next = getNextToCache();
// Let the user know we are precaching
downloaded++;
setPreloadGuiText(ChatColor.WHITE + "Downloading resources, files remaining: " + ChatColor.ITALIC + (toDownload-downloaded));
if (toDownload-downloaded == 0) {
setPreloadGuiText(ChatColor.WHITE + "Activating downloaded resources...");
}
// Send SpoutPlugin a request for the pre-cache zip
SpoutClient.getInstance().getPacketManager().sendSpoutPacket(new PacketRequestPrecache(next.getPlugin()));
}
public static File getPluginPreCacheFile(PrecacheTuple plugin) {
return getPluginPreCacheFile(plugin.getPlugin(), plugin.getVersion());
}
public static File getPluginPreCacheFile(String plugin, String version) {
return new File(FileUtil.getCacheDir(), plugin + ".zip");
}
public static void loadPrecache() {
// Unzip
final File cacheRoot = FileUtil.getCacheDir();
for (Entry entry : plugins.entrySet()) {
// Grab the tuple
final PrecacheTuple toCache = (PrecacheTuple) entry.getKey();
final File extractDir = new File(cacheRoot, toCache.getPlugin()); //Ex. /cache/pluginname/
if (spoutDebug) {
System.out.println("[Spoutcraft] Reading: " + extractDir.getName() + ".zip");
}
// Make the directories to unzip to
extractDir.mkdirs();
try {
// Read in a zip stream
final ZipInputStream stream = new ZipInputStream(new FileInputStream(getPluginPreCacheFile(toCache)));
final ReadableByteChannel read = Channels.newChannel(stream);
// Grab the first entry in the zip
ZipEntry inner = stream.getNextEntry();
while (inner != null) {
// Construct an output stream for the entry
final File toExtract = new File(extractDir, inner.getName());
if (!toExtract.exists()) {
final FileOutputStream write = new FileOutputStream(toExtract);
write.getChannel().transferFrom(read, 0, Long.MAX_VALUE);
// Close the writable buffer
write.close();
}
// Close the zip stream for this entry
stream.closeEntry();
// Grab the next entry in the zip
inner = stream.getNextEntry();
}
// Finally close the stream altogether
stream.close();
} catch (Exception e ) {
e.printStackTrace();
}
}
for (Entry entry : plugins.entrySet()) {
final File dir = new File(cacheRoot, ((PrecacheTuple) entry.getKey()).getPlugin());
if (!dir.isDirectory()) {
continue;
}
final File[] files = dir.listFiles();
for (File file : files) {
if (file.getName().endsWith(".sbd")) {
if (spoutDebug) {
System.out.println("[Spoutcraft] Loading Spout Block Design: " + file.getName() + " from: " + file.getParent());
}
loadDesign(file);
} else if (FileUtil.isImageFile(file.getName())) {
if (spoutDebug) {
System.out.println("[Spoutcraft] Loading image: " + file.getName() + " from: " + file.getParent());
}
Texture tex = CustomTextureManager.getTextureFromUrl(((PrecacheTuple) entry.getKey()).getPlugin(),file.getName());
if (spoutDebug && tex == null) {
System.out.println("[Spoutcraft] Precache tried to load a null image: " + tex);
}
}
}
}
if (Minecraft.getMinecraft().theWorld != null) {
Minecraft.getMinecraft().renderGlobal.updateAllRenderers();
if (spoutDebug) {
System.out.println("[Spoutcraft] Updating renderer...");
}
}
closePreloadGui();
}
public static void showPreloadGui() {
// Display precache GUI
if (SpoutClient.getHandle().currentScreen instanceof GuiDownloadTerrain) {
SpoutClient.getHandle().displayGuiScreen(new GuiPrecache(), false);
//setPreloadGuiText("Checking plugin caches...");
}
}
public static void closePreloadGui() {
if (SpoutClient.getHandle().currentScreen instanceof GuiPrecache) {
// Closes downloading terrain
SpoutClient.getHandle().displayGuiScreen(null, false);
// Prevent closing a plugin created menu from opening the downloading terrain
SpoutClient.getHandle().clearPreviousScreen();
}
SpoutClient.getInstance().getPacketManager().sendSpoutPacket(new org.spoutcraft.client.packet.PacketPreCacheCompleted());
}
public static void setPreloadGuiText(String text) {
if (SpoutClient.getHandle().currentScreen instanceof GuiPrecache) {
((GuiPrecache)SpoutClient.getHandle().currentScreen).statusText.setText(text);
((GuiPrecache)SpoutClient.getHandle().currentScreen).statusText.onTick();
}
}
public static void loadDesign(File file) {
short customId = -1;
byte data = 0;
GenericBlockDesign design = null;
try {
final FileInputStream stream = new FileInputStream(file);
final FileChannel read = stream.getChannel();
final MappedByteBuffer buffer = read.map(FileChannel.MapMode.READ_ONLY, 0, read.size());
stream.close();
customId = buffer.getShort();
data = buffer.get();
design = new GenericBlockDesign();
design.read(new SpoutInputStream(buffer));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (design != null && design.isReset()) {
design = null;
}
if (customId != -1) {
CustomBlock block = MaterialData.getCustomBlock(customId);
if (block != null) {
block.setBlockDesign(design, data);
}
}
}
}
}