v75: android: filestream open does not work

Monkey Forums/Monkey Bug Reports/v75: android: filestream open does not work

AdamRedwoods(Posted 2013) [#1]
using a simple example, brl.FileStream.Open() does not find a PRE-EXISTING file in the data folder in android.

A test works on glfw using path "monkey://data/file.txt" but on Android the function "PathToFilePath" returns an empty string. After some digging, the file "targets/android/androidgame.java"
String PathToFilePath( String path ) {
...
return "";
}


should probably be
return PathToAssetPath(path);


...but even with this change, it does not work. Something is amiss.

bananas/mak/filetest works, but when a "test.txt" file is manually created and attempted to open, a file not found is result. (neither data/ nor internal/ made any difference. I also checked that the text file was included in the apk.)

NOTE:
i see that in the docs, "data" is not listed as acceptable. perhaps this is not a bug but just limited functionality?


UPDATE:
after serious digging, turns out that the file is compressed, thus why it needs to be read with an asset manager. A work-around would be to copy the file.
http://stackoverflow.com/questions/4789325/android-path-to-asset-txt-file


AdamRedwoods(Posted 2013) [#2]
here is my solution:
allow Android's CopyFile() to copy from the assets folder. this way the destination can go to monkey://internal. this would allow for filestream random access.

under CopyFile in native/filesystem.java
			if( !srcf.isFile() ) {
				AssetManager am = BBAndroidGame.AndroidGame()._activity.getAssets();
				InputStream inputStream = am.open(src);
				
				   try{
					  File dstf = file(dst);
					  if( dstf.exists() && (!dstf.isFile() || !dstf.delete()) ) return false;
						if( !dstf.createNewFile() ) return false;
						
					  OutputStream outputStream = new FileOutputStream(dstf);
					  byte buffer[] = new byte[1024];
					  int length = 0;

					  while((length=inputStream.read(buffer)) > 0) {
						outputStream.write(buffer,0,length);
					  }

					  outputStream.close();
					  inputStream.close();

					  return true;
				   }catch (IOException e) {
						 //Logging exception
				   }
				return false;
			}


why is this useful? for large database files. dictionary files. level editors. etc.
the side effect is that the file is still there unless specifically deleted (no destructor to delete file, but could be checked and deleted by user).

so this works using the above (and works for GLFW).
CopyFile( "monkey://data/test.txt", "monkey://internal/test.txt")
Local file:=FileStream.Open( "monkey://internal/test.txt","r" )
If Not file Print "not found"



mr_twister(Posted 2014) [#3]
Hi, I also ran into this issue while trying to read a file from the data folder. In fact, I need to read a lot of files from the data folder (custom format game files).

The problem is that FileStream is implemented in Android using RandomFileAccess, which provides the required flexibility (read/write "seekable" access to files). But in android you can only get an InputStream handle to a file in the data folder (via .getAssets().open()) which makes sense because they reside inside a read-only compressed "filesystem" (the APK) as you already pointed out. Nor Android nor Monkey will let you get a RandomFileAccess handler to anything there.

And it's because of this that the android implementation of Monkey's FileStream class uses BBGame.Game().OpenFile (path, fmode) (that in turns calls PathToFilePath() which -as you already discovered- returns an empty string for any file, stopping any attempt to access files through that route).

To overcome this problem -and since I only need to perform really basic operations- I created an "AndroidInputStream" class that uses BBAndroidGame.AndroidGame().OpenInputStream(path) (which calls getAssets().open() for "monkey://data" URIs) to open the files and exposes a few methods (name-compatible with the ones in FileStream) that perform the operations I need (read a byte, read to a data buffer and check the file length). Then I use the preprocessor conditional #If TARGET="android" to make android uses this custom class instead of a regular FileStream. It's certainly not THAT elegant but it should be faster than copying the files to the internal filesystem and then reading from there. It also doesn't require patching the filesystem API (I've hacked/patched/modified native code in monkey before and it's a real PITA because every time you update your Monkey distribution you'll need to replicate your changes :/).

If Monkey had a "FileInputStream" that only allows read operations this wouldn't be an issue I guess. But I never quite liked the amount of highly specialized classes in Java that only gets bigger because they also have generalized alternatives. I don't really want to see monkey having a bunch of InputStream, OutputStream, RandomAccessStream, FileInputStream, FileOutputStream, RandomAccessFileStream, TCPInputStream, TCPOutput-.... no, no no.