diff --git a/src/java/com/threerings/cast/bundle/BundleUtil.java b/src/java/com/threerings/cast/bundle/BundleUtil.java index caa78d1e..3d8ef106 100644 --- a/src/java/com/threerings/cast/bundle/BundleUtil.java +++ b/src/java/com/threerings/cast/bundle/BundleUtil.java @@ -29,27 +29,24 @@ import java.io.ObjectInputStream; import com.samskivert.io.StreamUtil; import com.threerings.cast.Log; +import com.threerings.resource.FileResourceBundle; import com.threerings.resource.ResourceBundle; /** - * Utility functions related to creating and manipulating component - * bundles. + * Utility functions related to creating and manipulating component bundles. */ public class BundleUtil { /** The path in the metadata bundle to the serialized action table. */ public static final String ACTIONS_PATH = "actions.dat"; - /** The path in the metadata bundle to the serialized action tile sets - * table. */ + /** The path in the metadata bundle to the serialized action tile sets table. */ public static final String ACTION_SETS_PATH = "action_sets.dat"; - /** The path in the metadata bundle to the serialized component class - * table. */ + /** The path in the metadata bundle to the serialized component class table. */ public static final String CLASSES_PATH = "classes.dat"; - /** The path in the component bundle to the serialized component id to - * class/type mapping. */ + /** The path in the component bundle to the serialized component id to class/type mapping. */ public static final String COMPONENTS_PATH = "components.dat"; /** The file extension of our action tile images. */ @@ -59,22 +56,19 @@ public class BundleUtil public static final String TILESET_EXTENSION = ".dat"; /** - * Attempts to load an object from the supplied resource bundle with - * the specified path. + * Attempts to load an object from the supplied resource bundle with the specified path. * - * @param wipeBundleOnFailure if there is an error reading the object - * from the bundle and this parameter is true, we will instruct the - * bundle to delete its underlying jar file before propagating the - * exception with the expectation that it will be redownloaded and - * repaired the next time the application is run. + * @param wipeOnFailure if there is an error reading the object from the bundle and this + * parameter is true, we will instruct the bundle to delete its underlying jar file before + * propagating the exception with the expectation that it will be redownloaded and repaired the + * next time the application is run. * * @return the unserialized object in question. * - * @exception IOException thrown if an I/O error occurs while reading - * the object from the bundle. + * @exception IOException thrown if an I/O error occurs while reading the object from the + * bundle. */ - public static Object loadObject (ResourceBundle bundle, String path, - boolean wipeBundleOnFailure) + public static Object loadObject (ResourceBundle bundle, String path, boolean wipeOnFailure) throws IOException, ClassNotFoundException { InputStream bin = null; @@ -86,20 +80,19 @@ public class BundleUtil return new ObjectInputStream(bin).readObject(); } catch (InvalidClassException ice) { - Log.warning("Aiya! Serialized object is hosed " + - "[bundle=" + bundle.getSource().getPath() + - ", element=" + path + - ", error=" + ice.getMessage() + "]."); + Log.warning("Aiya! Serialized object is hosed [bundle=" + bundle + + ", element=" + path + ", error=" + ice.getMessage() + "]."); return null; } catch (IOException ioe) { - Log.warning("Error reading resource from bundle " + - "[bundle=" + bundle + ", path=" + path + - ", wiping?=" + wipeBundleOnFailure + "]."); - if (wipeBundleOnFailure) { + Log.warning("Error reading resource from bundle [bundle=" + bundle + ", path=" + path + + ", wiping?=" + wipeOnFailure + "]."); + if (wipeOnFailure) { StreamUtil.close(bin); bin = null; - bundle.wipeBundle(false); + if (bundle instanceof FileResourceBundle) { + ((FileResourceBundle)bundle).wipeBundle(false); + } } throw ioe; diff --git a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java index 7f3d6b62..1d340eb4 100644 --- a/src/java/com/threerings/cast/bundle/BundledComponentRepository.java +++ b/src/java/com/threerings/cast/bundle/BundledComponentRepository.java @@ -36,6 +36,7 @@ import com.samskivert.util.IntIntMap; import com.samskivert.util.Predicate; import com.samskivert.util.Tuple; +import com.threerings.resource.FileResourceBundle; import com.threerings.resource.ResourceBundle; import com.threerings.resource.ResourceManager; @@ -270,7 +271,7 @@ public class BundledComponentRepository // from interface ImageDataProvider public String getIdent () { - return "bcr:" + _bundle.getSource(); + return "bcr:" + _bundle.getIdent(); } // from interface ImageDataProvider @@ -331,8 +332,8 @@ public class BundledComponentRepository !StandardActions.SHADOW_TYPE.equals(type)) { Log.warning("Unable to locate tileset for action '" + imgpath + "' " + component + "."); - if (_wipeOnFailure) { - _bundle.wipeBundle(false); + if (_wipeOnFailure && _bundle instanceof FileResourceBundle) { + ((FileResourceBundle)_bundle).wipeBundle(false); } } return null; diff --git a/src/java/com/threerings/cast/bundle/tools/DumpBundle.java b/src/java/com/threerings/cast/bundle/tools/DumpBundle.java index d745539f..4bbede1a 100644 --- a/src/java/com/threerings/cast/bundle/tools/DumpBundle.java +++ b/src/java/com/threerings/cast/bundle/tools/DumpBundle.java @@ -29,8 +29,9 @@ import java.util.Map; import com.samskivert.util.HashIntMap; import com.samskivert.util.StringUtil; -import com.threerings.resource.ResourceBundle; import com.threerings.cast.bundle.BundleUtil; +import com.threerings.resource.FileResourceBundle; +import com.threerings.resource.ResourceBundle; /** * Dumps the contents of a component bundle to stdout. @@ -48,7 +49,7 @@ public class DumpBundle for (int i = 0; i < args.length; i++) { File file = new File(args[i]); try { - ResourceBundle bundle = new ResourceBundle(file); + ResourceBundle bundle = new FileResourceBundle(file); HashMap actions = (HashMap)BundleUtil.loadObject( bundle, BundleUtil.ACTIONS_PATH, false); diff --git a/src/java/com/threerings/media/tile/bundle/TileSetBundle.java b/src/java/com/threerings/media/tile/bundle/TileSetBundle.java index 8bb0f9af..9493c7c6 100644 --- a/src/java/com/threerings/media/tile/bundle/TileSetBundle.java +++ b/src/java/com/threerings/media/tile/bundle/TileSetBundle.java @@ -22,7 +22,7 @@ package com.threerings.media.tile.bundle; import java.awt.image.BufferedImage; -import java.io.File; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -36,30 +36,21 @@ import com.threerings.media.image.ImageDataProvider; import com.threerings.media.tile.TileSet; /** - * A tileset bundle is used to load up tilesets by id from a persistent - * bundle of tilesets stored on the local filesystem. + * A tileset bundle is used to load up tilesets by id from a persistent bundle of tilesets stored + * on the local filesystem. */ public class TileSetBundle extends HashIntMap implements Serializable, ImageDataProvider { /** - * Initializes this resource bundle with a reference to the jarfile - * from which it was loaded and from which it can load image data. The - * image manager will be used to decode the images. + * Initializes this resource bundle with a reference to the jarfile from which it was loaded + * and from which it can load image data. The image manager will be used to decode the images. */ public void init (ResourceBundle bundle) { _bundle = bundle; } - /** - * Returns the bundle file from which our tiles are fetched. - */ - public File getSource () - { - return _bundle.getSource(); - } - /** * Adds a tileset to this tileset bundle. */ @@ -95,7 +86,7 @@ public class TileSetBundle extends HashIntMap // documentation inherited from interface public String getIdent () { - return "tsb:" + _bundle.getSource(); + return "tsb:" + _bundle.getIdent(); } // documentation inherited from interface @@ -135,7 +126,7 @@ public class TileSetBundle extends HashIntMap /** That from which we load our tile images. */ protected transient ResourceBundle _bundle; - /** Increase this value when object's serialized state is impacted by - * a class change (modification of fields, inheritance). */ + /** Increase this value when object's serialized state is impacted by a class change + * (modification of fields, inheritance). */ private static final long serialVersionUID = 2; } diff --git a/src/java/com/threerings/media/tile/bundle/tools/DumpBundle.java b/src/java/com/threerings/media/tile/bundle/tools/DumpBundle.java index 661febf2..a88ff2cc 100644 --- a/src/java/com/threerings/media/tile/bundle/tools/DumpBundle.java +++ b/src/java/com/threerings/media/tile/bundle/tools/DumpBundle.java @@ -24,6 +24,7 @@ package com.threerings.media.tile.bundle.tools; import java.io.File; import java.util.Iterator; +import com.threerings.resource.FileResourceBundle; import com.threerings.resource.ResourceManager; import com.threerings.resource.ResourceBundle; @@ -63,7 +64,7 @@ public class DumpBundle try { TileSetBundle tsb = null; if (args[i].endsWith(".jar")) { - ResourceBundle bundle = new ResourceBundle(file); + ResourceBundle bundle = new FileResourceBundle(file); tsb = BundleUtil.extractBundle(bundle); tsb.init(bundle); } else { diff --git a/src/java/com/threerings/resource/FileResourceBundle.java b/src/java/com/threerings/resource/FileResourceBundle.java new file mode 100644 index 00000000..88c1d3ee --- /dev/null +++ b/src/java/com/threerings/resource/FileResourceBundle.java @@ -0,0 +1,422 @@ +// +// $Id: ResourceBundle.java 310 2007-10-26 19:50:53Z mdb $ +// +// Nenya library - tools for developing networked games +// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/nenya/ +// +// This library 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 2.1 of the License, or +// (at your option) any later version. +// +// This library 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 library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.resource; + +import java.awt.image.BufferedImage; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import com.samskivert.util.FileUtil; +import com.samskivert.util.StringUtil; + +import org.apache.commons.io.IOUtils; + +/** + * A resource bundle provides access to the resources in a jar file. + */ +public class FileResourceBundle extends ResourceBundle +{ + /** + * Constructs a resource bundle with the supplied jar file. + * + * @param source a file object that references our source jar file. + */ + public FileResourceBundle (File source) + { + this(source, false, false); + } + + /** + * Constructs a resource bundle with the supplied jar file. + * + * @param source a file object that references our source jar file. + * @param delay if true, the bundle will wait until someone calls + * {@link #sourceIsReady} before allowing access to its resources. + * @param unpack if true the bundle will unpack itself into a + * temporary directory + */ + public FileResourceBundle (File source, boolean delay, boolean unpack) + { + _source = source; + if (unpack) { + String root = stripSuffix(source.getPath()); + _unpacked = new File(root + ".stamp"); + _cache = new File(root); + } + + if (!delay) { + sourceIsReady(); + } + } + + @Override // from ResourceBundle + public String getIdent () + { + return _source.getPath(); + } + + @Override // from ResourceBundle + public InputStream getResource (String path) + throws IOException + { + // unpack our resources into a temp directory so that we can load + // them quickly and the file system can cache them sensibly + File rfile = getResourceFile(path); + return (rfile == null) ? null : new FileInputStream(rfile); + } + + @Override // from ResourceBundle + public BufferedImage getImageResource (String path) + throws IOException + { + return ResourceManager.loadImage(getResourceFile(path)); + } + + /** + * Returns the {@link File} from which resources are fetched for this + * bundle. + */ + public File getSource () + { + return _source; + } + + /** + * @return true if the bundle is fully downloaded and successfully + * unpacked. + */ + public boolean isUnpacked () + { + return (_source.exists() && _unpacked != null && + _unpacked.lastModified() == _source.lastModified()); + } + + /** + * Called by the resource manager once it has ensured that our + * resource jar file is up to date and ready for reading. + * + * @return true if we successfully unpacked our resources, false if we + * encountered errors in doing so. + */ + public boolean sourceIsReady () + { + // make a note of our source's last modification time + _sourceLastMod = _source.lastModified(); + + // if we are unpacking files, the time to do so is now + if (_unpacked != null && _unpacked.lastModified() != _sourceLastMod) { + try { + resolveJarFile(); + } catch (IOException ioe) { + Log.warning("Failure resolving jar file '" + _source + + "': " + ioe + "."); + wipeBundle(true); + return false; + } + + Log.info("Unpacking into " + _cache + "..."); + if (!_cache.exists()) { + if (!_cache.mkdir()) { + Log.warning("Failed to create bundle cache directory '" + + _cache + "'."); + closeJar(); + // we are hopelessly fucked + return false; + } + } else { + FileUtil.recursiveClean(_cache); + } + + // unpack the jar file (this will close the jar when it's done) + if (!FileUtil.unpackJar(_jarSource, _cache)) { + // if something went awry, delete everything in the hopes + // that next time things will work + wipeBundle(true); + return false; + } + + // if everything unpacked smoothly, create our unpack stamp + try { + _unpacked.createNewFile(); + if (!_unpacked.setLastModified(_sourceLastMod)) { + Log.warning("Failed to set last mod on stamp file '" + + _unpacked + "'."); + } + } catch (IOException ioe) { + Log.warning("Failure creating stamp file '" + _unpacked + + "': " + ioe + "."); + // no need to stick a fork in things at this point + } + } + + return true; + } + + /** + * Clears out everything associated with this resource bundle in the + * hopes that we can download it afresh and everything will work the + * next time around. + */ + public void wipeBundle (boolean deleteJar) + { + // clear out our cache directory + if (_cache != null) { + FileUtil.recursiveClean(_cache); + } + + // delete our unpack stamp file + if (_unpacked != null) { + _unpacked.delete(); + } + + // clear out any .jarv file that Getdown might be maintaining so + // that we ensure that it is revalidated + File vfile = new File(FileUtil.resuffix(_source, ".jar", ".jarv")); + if (vfile.exists() && !vfile.delete()) { + Log.warning("Failed to delete " + vfile + "."); + } + + // close and delete our source jar file + if (deleteJar && _source != null) { + closeJar(); + if (!_source.delete()) { + Log.warning("Failed to delete " + _source + + " [exists=" + _source.exists() + "]."); + } + } + } + + /** + * Returns a file from which the specified resource can be loaded. + * This method will unpack the resource into a temporary directory and + * return a reference to that file. + * + * @param path the path to the resource in this jar file. + * + * @return a file from which the resource can be loaded or null if no + * such resource exists. + */ + public File getResourceFile (String path) + throws IOException + { + if (resolveJarFile()) { + return null; + } + + // if we have been unpacked, return our unpacked file + if (_cache != null) { + File cfile = new File(_cache, path); + if (cfile.exists()) { + return cfile; + } else { + return null; + } + } + + // otherwise, we unpack resources as needed into a temp directory + String tpath = StringUtil.md5hex(_source.getPath() + "%" + path); + File tfile = new File(getCacheDir(), tpath); + if (tfile.exists() && (tfile.lastModified() > _sourceLastMod)) { + return tfile; + } + + JarEntry entry = _jarSource.getJarEntry(path); + if (entry == null) { +// Log.info("Couldn't locate " + path + " in " + _jarSource + "."); + return null; + } + + // copy the resource into the temporary file + BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(tfile)); + InputStream jin = _jarSource.getInputStream(entry); + IOUtils.copy(jin, fout); + jin.close(); + fout.close(); + + return tfile; + } + + /** + * Returns true if this resource bundle contains the resource with the + * specified path. This avoids actually loading the resource, in the + * event that the caller only cares to know that the resource exists. + */ + public boolean containsResource (String path) + { + try { + if (resolveJarFile()) { + return false; + } + return (_jarSource.getJarEntry(path) != null); + } catch (IOException ioe) { + return false; + } + } + + /** + * Returns a string representation of this resource bundle. + */ + public String toString () + { + try { + resolveJarFile(); + return (_jarSource == null) ? "[file=" + _source + "]" : + "[path=" + _jarSource.getName() + "]"; + + } catch (IOException ioe) { + return "[file=" + _source + ", ioe=" + ioe + "]"; + } + } + + /** + * Creates the internal jar file reference if we've not already got + * it; we do this lazily so as to avoid any jar- or zip-file-related + * antics until and unless doing so is required, and because the + * resource manager would like to be able to create bundles before the + * associated files have been fully downloaded. + * + * @return true if the jar file could not yet be resolved because we + * haven't yet heard from the resource manager that it is ready for us + * to access, false if all is cool. + */ + protected boolean resolveJarFile () + throws IOException + { + // if we don't yet have our resource bundle's last mod time, we + // have not yet been notified that it is ready + if (_sourceLastMod == -1) { + return true; + } + + if (!_source.exists()) { + throw new IOException("Missing jar file for resource bundle: " + + _source + "."); + } + + try { + if (_jarSource == null) { + _jarSource = new JarFile(_source); + } + return false; + + } catch (IOException ioe) { + String msg = "Failed to resolve resource bundle jar file '" + + _source + "'"; + Log.warning(msg + "."); + Log.logStackTrace(ioe); + throw (IOException) new IOException(msg).initCause(ioe); + } + } + + /** + * Closes our (possibly opened) jar file. + */ + protected void closeJar () + { + try { + if (_jarSource != null) { + _jarSource.close(); + } + } catch (Exception ioe) { + Log.warning("Failed to close jar file [path=" + _source + + ", error=" + ioe + "]."); + } + } + + /** + * Returns the cache directory used for unpacked resources. + */ + public static File getCacheDir () + { + if (_tmpdir == null) { + String tmpdir = System.getProperty("java.io.tmpdir"); + if (tmpdir == null) { + Log.info("No system defined temp directory. Faking it."); + tmpdir = System.getProperty("user.home"); + } + setCacheDir(new File(tmpdir)); + } + return _tmpdir; + } + + /** + * Specifies the directory in which our temporary resource files should be stored. + */ + public static void setCacheDir (File tmpdir) + { + String rando = Long.toHexString((long)(Math.random() * Long.MAX_VALUE)); + _tmpdir = new File(tmpdir, "narcache_" + rando); + if (!_tmpdir.exists()) { + if (_tmpdir.mkdirs()) { + Log.debug("Created narya temp cache directory '" + _tmpdir + "'."); + } else { + Log.warning("Failed to create temp cache directory '" + _tmpdir + "'."); + } + } + + // add a hook to blow away the temp directory when we exit + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run () { + Log.info("Clearing narya temp cache '" + _tmpdir + "'."); + FileUtil.recursiveDelete(_tmpdir); + } + }); + } + + /** Strips the .jar off of jar file paths. */ + protected static String stripSuffix (String path) + { + if (path.endsWith(".jar")) { + return path.substring(0, path.length()-4); + } else { + // we have to change the path somehow + return path + "-cache"; + } + } + + /** The file from which we construct our jar file. */ + protected File _source; + + /** The last modified time of our source jar file. */ + protected long _sourceLastMod = -1; + + /** A file whose timestamp indicates whether or not our existing jar + * file has been unpacked. */ + protected File _unpacked; + + /** A directory into which we unpack files from our bundle. */ + protected File _cache; + + /** The jar file from which we load resources. */ + protected JarFile _jarSource; + + /** A directory in which we temporarily unpack our resource files. */ + protected static File _tmpdir; +} diff --git a/src/java/com/threerings/resource/ResourceBundle.java b/src/java/com/threerings/resource/ResourceBundle.java index a960c164..f163e6b3 100644 --- a/src/java/com/threerings/resource/ResourceBundle.java +++ b/src/java/com/threerings/resource/ResourceBundle.java @@ -21,411 +21,39 @@ package com.threerings.resource; -import java.awt.image.BufferedImage; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import com.samskivert.util.FileUtil; -import com.samskivert.util.StringUtil; - -import org.apache.commons.io.IOUtils; +import java.awt.image.BufferedImage; /** - * A resource bundle provides access to the resources in a jar file. + * A resource bundle provides access to media resources. */ -public class ResourceBundle +public abstract class ResourceBundle { /** - * Constructs a resource bundle with the supplied jar file. - * - * @param source a file object that references our source jar file. + * Uniquely identifies this resource bundle. Used to create cache keys. */ - public ResourceBundle (File source) - { - this(source, false, false); - } + public abstract String getIdent (); /** - * Constructs a resource bundle with the supplied jar file. - * - * @param source a file object that references our source jar file. - * @param delay if true, the bundle will wait until someone calls - * {@link #sourceIsReady} before allowing access to its resources. - * @param unpack if true the bundle will unpack itself into a - * temporary directory - */ - public ResourceBundle (File source, boolean delay, boolean unpack) - { - _source = source; - if (unpack) { - String root = stripSuffix(source.getPath()); - _unpacked = new File(root + ".stamp"); - _cache = new File(root); - } - - if (!delay) { - sourceIsReady(); - } - } - - /** - * Returns the {@link File} from which resources are fetched for this - * bundle. - */ - public File getSource () - { - return _source; - } - - /** - * @return true if the bundle is fully downloaded and successfully - * unpacked. - */ - public boolean isUnpacked () - { - return (_source.exists() && _unpacked != null && - _unpacked.lastModified() == _source.lastModified()); - } - - /** - * Called by the resource manager once it has ensured that our - * resource jar file is up to date and ready for reading. - * - * @return true if we successfully unpacked our resources, false if we - * encountered errors in doing so. - */ - public boolean sourceIsReady () - { - // make a note of our source's last modification time - _sourceLastMod = _source.lastModified(); - - // if we are unpacking files, the time to do so is now - if (_unpacked != null && _unpacked.lastModified() != _sourceLastMod) { - try { - resolveJarFile(); - } catch (IOException ioe) { - Log.warning("Failure resolving jar file '" + _source + - "': " + ioe + "."); - wipeBundle(true); - return false; - } - - Log.info("Unpacking into " + _cache + "..."); - if (!_cache.exists()) { - if (!_cache.mkdir()) { - Log.warning("Failed to create bundle cache directory '" + - _cache + "'."); - closeJar(); - // we are hopelessly fucked - return false; - } - } else { - FileUtil.recursiveClean(_cache); - } - - // unpack the jar file (this will close the jar when it's done) - if (!FileUtil.unpackJar(_jarSource, _cache)) { - // if something went awry, delete everything in the hopes - // that next time things will work - wipeBundle(true); - return false; - } - - // if everything unpacked smoothly, create our unpack stamp - try { - _unpacked.createNewFile(); - if (!_unpacked.setLastModified(_sourceLastMod)) { - Log.warning("Failed to set last mod on stamp file '" + - _unpacked + "'."); - } - } catch (IOException ioe) { - Log.warning("Failure creating stamp file '" + _unpacked + - "': " + ioe + "."); - // no need to stick a fork in things at this point - } - } - - return true; - } - - /** - * Clears out everything associated with this resource bundle in the - * hopes that we can download it afresh and everything will work the - * next time around. - */ - public void wipeBundle (boolean deleteJar) - { - // clear out our cache directory - if (_cache != null) { - FileUtil.recursiveClean(_cache); - } - - // delete our unpack stamp file - if (_unpacked != null) { - _unpacked.delete(); - } - - // clear out any .jarv file that Getdown might be maintaining so - // that we ensure that it is revalidated - File vfile = new File(FileUtil.resuffix(_source, ".jar", ".jarv")); - if (vfile.exists() && !vfile.delete()) { - Log.warning("Failed to delete " + vfile + "."); - } - - // close and delete our source jar file - if (deleteJar && _source != null) { - closeJar(); - if (!_source.delete()) { - Log.warning("Failed to delete " + _source + - " [exists=" + _source.exists() + "]."); - } - } - } - - /** - * Fetches the named resource from this bundle. The path should be - * specified as a relative, platform independent path (forward - * slashes). For example sounds/scream.au. + * Fetches the named resource from this bundle. The path should be specified as a relative, + * platform independent path (forward slashes). For example sounds/scream.au. * * @param path the path to the resource in this jar file. * - * @return an input stream from which the resource can be loaded or - * null if no such resource exists. + * @return an input stream from which the resource can be loaded or null if no such resource + * exists. * - * @exception IOException thrown if an error occurs locating the - * resource in the jar file. + * @exception IOException thrown if an error occurs locating the resource in the jar file. */ - public InputStream getResource (String path) - throws IOException - { - // unpack our resources into a temp directory so that we can load - // them quickly and the file system can cache them sensibly - File rfile = getResourceFile(path); - return (rfile == null) ? null : new FileInputStream(rfile); - } - - /** - * Returns a file from which the specified resource can be loaded. - * This method will unpack the resource into a temporary directory and - * return a reference to that file. - * - * @param path the path to the resource in this jar file. - * - * @return a file from which the resource can be loaded or null if no - * such resource exists. - */ - public File getResourceFile (String path) - throws IOException - { - if (resolveJarFile()) { - return null; - } - - // if we have been unpacked, return our unpacked file - if (_cache != null) { - File cfile = new File(_cache, path); - if (cfile.exists()) { - return cfile; - } else { - return null; - } - } - - // otherwise, we unpack resources as needed into a temp directory - String tpath = StringUtil.md5hex(_source.getPath() + "%" + path); - File tfile = new File(getCacheDir(), tpath); - if (tfile.exists() && (tfile.lastModified() > _sourceLastMod)) { - return tfile; - } - - JarEntry entry = _jarSource.getJarEntry(path); - if (entry == null) { -// Log.info("Couldn't locate " + path + " in " + _jarSource + "."); - return null; - } - - // copy the resource into the temporary file - BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(tfile)); - InputStream jin = _jarSource.getInputStream(entry); - IOUtils.copy(jin, fout); - jin.close(); - fout.close(); - - return tfile; - } + public abstract InputStream getResource (String path) + throws IOException; /** * Decodes and returns the specified image resource. Returns null if no resource exists at the * specified path. */ - public BufferedImage getImageResource (String path) - throws IOException - { - return ResourceManager.loadImage(getResourceFile(path)); - } - - /** - * Returns true if this resource bundle contains the resource with the - * specified path. This avoids actually loading the resource, in the - * event that the caller only cares to know that the resource exists. - */ - public boolean containsResource (String path) - { - try { - if (resolveJarFile()) { - return false; - } - return (_jarSource.getJarEntry(path) != null); - } catch (IOException ioe) { - return false; - } - } - - /** - * Returns a string representation of this resource bundle. - */ - public String toString () - { - try { - resolveJarFile(); - return (_jarSource == null) ? "[file=" + _source + "]" : - "[path=" + _jarSource.getName() + "]"; - - } catch (IOException ioe) { - return "[file=" + _source + ", ioe=" + ioe + "]"; - } - } - - /** - * Creates the internal jar file reference if we've not already got - * it; we do this lazily so as to avoid any jar- or zip-file-related - * antics until and unless doing so is required, and because the - * resource manager would like to be able to create bundles before the - * associated files have been fully downloaded. - * - * @return true if the jar file could not yet be resolved because we - * haven't yet heard from the resource manager that it is ready for us - * to access, false if all is cool. - */ - protected boolean resolveJarFile () - throws IOException - { - // if we don't yet have our resource bundle's last mod time, we - // have not yet been notified that it is ready - if (_sourceLastMod == -1) { - return true; - } - - if (!_source.exists()) { - throw new IOException("Missing jar file for resource bundle: " + - _source + "."); - } - - try { - if (_jarSource == null) { - _jarSource = new JarFile(_source); - } - return false; - - } catch (IOException ioe) { - String msg = "Failed to resolve resource bundle jar file '" + - _source + "'"; - Log.warning(msg + "."); - Log.logStackTrace(ioe); - throw (IOException) new IOException(msg).initCause(ioe); - } - } - - /** - * Closes our (possibly opened) jar file. - */ - protected void closeJar () - { - try { - if (_jarSource != null) { - _jarSource.close(); - } - } catch (Exception ioe) { - Log.warning("Failed to close jar file [path=" + _source + - ", error=" + ioe + "]."); - } - } - - /** - * Returns the cache directory used for unpacked resources. - */ - public static File getCacheDir () - { - if (_tmpdir == null) { - String tmpdir = System.getProperty("java.io.tmpdir"); - if (tmpdir == null) { - Log.info("No system defined temp directory. Faking it."); - tmpdir = System.getProperty("user.home"); - } - setCacheDir(new File(tmpdir)); - } - return _tmpdir; - } - - /** - * Specifies the directory in which our temporary resource files should be stored. - */ - public static void setCacheDir (File tmpdir) - { - String rando = Long.toHexString((long)(Math.random() * Long.MAX_VALUE)); - _tmpdir = new File(tmpdir, "narcache_" + rando); - if (!_tmpdir.exists()) { - if (_tmpdir.mkdirs()) { - Log.debug("Created narya temp cache directory '" + _tmpdir + "'."); - } else { - Log.warning("Failed to create temp cache directory '" + _tmpdir + "'."); - } - } - - // add a hook to blow away the temp directory when we exit - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run () { - Log.info("Clearing narya temp cache '" + _tmpdir + "'."); - FileUtil.recursiveDelete(_tmpdir); - } - }); - } - - /** Strips the .jar off of jar file paths. */ - protected static String stripSuffix (String path) - { - if (path.endsWith(".jar")) { - return path.substring(0, path.length()-4); - } else { - // we have to change the path somehow - return path + "-cache"; - } - } - - /** The file from which we construct our jar file. */ - protected File _source; - - /** The last modified time of our source jar file. */ - protected long _sourceLastMod = -1; - - /** A file whose timestamp indicates whether or not our existing jar - * file has been unpacked. */ - protected File _unpacked; - - /** A directory into which we unpack files from our bundle. */ - protected File _cache; - - /** The jar file from which we load resources. */ - protected JarFile _jarSource; - - /** A directory in which we temporarily unpack our resource files. */ - protected static File _tmpdir; + public abstract BufferedImage getImageResource (String path) + throws IOException; } diff --git a/src/java/com/threerings/resource/ResourceManager.java b/src/java/com/threerings/resource/ResourceManager.java index 023f4e33..e360562a 100644 --- a/src/java/com/threerings/resource/ResourceManager.java +++ b/src/java/com/threerings/resource/ResourceManager.java @@ -248,7 +248,7 @@ public class ResourceManager } // resolve the configured resource sets - ArrayList dlist = new ArrayList(); + List dlist = new ArrayList(); Enumeration names = config.propertyNames(); while (names.hasMoreElements()) { String key = (String)names.nextElement(); @@ -326,7 +326,7 @@ public class ResourceManager public boolean checkBundle (String path) { File bfile = getResourceFile(path); - return (bfile == null) ? false : new ResourceBundle(bfile, true, _unpack).isUnpacked(); + return (bfile == null) ? false : new FileResourceBundle(bfile, true, _unpack).isUnpacked(); } /** @@ -345,7 +345,7 @@ public class ResourceManager return; } - final ResourceBundle bundle = new ResourceBundle(bfile, true, _unpack); + final FileResourceBundle bundle = new FileResourceBundle(bfile, true, _unpack); if (bundle.isUnpacked()) { if (bundle.sourceIsReady()) { listener.requestCompleted(bundle); @@ -404,8 +404,8 @@ public class ResourceManager InputStream in = null; // first look for this resource in our default resource bundle - for (int i = 0; i < _default.length; i++) { - in = _default[i].getResource(path); + for (ResourceBundle bundle : _default) { + in = bundle.getResource(path); if (in != null) { return in; } @@ -458,10 +458,10 @@ public class ResourceManager throws IOException { // first look for this resource in our default resource bundle - for (int i = 0; i < _default.length; i++) { - File file = _default[i].getResourceFile(path); - if (file != null) { - return loadImage(file); + for (ResourceBundle bundle : _default) { + BufferedImage image = bundle.getImageResource(path); + if (image != null) { + return image; } } @@ -624,13 +624,15 @@ public class ResourceManager /** * Loads up a resource set based on the supplied definition information. */ - protected void resolveResourceSet (String setName, String definition, List dlist) + protected void resolveResourceSet ( + String setName, String definition, List dlist) { - ArrayList set = new ArrayList(); + List set = new ArrayList(); StringTokenizer tok = new StringTokenizer(definition, ":"); while (tok.hasMoreTokens()) { String path = tok.nextToken().trim(); - ResourceBundle bundle = new ResourceBundle(getResourceFile(path), true, _unpack); + FileResourceBundle bundle = + new FileResourceBundle(getResourceFile(path), true, _unpack); set.add(bundle); if (bundle.isUnpacked() && bundle.sourceIsReady()) { continue; @@ -639,8 +641,7 @@ public class ResourceManager } // convert our array list into an array and stick it in the table - ResourceBundle[] setvec = new ResourceBundle[set.size()]; - set.toArray(setvec); + ResourceBundle[] setvec = set.toArray(new ResourceBundle[set.size()]); _sets.put(setName, setvec); // if this is our default resource bundle, keep a reference to it @@ -693,7 +694,7 @@ public class ResourceManager /** Used to unpack bundles on a separate thread. */ protected static class Unpacker extends Thread { - public Unpacker (List bundles, InitObserver obs) + public Unpacker (List bundles, InitObserver obs) { _bundles = bundles; _obs = obs; @@ -708,9 +709,9 @@ public class ResourceManager } int count = 0; - for (Iterator iter = _bundles.iterator(); iter.hasNext(); ) { - ResourceBundle bundle = (ResourceBundle)iter.next(); - if (!bundle.sourceIsReady()) { + for (ResourceBundle bundle : _bundles) { + if (bundle instanceof FileResourceBundle && + !((FileResourceBundle)bundle).sourceIsReady()) { Log.warning("Bundle failed to initialize " + bundle + "."); } if (_obs != null) { @@ -732,7 +733,7 @@ public class ResourceManager } } - protected List _bundles; + protected List _bundles; protected InitObserver _obs; }