/* * This file is part of FTB Launcher. * * Copyright © 2012-2016, FTB Launcher Contributors <https://github.com/Slowpoke101/FTBLaunch/> * FTB Launcher is 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 net.ftb.util; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.annotation.Nonnull; import com.beust.jcommander.internal.Sets; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import net.ftb.data.ModPack; import net.ftb.log.Logger; /** * Utility functions specific to mod packs, such as information on default mods */ public final class ModPackUtil { /** Cached results for lookups into the zip archives for the mod packs - avoid repeated file I/O */ private static Cache<String, Set<String>> defaultMods; static { // The "10" initial size is somewhat arbitrary - may be changed without significant concern defaultMods = CacheBuilder.newBuilder().initialCapacity(10).build(); } private ModPackUtil () { // Prevent instantiation of the utility class, meant to provide static methods } /** * Looks up the set of mod files which are downloaded with a "stock" installation of the mod * pack, before any user customization * * <p> * Uses Java FileSystem to read the mods files from the cached/downloaded zip archive of the mod, * and caches the result to avoid repeated file I/O * </p> * * @param modpack The mod pack to look up default mod entries for * @return A set of the mod files found in the default mod archive, or an empty set if the * archive could not be located */ public static Set<String> getDefaultModFiles (ModPack modpack) { // DEV NOTE: A set is used to ensure that there are no duplicate entries in the returned group, and to // utilize the more efficient backing implementation for lookup operations such as .contains() if (modpack != null) { String modpackKey = getModpackCacheKey(modpack); Set<String> defaults = defaultMods.getIfPresent(modpackKey); if (defaults == null || defaults.isEmpty()) { defaults = loadDefaultMods(modpack); defaultMods.put(modpackKey, defaults); } return defaults; } else { Logger.logWarn("Null modpack provided for retrieving default mod data"); } return Sets.newHashSet(); } /** * Clears the cached entry for default mods in a mod pack. Used when the mod pack is * being updated or forcibly reloaded * @param modpack The mod pack to clear cached data for */ public static void clearDefaultModFiles (ModPack modpack) { if (modpack != null) { defaultMods.invalidate(getModpackCacheKey(modpack)); } else { Logger.logWarn("Null modpack provided for clearing default mod data"); } } /** * @param modpack The modpack to construct a lookup key for * @return A unique string key used to reference the modpack's data in the cache */ private static String getModpackCacheKey (@Nonnull ModPack modpack) { // Using the directory combined with the URL, because this is used as the cached download location for each mod, // and should therefore be guaranteed unique per modpack return (modpack.getDir() + ":" + modpack.getUrl()); } /** * Performs file system lookup and reading of the default mods in the archive * @param modpack The mod pack to look up * @return A set of the mod files found in the default mod archive, or an empty set if the * archive could not be located */ private static Set<String> loadDefaultMods (@Nonnull ModPack modpack) { // This was written based on ModManager's update routine String installPath = OSUtils.getCacheStorageLocation(); File modPackZip = new File(installPath, "ModPacks" + File.separator + modpack.getDir() + File.separator + modpack.getUrl()); if (modPackZip.exists()) { ZipInputStream zip = null; try { zip = new ZipInputStream(new FileInputStream(modPackZip)); Set<String> fileNames = new HashSet<String>(); ZipEntry ze = null; while ((ze = zip.getNextEntry()) != null) { if (!ze.isDirectory()) { String fileName = ze.getName(); if (fileName != null) { // This is always '/', regardless of the OS - does not match the value of File.separator int lastSeparator = fileName.lastIndexOf('/'); if (fileName.length() > lastSeparator + 1) { if (lastSeparator != -1) { fileName = fileName.substring(lastSeparator + 1); } fileNames.add(fileName.toLowerCase()); } } } } return fileNames; // TODO (romeara) - When the launcher is upgraded to Java7, switch to using nio's FileSystem and visitor pattern, // it is significantly more performant, such as below: // FileSystem system = FileSystems.newFileSystem(modPackZip.toPath(), null); // // Visitor is an implements of FileVistor // Files.walkFileTree(system.getPath(File.separator), visitor); // return visitor.getFileNames(); } catch (IOException e) { Logger.logError("Error attempting to read default mods", e); return Sets.newHashSet(); } finally { if (zip != null) { try { zip.close(); } catch (IOException e) { Logger.logError("Error attempting to close stream used to read default mods", e); } } } } return Sets.newHashSet(); } }