From 7df2ae4e116de6e28b9313e79c632432767cbe3b Mon Sep 17 00:00:00 2001 From: sk89q Date: Thu, 10 Mar 2011 00:21:45 -0800 Subject: [PATCH] WorldEdit should now support McRegion. --- .../data/TrueZipMcRegionChunkStore.java | 180 ++++++++++++++++++ ...Store.java => ZippedLegacyChunkStore.java} | 6 +- .../data/ZippedMcRegionChunkStore.java | 176 +++++++++++++++++ .../sk89q/worldedit/snapshots/Snapshot.java | 40 +++- 4 files changed, 396 insertions(+), 6 deletions(-) create mode 100644 src/com/sk89q/worldedit/data/TrueZipMcRegionChunkStore.java rename src/com/sk89q/worldedit/data/{ZippedAlphaChunkStore.java => ZippedLegacyChunkStore.java} (96%) create mode 100644 src/com/sk89q/worldedit/data/ZippedMcRegionChunkStore.java diff --git a/src/com/sk89q/worldedit/data/TrueZipMcRegionChunkStore.java b/src/com/sk89q/worldedit/data/TrueZipMcRegionChunkStore.java new file mode 100644 index 000000000..43d54d683 --- /dev/null +++ b/src/com/sk89q/worldedit/data/TrueZipMcRegionChunkStore.java @@ -0,0 +1,180 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.data; + +import java.io.*; +import java.util.regex.Pattern; +import java.util.zip.ZipException; +import java.util.Enumeration; +import de.schlichtherle.util.zip.*; + +/** + * Represents the chunk store used by Minecraft but zipped. Uses + * the replacement classes for java.util.zip.* from TrueZip. + * + * @author sk89q + */ +public class TrueZipMcRegionChunkStore extends McRegionChunkStore { + /** + * ZIP file. + */ + protected File zipFile; + /** + * Actual ZIP. + */ + protected ZipFile zip; + /** + * Folder inside the ZIP file to read from, if any. + */ + protected String folder; + + /** + * Create an instance. The folder argument lets you choose a folder or + * path to look into in the ZIP for the files. Use a blank string for + * the folder to not look into a subdirectory. + * + * @param zipFile + * @param folder + * @throws IOException + * @throws ZipException + */ + public TrueZipMcRegionChunkStore(File zipFile, String folder) + throws IOException, ZipException { + this.zipFile = zipFile; + this.folder = folder; + + zip = new ZipFile(zipFile); + } + + /** + * Create an instance. The subfolder containing the chunk data will + * be detected. + * + * @param zipFile + * @throws IOException + * @throws ZipException + */ + public TrueZipMcRegionChunkStore(File zipFile) + throws IOException, ZipException { + this.zipFile = zipFile; + + zip = new ZipFile(zipFile); + } + + /** + * Get the input stream for a chunk file. + * + * @param name + * @return + * @throws IOException + * @throws DataException + */ + @Override + @SuppressWarnings("unchecked") + protected InputStream getInputStream(String name) + throws IOException, DataException { + String file = "region/" + name; + + // Detect subfolder for the world's files + if (folder != null) { + if (!folder.equals("")) { + file = folder + "/" + file; + } + } else { + ZipEntry testEntry = zip.getEntry("level.dat"); + + // So, the data is not in the root directory + if (testEntry == null) { + // Let's try a world/ sub-directory + testEntry = getEntry("world/level.dat"); + + Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$"); + + // So not there either... + if (testEntry == null) { + for (Enumeration e = zip.entries(); + e.hasMoreElements(); ) { + + testEntry = e.nextElement(); + + // Whoo, found level.dat! + if (pattern.matcher(testEntry.getName()).matches()) { + file = testEntry.getName().replaceAll("level\\.dat$", "") + + file; + break; + } + } + } else { + file = "world/" + file; + } + } + } + + ZipEntry entry = getEntry(file); + if (entry == null) { + throw new MissingChunkException(); + } + try { + return zip.getInputStream(entry); + } catch (ZipException e) { + throw new IOException("Failed to read " + file + " in ZIP"); + } + } + + /** + * Get an entry from the ZIP, trying both types of slashes. + * + * @param file + * @return + */ + private ZipEntry getEntry(String file) { + ZipEntry entry = zip.getEntry(file); + if (entry != null) { + return entry; + } + return zip.getEntry(file.replace("/", "\\")); + } + + /** + * Close resources. + * + * @throws IOException + */ + @Override + public void close() throws IOException { + zip.close(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean isValid() { + for (Enumeration e = zip.entries(); + e.hasMoreElements(); ) { + + ZipEntry testEntry = e.nextElement(); + + if (testEntry.getName().matches(".*\\.mcr$")) { + return true; + } + } + + return false; + } +} diff --git a/src/com/sk89q/worldedit/data/ZippedAlphaChunkStore.java b/src/com/sk89q/worldedit/data/ZippedLegacyChunkStore.java similarity index 96% rename from src/com/sk89q/worldedit/data/ZippedAlphaChunkStore.java rename to src/com/sk89q/worldedit/data/ZippedLegacyChunkStore.java index 80c4d0540..81754c2da 100644 --- a/src/com/sk89q/worldedit/data/ZippedAlphaChunkStore.java +++ b/src/com/sk89q/worldedit/data/ZippedLegacyChunkStore.java @@ -29,7 +29,7 @@ import java.util.Enumeration; * * @author sk89q */ -public class ZippedAlphaChunkStore extends LegacyChunkStore { +public class ZippedLegacyChunkStore extends LegacyChunkStore { /** * ZIP file. */ @@ -54,7 +54,7 @@ public class ZippedAlphaChunkStore extends LegacyChunkStore { * @throws IOException * @throws ZipException */ - public ZippedAlphaChunkStore(File zipFile, String folder) + public ZippedLegacyChunkStore(File zipFile, String folder) throws IOException, ZipException { this.zipFile = zipFile; this.folder = folder; @@ -70,7 +70,7 @@ public class ZippedAlphaChunkStore extends LegacyChunkStore { * @throws IOException * @throws ZipException */ - public ZippedAlphaChunkStore(File zipFile) + public ZippedLegacyChunkStore(File zipFile) throws IOException, ZipException { this.zipFile = zipFile; diff --git a/src/com/sk89q/worldedit/data/ZippedMcRegionChunkStore.java b/src/com/sk89q/worldedit/data/ZippedMcRegionChunkStore.java new file mode 100644 index 000000000..045fd414e --- /dev/null +++ b/src/com/sk89q/worldedit/data/ZippedMcRegionChunkStore.java @@ -0,0 +1,176 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package com.sk89q.worldedit.data; + +import java.io.*; +import java.util.regex.Pattern; +import java.util.zip.*; +import java.util.Enumeration; + +/** + * Represents the chunk store used by Minecraft alpha but zipped. + * + * @author sk89q + */ +public class ZippedMcRegionChunkStore extends McRegionChunkStore { + /** + * ZIP file. + */ + protected File zipFile; + /** + * Actual ZIP. + */ + protected ZipFile zip; + /** + * Folder inside the ZIP file to read from, if any. + */ + protected String folder; + + /** + * Create an instance. The folder argument lets you choose a folder or + * path to look into in the ZIP for the files. Use a blank string for + * the folder to not look into a subdirectory. + * + * @param zipFile + * @param folder + * @throws IOException + * @throws ZipException + */ + public ZippedMcRegionChunkStore(File zipFile, String folder) + throws IOException, ZipException { + this.zipFile = zipFile; + this.folder = folder; + + zip = new ZipFile(zipFile); + } + + /** + * Create an instance. The subfolder containing the chunk data will + * be detected. + * + * @param zipFile + * @throws IOException + * @throws ZipException + */ + public ZippedMcRegionChunkStore(File zipFile) + throws IOException, ZipException { + this.zipFile = zipFile; + + zip = new ZipFile(zipFile); + } + + /** + * Get the input stream for a chunk file. + * + * @param name + * @return + * @throws IOException + * @throws DataException + */ + @Override + protected InputStream getInputStream(String name) + throws IOException, DataException { + String file = "region/" + name; + + // Detect subfolder for the world's files + if (folder != null) { + if (!folder.equals("")) { + file = folder + "/" + file; + } + } else { + ZipEntry testEntry = zip.getEntry("level.dat"); + + // So, the data is not in the root directory + if (testEntry == null) { + // Let's try a world/ sub-directory + testEntry = getEntry("world/level.dat"); + + Pattern pattern = Pattern.compile(".*[\\\\/]level\\.dat$"); + + // So not there either... + if (testEntry == null) { + for (Enumeration e = zip.entries(); + e.hasMoreElements(); ) { + + testEntry = (ZipEntry)e.nextElement(); + + // Whoo, found level.dat! + if (pattern.matcher(testEntry.getName()).matches()) { + file = testEntry.getName().replaceAll("level\\.dat$", "") + + file; + break; + } + } + } else { + file = "world/" + file; + } + } + } + + ZipEntry entry = getEntry(file); + if (entry == null) { + throw new MissingChunkException(); + } + try { + return zip.getInputStream(entry); + } catch (ZipException e) { + throw new IOException("Failed to read " + file + " in ZIP"); + } + } + + /** + * Get an entry from the ZIP, trying both types of slashes. + * + * @param file + * @return + */ + private ZipEntry getEntry(String file) { + ZipEntry entry = zip.getEntry(file); + if (entry != null) { + return entry; + } + return zip.getEntry(file.replace("/", "\\")); + } + + /** + * Close resources. + * + * @throws IOException + */ + @Override + public void close() throws IOException { + zip.close(); + } + + @Override + public boolean isValid() { + for (Enumeration e = zip.entries(); + e.hasMoreElements(); ) { + + ZipEntry testEntry = e.nextElement(); + + if (testEntry.getName().matches(".*\\.mcr$")) { + return true; + } + } + + return false; + } +} diff --git a/src/com/sk89q/worldedit/snapshots/Snapshot.java b/src/com/sk89q/worldedit/snapshots/Snapshot.java index 8ac9199e3..fd8dcf5b6 100644 --- a/src/com/sk89q/worldedit/snapshots/Snapshot.java +++ b/src/com/sk89q/worldedit/snapshots/Snapshot.java @@ -58,17 +58,51 @@ public class Snapshot { * @throws DataException */ public ChunkStore getChunkStore() throws IOException, DataException { + ChunkStore chunkStore = _getChunkStore(); + + logger.info("WorldEdit: Using " + chunkStore.getClass().getCanonicalName() + + " for loading snapshot '" + file.getAbsolutePath() + "'"); + + return chunkStore; + } + + /** + * Get a chunk store. + * + * @return + * @throws IOException + * @throws DataException + */ + public ChunkStore _getChunkStore() throws IOException, DataException { if (file.getName().toLowerCase().endsWith(".zip")) { try { - return new TrueZipLegacyChunkStore(file); + ChunkStore chunkStore = new TrueZipMcRegionChunkStore(file); + + if (!chunkStore.isValid()) { + return new TrueZipLegacyChunkStore(file); + } + + return chunkStore; } catch (NoClassDefFoundError e) { - return new ZippedAlphaChunkStore(file); + ChunkStore chunkStore = new ZippedMcRegionChunkStore(file); + + if (!chunkStore.isValid()) { + return new ZippedLegacyChunkStore(file); + } + + return chunkStore; } } else if (file.getName().toLowerCase().endsWith(".tar.bz2") || file.getName().toLowerCase().endsWith(".tar.gz") || file.getName().toLowerCase().endsWith(".tar")) { try { - return new TrueZipLegacyChunkStore(file); + ChunkStore chunkStore = new TrueZipMcRegionChunkStore(file); + + if (!chunkStore.isValid()) { + return new TrueZipLegacyChunkStore(file); + } + + return chunkStore; } catch (NoClassDefFoundError e) { throw new DataException("TrueZIP is required for .tar support"); }