JmeSystem.getStorageFolder() updates

As you probably know, well maybe not, JmeSystem has a static method called getStorageFolder() that returns a platform dependent storage location for files. Namely game data, screenshots, etc.



On Desktop, this method returns the .jme3 subdirectory inside the directory defined by the java property user.home.



On Android, it returns the directory /mnt/sdcard/Android/data//files. This directory is managed by Android, meaning that it is sort of an allocated directory for storing files related to the application and when the application is removed from the device, this directory is also removed. The physical location of this directory is device specific. On some devices, it is located physically on the removable SDCard. On other devices, it is located within the internal memory of the device.



One advantage of using this folder is that when a PC is connected to the Android device, the PC can browse to this directory and retrieve the files. This is a convenient way to transfer screenshots to a PC. However, the down side of using this directory is that there are cases where the folder is not accessible by the application. For instance, if the Android device is connected to a PC, say for charging, and the device is configured as ā€œMass Storageā€ when connected to a PC over USB, then the application no longer has access to the directory because the PC is mounted to it. Other configurations, like ā€œMedia Deviceā€ allows for the PC to transfer files from the device but still allows the Android application to access the folder as well.



While this is a nice feature for transferring files between the device and a PC, it also makes it not a good place to store internal game data, like preferences or other data that should not be accessible by the user or a connected PC. This data should also always be allowed to save / load even if the user takes out the SDCard or is connected to a PC over USB.



For this reason, a new variant of the JmeSystem.getStorageFolder() is being created to specify which kind of storage folder you wish to retrieve. This new method is: JmeSystem.getStorageFolder(JmeSystem.StorageFolderType type)



You will notice that this is inside the class JmeSystem so that it is available for both PC and Android (and future iOS). This means that games need only call JmeSystem.getStorageFolder(type) and the engine will return the appropriate directory based on the platform the game is running on.



There are initially 2 types of folders in the JmeSystem.StorageFolderType enum: Internal and External. There may be more added later, weā€™ll see.



The purpose of Internal is to define a directory that is to be used for storing data that should always be available, but not accessible by anything other than the application (ie. game preferences).



The purpose of External is to define a directory that is public (or shared) by the application as well as the outside world (ie. Screenshots or videos).



For Desktop systems, there really isnā€™t an issue with the folder not being available, so for now, both the Internal and External folder types will return the same directory as is used today with getStorageFolder(). This is the .jme3 subdirectory within the user.home directory. This may change in the future if a different Internal directory is identified for Desktop systems, but for now, both Internal and External will return the same directory.



For Android, 2 different directories will be returned based on the type requested. The External folder type will return the same directory as getStorageFolder() does today (/mnt/sdcard/Android/data//files). It is a directory that is accessible by users via the Files application on the device, or by a PC. The Internal folder type will return the Android folder automatically created for private app data that is only accessible by the application itself (/data/data//app_).



Below are some links related to the Internal and External storage folders in Android.

Android Storage Options

Using the Internal Storage

Using the External Storage



This new feature will be committed very soon.

7 Likes

Cool, thanks for putting the time into this!

Nice update, just learned something really cool ^^

Indeed, was just looking into figuring out how to store game data, for both pc/android :slight_smile:

really nicely written post, thx :slight_smile:

Weeeeeee!!! -

SaveGame has just been modified to include some new methods to allow users to specify the folder type to load from and save to.

http://code.google.com/p/jmonkeyengine/source/detail?r=10022

Should be available in the next nightly.



The new methods include an extra parameter so you can specify which JmeSystem.StorageFolderType to use.

1 Like

Good work Eric, thanks :wink: !

Hi folks, just tried this on the latest nightly (2012-12-06), but it appears to be failing on android atm, is this expected? or am I doing something stupid?



Tested on: asus 300, android 4.1



code snippet:

[java]

public void save(List<ScoreData> sd, JmeSystem.StorageFolderType type) {

File dataStore = JmeSystem.getStorageFolder(type);

String path = dataStore.getAbsolutePath();

if (!dataStore.exists()){

Log.e(TAG,"Data store does not exist: " + path+" creatingā€¦");

if (dataStore.mkdirs()){

Log.i(TAG,"Data store created");

}

}



if (dataStore.exists()){

Log.i(TAG, "Data store does exist"+path);

File t = new File(path+File.separator+tableName+".scd");

if (t!=null){

Log.i(TAG, "HichScore File does exist, path="+path+File.separator+tableName+".scd isFile:" +t.isFile() +" writeable:"+t.canWrite());

}

t=null;

}

else{

Log.e(TAG,"Data store does not exist: " + path);

}



BufferedWriter writer = null;

try{

writer = new BufferedWriter(new FileWriter(path+File.separator+tableName+".scd"));

writer.write("");

for (ScoreData s:sd){

writer.append(s.toCSV());

writer.append("n");

}

}

catch(Exception e){

Log.e(TAG,"Failed to write highscore data! using storeType:["+type+"] path: [" +path+ "] Exception message:" + e.getMessage());



if (!type.equals(JmeSystem.StorageFolderType.External)){

Log.e(TAG,"Retrying using External Storage");

save(sd,JmeSystem.StorageFolderType.External);

}

else

Log.e(TAG,"Giving up, unable to store data at external location");

}

finally{

if (writer != null)

try{writer.close();}

catch(Exception e){

Log.e(TAG,"Failed to close highscore data! "+e.getMessage());

}

}

}

[/java]



Log Snippet:

I/JmeSystem( 7605): INFO JmeSystemDelegate 1:25:46 PM Storage Folder Path: /.jme3

I/System.out( 7605): ScoreManagerā€“>Data store does not exist: /.jme3 creatingā€¦

I/System.out( 7605): ScoreManagerā€“>Data store does not exist: /.jme3

I/System.out( 7605): ScoreManagerā€“>Failed to write highscore data! using storeType:[Internal] path: [/.jme3] Exception message:/.jme3/test.scd: open failed: ENOENT (No such file or directory)

I/System.out( 7605): ScoreManagerā€“>Retrying using External Storage

I/JmeSystem( 7605): INFO JmeSystemDelegate 1:25:46 PM Storage Folder Path: /.jme3

I/System.out( 7605): ScoreManagerā€“>Data store does not exist: /.jme3 creatingā€¦

I/System.out( 7605): ScoreManagerā€“>Data store does not exist: /.jme3

I/System.out( 7605): ScoreManagerā€“>Failed to write highscore data! using storeType:[External] path: [/.jme3] Exception message:/.jme3/test.scd: open failed: ENOENT (No such file or directory)

I/System.out( 7605): ScoreManagerā€“>Giving up, unable to store data at external location

Thereā€™s definitely something weird going on. getStorageFolder on Android has nothing with ā€œ.jme3ā€ in the path, only Desktop does.



Are you sure the log was from an Android run?



The only way I can see that ā€œ.jme3ā€ is in the path is if you are running the Desktop version which includes the getStorageFolder from JmeSystemDelegate instead of JmeAndroidSystem.

yeah, it was from the android logs, you can ā€˜tell by the logging format aboveā€™ so to speak.



If there any additional info I can dig up for u,holler.

Are you using Eclipse instead of the SDK?



Honestly, ā€œ.jme3ā€ should only be in the path returned by getStorageFolder when you are using the Desktop version of JmeSystemDelegate.

I just put the following logic (based on your above) in my project:

[java]

File dataStore = JmeSystem.getStorageFolder(JmeSystem.StorageFolderType.Internal);

String path = dataStore.getAbsolutePath();

if (!dataStore.exists()){

logger.log(Level.INFO, "Data store does not exist: {0} creatingā€¦", path);

if (dataStore.mkdirs()){

logger.log(Level.INFO, "Data store created: {0}", path);

}

} else {

logger.log(Level.INFO, "Data store path: {0}", path);

}

[/java]



Log out is:

[java]

12-08 18:22:34.719 12478 12496 I JmeSystem: INFO JmeAndroidSystem 6:22:34 PM Base Storage Folder Path: /data/data/com.interstatewebgroup.rcracer/app_

12-08 18:22:34.719 12478 12496 I Main : INFO Main 6:22:34 PM Data store path: /data/data/com.interstatewebgroup.rcracer/app_

[/java]



Is it possible you are somehow not including the jME3-android.jar file in your project?

Actually, it looks like you are definetly not running the android version of getStorageFolder.



If you look at the source code for JmeSystemDelegate, the line that prints to the log is:

[java]

logger.log(Level.INFO, ā€œStorage Folder Path: {0}ā€, storageFolder.getAbsolutePath());

[/java]



If you look at the source code for JmeAndroidSystem, the line that prints to the log is:

[java]

logger.log(Level.INFO, ā€œBase Storage Folder Path: {0}ā€, storageFolder.getAbsolutePath());

[/java]



Your log does not include the word ā€œBaseā€ in the log print so you must not be executing the Android version of getStorageFolder. Somehow, your project is not configured right.

I suspect your right, this is first time iā€™ve grabbed a nigtly build, didnā€™t realize I needed pull libraries from opt.



Currently Iā€™m having some other issues with Nifty, which doesnā€™t appear to passing events correctly, making it difficult if not impossible to get the game to that point to validate, now that Iā€™m including the android jar instead of the desktop jar.



Hopefully will have more info shortlyā€¦

update:

I donā€™t know, after playing around some more, from what I can tell its always pulling in jME3-android.jar into the mobile/libs/ directory (even if i donā€™t have it actually included in libraries section, Iā€™m guessing the android build pulls it in automaticallyā€¦ug ok,think I just figured it outā€¦



while Iā€™m switching out the rc2 jars for the nigtly in the UI, it looks like these, or at least some of these are not getting pulled in, i.e. its still pulling in the rc2 release version of jME3-android.jar. Just figured this out by comparing the size of the jar in nightly vs the one that keeps popping into the mobile/lib directory, and its not matchingā€¦so yeahā€¦Iā€™m guessing atm its still pulling in the release version.



will muck around some more tomorrow, probably I need to update a build.xml somewhere.

The mobile-impl.xml file in nbproject automatically includes the android jar files during build for Android.



[java]

<echo>Adding libraries for android.</echo>

<copy todir="mobile/libs" flatten="true">

<path>

<pathelement path="${libs.android-base.classpath}"/>

</path>

</copy>

[/java]



Either change this to your jar from the nightly or override the target in build.xml.



I use the following patch file for the mobile-impl.xml file. I added a new global library in NetBeans called android-base-modified that includes the jar files from my svn build.



[java]

diff --git a/nbproject/mobile-impl.xml b/nbproject/mobile-impl.xml

index b150d81ā€¦e91c091 100644

ā€” a/nbproject/mobile-impl.xml

+++ b/nbproject/mobile-impl.xml

@@ -51,7 +51,10 @@

<echo>Adding libraries for android.</echo>

<copy todir="mobile/libs" flatten="true">

<path>

+<!ā€“

<pathelement path="${libs.android-base.classpath}"/>

Ā±->

  •            &lt;pathelement path=&quot;${libs.android-base-modified.classpath}&quot;/&gt;<br />
    

</path>

</copy>

<antcall target="-unzip-bullet-libs"/>

[/java]

1 Like

That did it, many thanx :slight_smile:



I/JmeSystem(20527): INFO JmeAndroidSystem 9:41:58 AM Base Storage Folder Path: /data/data/com.hpg.pinball.android/app_