diff --git a/.gitignore b/.gitignore index 164936d32..ed9bf34b0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ manifest.mf *.iws .idea/ -EssentialsRelease/ \ No newline at end of file +EssentialsRelease/ +/Essentials/dependency-reduced-pom.xml \ No newline at end of file diff --git a/Essentials/pom.xml b/Essentials/pom.xml index f3fd665ff..75b7823be 100644 --- a/Essentials/pom.xml +++ b/Essentials/pom.xml @@ -21,7 +21,17 @@ org.projectlombok lombok - 0.11.4 + 0.11.6 + + + commons-io + commons-io + 2.4 + + + org.apache.commons + commons-compress + 1.4.1 @@ -63,4 +73,37 @@ 1.2 + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.0 + + + package + + shade + + + + + commons-io:* + org.apache.commons:* + + + true + + + org.apache.commons + net.ess3.commons + + + + + + + + diff --git a/Essentials/src/net/ess3/storage/StorageObjectMap.java b/Essentials/src/net/ess3/storage/StorageObjectMap.java index bec082e31..945beb06a 100644 --- a/Essentials/src/net/ess3/storage/StorageObjectMap.java +++ b/Essentials/src/net/ess3/storage/StorageObjectMap.java @@ -5,16 +5,23 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.util.concurrent.UncheckedExecutionException; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.util.Collections; +import java.util.Enumeration; import java.util.Locale; import java.util.Set; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import java.util.regex.Pattern; import net.ess3.api.IEssentials; import net.ess3.api.InvalidNameException; import net.ess3.utils.Util; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.io.IOUtils; public abstract class StorageObjectMap extends CacheLoader implements IStorageObjectMap @@ -23,6 +30,7 @@ public abstract class StorageObjectMap extends CacheLoader impleme private final transient File folder; protected final transient Cache cache = CacheBuilder.newBuilder().softValues().build(this); protected final transient ConcurrentSkipListSet keys = new ConcurrentSkipListSet(); + protected final transient ConcurrentSkipListMap zippedfiles = new ConcurrentSkipListMap(); public StorageObjectMap(final IEssentials ess, final String folderName) { @@ -51,20 +59,77 @@ public abstract class StorageObjectMap extends CacheLoader impleme cache.invalidateAll(); for (String string : folder.list()) { + final File file = new File(folder, string); + if (!file.isFile() || !file.canRead()) + { + continue; + } + if (string.endsWith(".yml")) + { + addFileToKeys(string.substring(0, string.length() - 4)); + } + if (string.endsWith(".zip")) + { + addZipFile(file); + } + } + } + + private void addFileToKeys(String filename) + { + try + { + + final String name = Util.decodeFileName(filename); + keys.add(name.toLowerCase(Locale.ENGLISH)); + + } + catch (InvalidNameException ex) + { + ess.getLogger().log(Level.WARNING, "Invalid filename: " + filename, ex); + } + } + + private final Pattern zipCheck = Pattern.compile("^[a-zA-Z0-9-]+\\.yml$"); + + private void addZipFile(File file) + { + try + { + ZipFile zipFile = new ZipFile(file); try { - if (!string.endsWith(".yml")) + Enumeration entries = zipFile.getEntriesInPhysicalOrder(); + while (entries.hasMoreElements()) { - continue; + ZipArchiveEntry entry = entries.nextElement(); + String name = entry.getName(); + if (entry.isDirectory() || entry.getSize() == 0 || !zipCheck.matcher(name).matches()) + { + continue; + } + try + { + String shortName = name.substring(0, name.length() - 4); + addFileToKeys(shortName); + final String decodedName = Util.decodeFileName(shortName).toLowerCase(Locale.ENGLISH); + zippedfiles.put(decodedName, file); + } + catch (InvalidNameException ex) + { + ess.getLogger().log(Level.WARNING, "Invalid filename " + name + " in " + file.getAbsoluteFile(), ex); + } } - final String name = Util.decodeFileName(string.substring(0, string.length() - 4)); - keys.add(name.toLowerCase(Locale.ENGLISH)); } - catch (InvalidNameException ex) + finally { - ess.getLogger().log(Level.WARNING, "Invalid filename: " + string, ex); + zipFile.close(); } } + catch (IOException ex) + { + ess.getLogger().log(Level.WARNING, "Error opening file " + file.getAbsolutePath(), ex); + } } }); } @@ -98,13 +163,18 @@ public abstract class StorageObjectMap extends CacheLoader impleme @Override public void removeObject(final String name) throws InvalidNameException { - keys.remove(name.toLowerCase(Locale.ENGLISH)); - cache.invalidate(name.toLowerCase(Locale.ENGLISH)); + String lowerCaseName = name.toLowerCase(Locale.ENGLISH); + keys.remove(lowerCaseName); + cache.invalidate(lowerCaseName); final File file = getStorageFile(name); if (file.exists()) { file.delete(); } + if (zippedfiles.containsKey(lowerCaseName)) + { + zippedfiles.put(lowerCaseName, null); + } } @Override @@ -126,7 +196,14 @@ public abstract class StorageObjectMap extends CacheLoader impleme { throw new InvalidNameException(new IOException("Folder does not exists: " + folder)); } - return new File(folder, Util.sanitizeFileName(name) + ".yml"); + String sanitizedFilename = Util.sanitizeFileName(name) + ".yml"; + File file = new File(folder, sanitizedFilename); + + if (!file.exists()) + { + extractFileFromZip(name, sanitizedFilename, file); + } + return file; } @Override @@ -134,4 +211,44 @@ public abstract class StorageObjectMap extends CacheLoader impleme { loadAllObjectsAsync(); } + + private void extractFileFromZip(final String name, String sanitizedFilename, File file) + { + String lowerCaseName = name.toLowerCase(Locale.ENGLISH); + File zipFile = zippedfiles.get(lowerCaseName); + if (zipFile != null) + { + try + { + ZipFile zip = new ZipFile(zipFile); + try + { + ZipArchiveEntry entry = zip.getEntry(sanitizedFilename); + if (entry != null) + { + try + { + IOUtils.copy(zip.getInputStream(entry), new FileOutputStream(file)); + } + catch (IOException ex) + { + ess.getLogger().log(Level.WARNING, "Failed to write file: " + file.getAbsolutePath(), ex); + } + } + else + { + ess.getLogger().log(Level.WARNING, "File " + file.getAbsolutePath() + " not found in zip file " + zipFile.getAbsolutePath()); + } + } + finally + { + zip.close(); + } + } + catch (IOException ex) + { + ess.getLogger().log(Level.WARNING, "File " + file.getAbsolutePath() + " could not be extracted from " + zipFile.getAbsolutePath(), ex); + } + } + } } diff --git a/Essentials/test/net/ess3/EssentialsTest.java b/Essentials/test/net/ess3/EssentialsTest.java index 547b03f19..5eef12025 100644 --- a/Essentials/test/net/ess3/EssentialsTest.java +++ b/Essentials/test/net/ess3/EssentialsTest.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.logging.Logger; import junit.framework.TestCase; import net.ess3.api.IPlugin; +import org.apache.commons.io.FileUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.Server; @@ -82,17 +83,8 @@ public abstract class EssentialsTest extends TestCase plugin = mock(IPlugin.class); - File tmp; - try - { - tmp = File.createTempFile("ess", "tmp"); - } - catch (IOException ex) - { - throw new RuntimeException(ex); - } - tmp.deleteOnExit(); - when(plugin.getDataFolder()).thenReturn(tmp.getParentFile()); + File folder = FileUtils.getTempDirectory(); + when(plugin.getDataFolder()).thenReturn(folder); when(world.getName()).thenReturn("world"); ess = new Essentials(server, logger, plugin);