JNLP - Extract my Music.jar to temp folder

Hi,

This is slightly cross posting with my other thread about mod playback in JavaSound, but I think a similar technique would be useful for other things as well.

First let me explain how my stuff is arranged

I have jars like this:

GameCode.jar
Music.jar
Sprites.jar

I can load the images from the Sprites.jar easy with the toolkit.

When I want to use the music from Music.jar, however, it uses RandomAccessFile to read the music in, something JNLP is obvioulsy not happy about doing straight from a .jar

Anyway, I need some technique to do the following:

On execution of the game code :

  1. Unpack the music.jar to the users temp webstart folder
  2. Load the music from the webstart temp folder

On exit:

  1. Clean up temp files

Is this possible over Webstart (e.g. will it still be click & go for the end user?)?

Has anyone developed a similar technique?

I’ve been scouring the web and a couple of books for 3 days solid now and see no way to accomplish this :frowning:

Any ideas?

If your music is inside the jar why don’t you just use the getResourceAsStream method from the ClassLoader class. I use this to load in my models just fine and they’re not images, so it shuold work just as well for your music.

Check out my Beta v0.2 example app for code on doing this

http://games.swizel-studios.com/tutorials.html

Regards,

Andy.

I think your problem is already solved, but in case you did want to do temp files, look at the java.io.File class more carefully - it has the ability to make OS-automatic-temp files (i.e.they are automatically deleted by the OS when app closes / system shutsdown / hard disk runs out of space - depending upon which args you supply + OS support)

Thanks for the replies.

Unfortunately, I can’t use streams, unless there’s some way to convert a stream to be compatible with a RandomAccessFile.

All my music code uses RandomAccessFile and due to the compressed nature of .jars and the fact RandomAccessFile requires a filesystem of some type to work, means I have decompress the files to a temp location first (unless i’m overlooking something).

I’ve looked into those blahblahblahh. It sounds like they do what I need.

A couple of problems though:

  1. How do I get the users temp JNLP directory (varies massively between clients)? (e.g. the location where i’m unjaring the music to.)
  2. Will the app run ok still on the client (e.g. are there restrictions about unjaring files to a temp folder)?

You could load the whole file into ram and access it there. (Since tracker modules are usually pretty small, it shouldn’t be a problem.)

For writing/deleting temp files you would need to sign it.

The RandomAccessFile stuff has almost the exact same methods as InputStream. Take a look at my example (as posted above), it loads into memory the whole MP3 file and then plays it from there, no need to write a temporary file to disk.

Andy.

Thanks a lot ap_kelly :slight_smile:

I went back over your stuff (I dunno what I was doing the 1st time I read it) and managed to hack it together to work with the music in the same .jar as the code (Finally ;D) , but it never manages to find the music when I put my music in a separate .jar file :frowning:

Any ideas where I might be going wrong on this?

I have my stuff organised in the following manner:

/MainClass
/Music/Mod00.mod

JNLP File


<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+"
  codebase="http://127.0.0.1">
<information>
  <title>Mod Player Test</title>
  <vendor>ME</vendor>
  <homepage href="http://127.0.0.1" />
  <description>Mod Player Multiple jar Test</description>
</information>
<offline-allowed/>
<security>
  <all-permissions/>
</security>
<resources>
  <j2se version="1.2+" />
    <jar href="ModTest2.jar" main="true"/>  
<jar href="Music.jar" />
</resources>
 <application-desc>      
        
</application-desc>

</jnlp>

Main Class


      DataInput dil = null;
    try
    {
      path = "Music/Mod00.mod";

      cl = this.getClass().getClassLoader();

        dil = new DataInputStream(cl.getSystemResourceAsStream(path));
      
      if (dil == null)
      {

        dil = new DataInputStream(new FileInputStream(path));
      }
      }


      catch (Exception e)
      {

        }


            try
            {
                  
                  outDev = new JavaSoundOutputDevice( new SS16LEAudioFormatConverter(), 44100, 1000 );

                  Module module = ModuleLoader.read(dil);
                  microMod = new MicroMod( module, outDev, new LinearResampler() );

                  /** Start the playback*/
                  outDev.start();
                  this.run();
            }
            catch( Exception e )
            {
                  e.printStackTrace();
            }
      }

Sorry for the long post, but here is my latest resource loading code. It seems to work fine for loading resources from any jar file. I too keep my resources in a different jar than my code.

Here goes!


/**
 * $Id$
 * 
 * (c) 2004 Swizel Studios.
 */
package com.swizel.utils;

import com.swizel.exceptions.ResourceNotFoundException;
import com.swizel.exceptions.ResourceNotLoadedException;

import java.awt.Image;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Arrays;
import java.util.Comparator;
import javax.swing.ImageIcon;

/**
 * Resource loader and managment class.
 * 
 * @author $author$
 * @version $revision$
 */
public class ResourceUtils  {

  private static final float ONE_HUNDRED_PERCENT = 100.0f;
  
  private static Hashtable<String, Object> resources = new Hashtable<String, Object>();
  private static int resourceCount = 0;
  private static int resourcesLoaded = 0;

  /**
   * Load the list of specified resources ready for use.
   * @param resourceFile The file that lists the resources we want to load.
   * @throws ResourceNotLoadedException If there is a problem loading one of the resources.
   * @throws FileNotFoundException If the specified resource file can't be found.
   * @throws IOException If the specified resource file can't be loaded.
   */
  public static void loadResources(String resourceFile) throws FileNotFoundException, IOException, ResourceNotLoadedException {
    Properties resourceProperties = new Properties();
    resourceProperties.load(loadInputStream(resourceFile));
    
    resourceCount = resourceProperties.size();
    resourcesLoaded = 0;

    Enumeration names = resourceProperties.propertyNames();
    String[] propNames = new String[resourceProperties.size()];

    int i=0;
    while (names.hasMoreElements()) {
      String name = (String) names.nextElement();
      propNames[i++] = name;
    }

    Arrays.sort(propNames, new ResourceComparator());
    
    for (int j=0; j< propNames.length; j++) {
      String propertyName = propNames[j].toLowerCase();
      String propertyValue = resourceProperties.getProperty(propertyName);

      try {
       Thread.sleep(10); 
      } catch (InterruptedException ie) {
      }
//      System.out.println("PropertyName  : " + propertyName);
//      System.out.println("PropertyValue : " + propertyValue);
      
      if ((propertyValue != null) && (propertyValue.trim().length() > 0)) {
        if (propertyName.indexOf("image") != -1) {
          Image img = loadImage(propertyValue);
          if (img != null) {
            resources.put(propertyName, img);
          } else {
            throw new ResourceNotLoadedException(propertyName);
          }
        } else {
          resources.put(propertyName, loadInputStream(propertyValue));         
        }
      } else {
        throw new ResourceNotLoadedException(propertyName + " has no value.");
      }
      resourcesLoaded ++;
    }    
  }

  /**
   * Calculate how many resources have been loaded.
   * @return The percentage of resources that have been loaded.
   */
  public static int getloadResourcesProgress() {
    return Math.round((ONE_HUNDRED_PERCENT / Math.max(1, resourceCount)) * resourcesLoaded);    
  }

  /**
   * Retrive a resource from the cache of resources.
   * @param resourceName The name of the resource as specified inside the resource properties file.
   * @throws ResourceNotFoundException If the specified resource can't be found.
   * @throws ResourceNotLoadedException If the specified resource can't be found, but loading of resources is still taking place.
   * @return The specified resource.
   */
  public static Object getResource(String resourceName) throws ResourceNotFoundException, ResourceNotLoadedException {
    String resourceNm = new String(resourceName.toLowerCase());
    if (resources.containsKey(resourceNm)) {
      return resources.get(resourceNm);
    } else {
      if (getloadResourcesProgress() != ONE_HUNDRED_PERCENT) {
        throw new ResourceNotLoadedException(resourceNm);                
      } else {
        throw new ResourceNotFoundException(resourceNm);        
      }
    }
  }

  /**
   * Remove a resource from the cache of resources, if the resource can't be found no further action takes place.
   * @param resourceName The name of the resource to be deleted.
   */
  public static void deleteResource(String resourceName) {
    String resourceNm = new String(resourceName.toLowerCase());
    if (resources.containsKey(resourceNm)) {
      resources.remove(resourceNm);
    }
  }

  /**
   * Load an image, this method can load resources from jar files
   * as well as from the local file system.
   * @param pPath The path to the images being loaded.
   * @return The loaded image.
   */
  public static Image loadImage(String pPath) {
    ClassLoader cl = ResourceUtils.class.getClassLoader();

    ImageIcon lvIcon = null;
    URL lvURL = cl.getResource(pPath);
    if (lvURL == null) {
      lvURL = ClassLoader.getSystemResource(pPath);
    }

    if (lvURL == null) {
      lvIcon = new ImageIcon(pPath);
    } else {
      lvIcon = new ImageIcon(lvURL);
    }
    
    if (lvIcon == null) {
      System.out.println("File not found : " + pPath);
      return null;
    } else {
      return lvIcon.getImage();
    }
  }

  /**
   * Load a binary stream, this method can load resources from jar files
   * as well as from the local file system.
   * @param pPath The path to the resource being loaded.
   * @return The InputStream of the resource.
   */
  public static InputStream loadInputStream(String pPath) {
    ClassLoader cl = ResourceUtils.class.getClassLoader();

    try {
      InputStream is = cl.getResourceAsStream(pPath);
      if (is == null) {
        is = ClassLoader.getSystemResourceAsStream(pPath);
      }
        
      if (is == null) {
        is = new FileInputStream(pPath);
      }    
      
      return is;
    } catch (FileNotFoundException fnfe) {
      System.out.println("File not found : " + pPath);
      return null;
    }
  }
  
} // class

/**
 * This class is used to place in sequence the resources 
 * loaded from a properties file.
 * 
 * Fonts are loaded first, followed by the Splash screen, 
 * finally all other resources are loaded alphabetically.
 *
 */
class ResourceComparator implements Comparator {

  /**
   * Compare two objects and order them according to the rules above.
   * @param o1 The first object being compared.
   * @param o2 The second object being compared.
   * @return -1, 0 or 1 depending on tthe order of these two objects.
   */
  public int compare(Object o1, Object o2) {
    String s1 = (String) o1;
    String s2 = (String) o2;
    if (s1.indexOf("font") != -1) {
      return -1;
    } else {
      if (s2.indexOf("font") != -1) {
        return 1;
      } else {
        if (s1.indexOf("splash") != -1) {
          return -1;
        } else {
          if (s2.indexOf("splash") != -1) {
            return 1;
          } else {
            return s1.compareTo(s2);
          }
        }
      }
    }
  }

  /**
   * Test to see if a given object is equal to another object.
   * @param obj The object being compared to this object.
   * @return true if the objects are equal, false otherwise.
   */
  public boolean equals(Object obj) {
    return true;
  }
    
} // class

/**
 * Changelog
 * ---------
 * $log$
 * 
 */


loadResources() does most of the work, I load my resources in a certain order (hence the comparator), they’re listed in a properties file. This code also allows me to load them in the background in a seperate Thread so I can have a splash screen and progress bar. Later when I need a resource, I just call getResource().

Regards,

Andy.

Thread.sleep(10); ? :slight_smile:

Woohoo, I finally got it working properly today :slight_smile:

Thanks so much for the help guys

I think the problem was I accidentally built the Music.jar into my main .jar and that screwed it up somehow. It was either that or the MANIFEST.MF was dodgy, anyway it’s working perfectly now.

Thanks again 8)

The sleep() command stops my loading loop from using 100% CPU. I like to be kind to the rest of the system, and the user doesn’t even notice the difference in loading times.

My loading loop also runs in a different Thread from my render loop so that I can show a progress bar on the screen.

Andy.

[quote]The sleep() command stops my loading loop from using 100% CPU.
[/quote]
If that’s all you’re after then Thread.yield is likely the better option since it gives up the remaining timeslice.