Modified the resource manager to load resource sets not from the file
system but from a URL into a local cache. It presently loads all of the resources every time the application starts. This will eventually be made to be more sophisticated. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@862 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
//
|
//
|
||||||
// $Id: ResourceManager.java,v 1.6 2001/11/27 08:09:35 mdb Exp $
|
// $Id: ResourceManager.java,v 1.7 2002/01/16 03:00:06 mdb Exp $
|
||||||
|
|
||||||
package com.threerings.resource;
|
package com.threerings.resource;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@@ -19,7 +18,8 @@ import java.util.HashMap;
|
|||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import com.samskivert.Log;
|
import org.apache.commons.util.StreamUtils;
|
||||||
|
import com.samskivert.util.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The resource manager is responsible for maintaining a repository of
|
* The resource manager is responsible for maintaining a repository of
|
||||||
@@ -36,77 +36,62 @@ import com.samskivert.Log;
|
|||||||
* locate a resource in the default resource set, it falls back to loading
|
* locate a resource in the default resource set, it falls back to loading
|
||||||
* the resource via the classloader (which will search the classpath).
|
* the resource via the classloader (which will search the classpath).
|
||||||
*
|
*
|
||||||
* <p> The resource manager can be provided with config properties at
|
* <p> The resource manager must be provided with the URL of a resource
|
||||||
* construct time, or it can load them via {@link #getResource} with a
|
* definition file which describes these resource sets at construct
|
||||||
* path of <code>config/resource/manager.properties</code>. The config
|
* time. The definition file will be loaded and the resource bundles
|
||||||
* properties should contain resource set definitions for the default
|
* defined within will be loaded relative to the resource definition URL.
|
||||||
* resource set and for any named resource sets needed by the application.
|
* The bundles will be cached in the user's home directory and only
|
||||||
* An example configuration follows:
|
* reloaded when the source resources have been updated. The resource
|
||||||
|
* definition file looks something like the following:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* resource.set.default = rsrc/sets/misc
|
* resource.set.default = sets/misc/config.jar: \
|
||||||
* resource.set.tiles = rsrc/sets/tiles:/global/resources/tiles: \
|
* sets/misc/icons.jar
|
||||||
* /home/mdb/test_tiles.jar
|
* resource.set.tiles = sets/tiles/ground.jar: \
|
||||||
* resource.set.sounds = rsrc/sets/sounds:/global/resources/sounds
|
* sets/tiles/objects.jar: \
|
||||||
|
* /global/resources/tiles/ground.jar: \
|
||||||
|
* /global/resources/tiles/objects.jar
|
||||||
|
* resource.set.sounds = sets/sounds/sfx.jar: \
|
||||||
|
* sets/sounds/music.jar: \
|
||||||
|
* /global/resources/sounds/sfx.jar: \
|
||||||
|
* /global/resources/sounds/music.jar
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* Platform-specific file and path separators should be used in the
|
|
||||||
* resource set definitions as these are actual file paths. If a path
|
|
||||||
* component starts with a file separator, it will be interpreted as an
|
|
||||||
* absolute path, whereas if it doesn't, it will be interpreted as
|
|
||||||
* relative to the application root that was supplied to the resource
|
|
||||||
* manager at construct time.
|
|
||||||
*
|
|
||||||
* <p> All resource set definitions are prefixed with
|
* <p> All resource set definitions are prefixed with
|
||||||
* <code>resource.set.</code> and all text following that string is
|
* <code>resource.set.</code> and all text following that string is
|
||||||
* considered to be the name of the resource set. The resource set named
|
* considered to be the name of the resource set. The resource set named
|
||||||
* <code>default</code> is the default resource set and is the one that is
|
* <code>default</code> is the default resource set and is the one that is
|
||||||
* searched for resources is a call to {@link #getResource}.
|
* searched for resources is a call to {@link #getResource}.
|
||||||
*
|
*
|
||||||
* <p> Resource set definitions can contain directories or individual jar
|
* <p> When a resource is loaded from a resource set, the set is searched
|
||||||
* files, the latter are simply added to the resource set; for the former,
|
* in the order that entries are specified in the definition.
|
||||||
* all jar files in the specified directory (but not its subdirectories)
|
|
||||||
* are added to the set. When a resource is loaded from a resource set,
|
|
||||||
* the set is searched in the order that entries are specified in the
|
|
||||||
* definition (the left-most entry first, and so on). Jar files in a
|
|
||||||
* directory are added to the set (and thus, searched) in alphabetical
|
|
||||||
* order.
|
|
||||||
*/
|
*/
|
||||||
public class ResourceManager
|
public class ResourceManager
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constructs a resource manager with the supplied application and
|
* Constructs a resource manager which will load resources as
|
||||||
* resource roots. The resource manager configuration is loaded via
|
* specified in the configuration file, the path to which is supplied
|
||||||
* the resource root (see class documentation for details).
|
* via <code>configPath</code>. If resource sets are not needed and
|
||||||
|
* resources will only be loaded via the classpath, null may be passed
|
||||||
|
* in <code>resourceURL</code> and <code>configPath</code>.
|
||||||
*
|
*
|
||||||
* @param appRoot the path to the application root directory. This is
|
|
||||||
* a platform dependent path and should contain separator characters
|
|
||||||
* proper to the host platform. If null, this will be obtained via the
|
|
||||||
* <code>application.root</code> system property.
|
|
||||||
* @param resourceRoot the path to prepend to resource paths prior to
|
* @param resourceRoot the path to prepend to resource paths prior to
|
||||||
* attempting to load them via the classloader. This is not a platform
|
* attempting to load them via the classloader. When resources are
|
||||||
* dependent path.
|
* bundled into the default resource bundle, they don't need this
|
||||||
*/
|
* prefix, but if they're to be loaded from the classpath, it's likely
|
||||||
public ResourceManager (String appRoot, String resourceRoot)
|
* that they'll live in some sort of <code>resources</code> directory
|
||||||
{
|
* to isolate them from the rest of the files in the classpath. This
|
||||||
this(appRoot, resourceRoot, null);
|
* is not a platform dependent path (forward slash is always used to
|
||||||
}
|
* separate path elements).
|
||||||
|
* @param resourceURL the base URL from which resources are loaded.
|
||||||
/**
|
* Relative paths specified in the resource definition file will be
|
||||||
* Constructs a resource manager with the supplied application and
|
* loaded relative to this path. If this is null, the system property
|
||||||
* resource roots.
|
* <code>resource_url</code> will be used, if available.
|
||||||
*
|
* @param configPath the path (relative to the resource URL) of the
|
||||||
* @param appRoot the path to the application root directory. This is
|
* resource definition file.
|
||||||
* a platform dependent path and should contain separator characters
|
|
||||||
* proper to the host platform.
|
|
||||||
* @param resourceRoot the path to prepend to resource paths prior to
|
|
||||||
* attempting to load them via the classloader. This is not a platform
|
|
||||||
* dependent path.
|
|
||||||
* @param config the configuration for this resource manager. See
|
|
||||||
* class documentation for a description of the config properties.
|
|
||||||
*/
|
*/
|
||||||
public ResourceManager (
|
public ResourceManager (
|
||||||
String appRoot, String resourceRoot, Properties config)
|
String resourceRoot, String resourceURL, String configPath)
|
||||||
{
|
{
|
||||||
// keep track of our root path
|
// keep track of our root path
|
||||||
_rootPath = resourceRoot;
|
_rootPath = resourceRoot;
|
||||||
@@ -118,50 +103,43 @@ public class ResourceManager
|
|||||||
_rootPath = _rootPath + "/";
|
_rootPath = _rootPath + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
// get our app root if we weren't provided with one
|
|
||||||
if (appRoot == null) {
|
|
||||||
try {
|
|
||||||
appRoot = System.getProperty(APP_ROOT_PROPERTY);
|
|
||||||
} catch (SecurityException se) {
|
|
||||||
// we may be running in an applet, so we'll calmly ignore
|
|
||||||
// this little freakout
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it's still null, we complain loudly
|
|
||||||
if (appRoot == null) {
|
|
||||||
Log.warning("No application root provided to resource " +
|
|
||||||
"manager. Assuming current working directory.");
|
|
||||||
appRoot = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make the app root end with a file separator (unless we're
|
|
||||||
// rolling with an empty app root)
|
|
||||||
if ((appRoot.length() > 0) && !appRoot.endsWith(File.separator)) {
|
|
||||||
appRoot = appRoot + File.separator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the classloader that loaded us
|
// use the classloader that loaded us
|
||||||
_loader = getClass().getClassLoader();
|
_loader = getClass().getClassLoader();
|
||||||
|
|
||||||
// load up our configuration if it wasn't supplied by the caller
|
// if the resource URL wasn't provided, we try to figure it out
|
||||||
try {
|
// for ourselves
|
||||||
if (config == null) {
|
if (resourceURL == null) {
|
||||||
config = new Properties();
|
try {
|
||||||
config.load(getResource(CONFIG_PATH));
|
// first look for the explicit system property
|
||||||
|
resourceURL = System.getProperty("resource_url");
|
||||||
|
|
||||||
|
// if that doesn't work, fall back to the current directory
|
||||||
|
if (resourceURL == null) {
|
||||||
|
resourceURL = "file:" + System.getProperty("user.dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SecurityException se) {
|
||||||
|
resourceURL = "file:" + File.separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (FileNotFoundException fnfe) {
|
|
||||||
// nothing to worry about here, we aren't required to have a
|
|
||||||
// configuration
|
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
// complain if some other error occurs
|
|
||||||
Log.warning("Error loading resource manager configuration " +
|
|
||||||
"[path=" + CONFIG_PATH + ", error=" + ioe + "].");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// load up any configured resource sets
|
// make sure there's a slash at the end of the URL
|
||||||
|
if (!resourceURL.endsWith("/")) {
|
||||||
|
resourceURL += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
URL rurl = null;
|
||||||
|
try {
|
||||||
|
rurl = new URL(resourceURL);
|
||||||
|
} catch (MalformedURLException mue) {
|
||||||
|
Log.warning("Invalid resource URL [url=" + resourceURL +
|
||||||
|
", error=" + mue + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
// load up our configuration
|
||||||
|
Properties config = loadConfig(rurl, configPath);
|
||||||
|
|
||||||
|
// resolve the configured resource sets
|
||||||
Enumeration names = config.propertyNames();
|
Enumeration names = config.propertyNames();
|
||||||
while (names.hasMoreElements()) {
|
while (names.hasMoreElements()) {
|
||||||
String key = (String)names.nextElement();
|
String key = (String)names.nextElement();
|
||||||
@@ -169,70 +147,166 @@ public class ResourceManager
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String setName = key.substring(RESOURCE_SET_PREFIX.length());
|
String setName = key.substring(RESOURCE_SET_PREFIX.length());
|
||||||
resolveResourceSet(appRoot, setName, config.getProperty(key));
|
resolveResourceSet(rurl, setName, config.getProperty(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads up the most recent version of the resource manager
|
||||||
|
* configuration.
|
||||||
|
*/
|
||||||
|
protected Properties loadConfig (URL resourceURL, String configPath)
|
||||||
|
{
|
||||||
|
Properties config = new Properties();
|
||||||
|
URL curl = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (configPath != null) {
|
||||||
|
curl = new URL(resourceURL, configPath);
|
||||||
|
config.load(curl.openStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (MalformedURLException mue) {
|
||||||
|
Log.warning("Unable to construct config URL " +
|
||||||
|
"[resourceURL=" + resourceURL +
|
||||||
|
", configPath=" + configPath +
|
||||||
|
", error=" + mue + "].");
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
// complain if some other error occurs
|
||||||
|
Log.warning("Error loading resource manager configuration " +
|
||||||
|
"[url=" + curl + ", error=" + ioe + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the cache directory for the specified resource set is
|
||||||
|
* created.
|
||||||
|
*
|
||||||
|
* @return true if the directory was successfully created (or was
|
||||||
|
* already there), false if we failed to create it.
|
||||||
|
*/
|
||||||
|
protected boolean createCacheDirectory (String setName)
|
||||||
|
{
|
||||||
|
// get the path to the top-level cache directory if we don't
|
||||||
|
// already have it
|
||||||
|
if (_cachePath == null) {
|
||||||
|
try {
|
||||||
|
String dir = System.getProperty("user.home");
|
||||||
|
_cachePath = (dir + File.separator +
|
||||||
|
CACHE_PATH + File.separator);
|
||||||
|
} catch (SecurityException se) {
|
||||||
|
Log.info("Can't obtain user.home system property. Probably " +
|
||||||
|
"won't be able to create our cache directory " +
|
||||||
|
"either. [error=" + se + "].");
|
||||||
|
_cachePath = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the main cache directory exists
|
||||||
|
if (!createDirectory(_cachePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the set-specific cache directory exists
|
||||||
|
return createDirectory(_cachePath + setName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the specified directory if it doesn't already exist.
|
||||||
|
*
|
||||||
|
* @return true if directory was created (or existed), false if not.
|
||||||
|
*/
|
||||||
|
protected boolean createDirectory (String path)
|
||||||
|
{
|
||||||
|
File cdir = new File(path);
|
||||||
|
if (cdir.exists()) {
|
||||||
|
if (!cdir.isDirectory()) {
|
||||||
|
Log.warning("Cache dir exists but isn't a directory?! " +
|
||||||
|
"[path=" + path + "].");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (!cdir.mkdir()) {
|
||||||
|
Log.warning("Unable to create cache dir. " +
|
||||||
|
"[path=" + path + "].");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the name of the bundle cache file given the name of the
|
||||||
|
* resource set to which it belongs and the relative path URL.
|
||||||
|
*/
|
||||||
|
protected String genCachePath (String setName, String resourcePath)
|
||||||
|
{
|
||||||
|
return _cachePath + setName + File.separator +
|
||||||
|
StringUtil.replace(resourcePath, "/", "-");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads up a resource set based on the supplied definition
|
* Loads up a resource set based on the supplied definition
|
||||||
* information.
|
* information.
|
||||||
*/
|
*/
|
||||||
protected void resolveResourceSet (
|
protected void resolveResourceSet (
|
||||||
String appRoot, String name, String definition)
|
URL resourceURL, String setName, String definition)
|
||||||
{
|
{
|
||||||
StringTokenizer tok =
|
StringTokenizer tok = new StringTokenizer(definition, ":");
|
||||||
new StringTokenizer(definition, File.pathSeparator);
|
|
||||||
ArrayList set = new ArrayList();
|
ArrayList set = new ArrayList();
|
||||||
|
|
||||||
while (tok.hasMoreTokens()) {
|
while (tok.hasMoreTokens()) {
|
||||||
// obtain the path and fully qualify it
|
|
||||||
String path = tok.nextToken().trim();
|
String path = tok.nextToken().trim();
|
||||||
if (!path.startsWith(File.separator)) {
|
URL burl = null;
|
||||||
path = appRoot + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File efile = new File(path);
|
burl = new URL(resourceURL, path);
|
||||||
|
Log.info("Resolving resource: " + burl);
|
||||||
|
|
||||||
// if this isn't a directory, we assume it's a jar file
|
// make sure the cache directory exists for this set
|
||||||
if (!efile.isDirectory()) {
|
createCacheDirectory(setName);
|
||||||
set.add(new ResourceBundle(efile));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// it is a directory, so we have to add all of its entries
|
// compute the path to the cache file for this bundle
|
||||||
// to the bundle
|
File cfile = new File(genCachePath(setName, path));
|
||||||
File[] efiles = efile.listFiles(new FilenameFilter() {
|
|
||||||
public boolean accept (File dir, String filename) {
|
|
||||||
// only worry about .jar files
|
|
||||||
return filename.endsWith(".jar");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (efiles == null) {
|
Log.info("Cached to " + cfile.getPath());
|
||||||
Log.warning("Failure enumerating jars in directory " +
|
|
||||||
"[path=" + path + "].");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// phew, we made it
|
// download the resource bundle from the specified URL
|
||||||
for (int i = 0; i < efiles.length; i++) {
|
InputStream in = burl.openStream();
|
||||||
set.add(new ResourceBundle(efiles[i]));
|
FileOutputStream out = new FileOutputStream(cfile);
|
||||||
}
|
|
||||||
|
// pipe the input stream into the output stream
|
||||||
|
StreamUtils.pipe(in, out);
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
// finally add this newly cached file to the set as a
|
||||||
|
// resource bundle
|
||||||
|
set.add(new ResourceBundle(cfile));
|
||||||
|
|
||||||
|
} catch (MalformedURLException mue) {
|
||||||
|
Log.warning("Unable to create URL for resource " +
|
||||||
|
"[set=" + setName + ", path=" + path +
|
||||||
|
", error=" + mue + "].");
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Log.warning("Error processing resource set entry " +
|
Log.warning("Error processing resource set entry " +
|
||||||
"[entry=" + path + ", error=" + ioe + "].");
|
"[url=" + burl + ", error=" + ioe + "].");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert our array list into an array and stick it in the table
|
// convert our array list into an array and stick it in the table
|
||||||
ResourceBundle[] setvec = new ResourceBundle[set.size()];
|
ResourceBundle[] setvec = new ResourceBundle[set.size()];
|
||||||
set.toArray(setvec);
|
set.toArray(setvec);
|
||||||
_sets.put(name, setvec);
|
_sets.put(setName, setvec);
|
||||||
|
|
||||||
// if this is our default resource bundle, keep a reference to it
|
// if this is our default resource bundle, keep a reference to it
|
||||||
if (DEFAULT_RESOURCE_SET.equals(name)) {
|
if (DEFAULT_RESOURCE_SET.equals(setName)) {
|
||||||
_default = setvec;
|
_default = setvec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,24 +364,22 @@ public class ResourceManager
|
|||||||
* them from the classpath. */
|
* them from the classpath. */
|
||||||
protected String _rootPath;
|
protected String _rootPath;
|
||||||
|
|
||||||
|
/** The path to our bundle cache directory. */
|
||||||
|
protected String _cachePath;
|
||||||
|
|
||||||
/** Our default resource set. */
|
/** Our default resource set. */
|
||||||
protected ResourceBundle[] _default = new ResourceBundle[0];
|
protected ResourceBundle[] _default = new ResourceBundle[0];
|
||||||
|
|
||||||
/** A table of our resource sets. */
|
/** A table of our resource sets. */
|
||||||
protected HashMap _sets = new HashMap();
|
protected HashMap _sets = new HashMap();
|
||||||
|
|
||||||
/** The system property from which we load our application root. */
|
|
||||||
protected static final String APP_ROOT_PROPERTY = "application.root";
|
|
||||||
|
|
||||||
/** The path to the resource manager config file (which will be loaded
|
|
||||||
* via the classloader). */
|
|
||||||
protected static final String CONFIG_PATH =
|
|
||||||
"config/resource/manager.properties";
|
|
||||||
|
|
||||||
/** The prefix of configuration entries that describe a resource
|
/** The prefix of configuration entries that describe a resource
|
||||||
* set. */
|
* set. */
|
||||||
protected static final String RESOURCE_SET_PREFIX = "resource.set.";
|
protected static final String RESOURCE_SET_PREFIX = "resource.set.";
|
||||||
|
|
||||||
/** The name of the default resource set. */
|
/** The name of the default resource set. */
|
||||||
protected static final String DEFAULT_RESOURCE_SET = "default";
|
protected static final String DEFAULT_RESOURCE_SET = "default";
|
||||||
|
|
||||||
|
/** The name of our resource bundle cache directory. */
|
||||||
|
protected static final String CACHE_PATH = ".naryarsrc";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user