Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AAssetDir_getNextFileName #603

Closed
Nielsbishere opened this issue Jan 2, 2019 · 14 comments
Closed

AAssetDir_getNextFileName #603

Nielsbishere opened this issue Jan 2, 2019 · 14 comments

Comments

@Nielsbishere
Copy link

Nielsbishere commented Jan 2, 2019

The spec shows that there is only a function for iterating over files, not directories. This either means that the developers using the NDK can't query the asset manager for directories; at least not on my device. Or that it only works on some devices (not on 8.0.0 s7 edge). I'd recommend adding AAssetDir_getNextFolderName, requiring AAssetDir_getNextFileName to display directories as well as assets or providing a function to loop over all files in the assets dir (nested).

	AAssetManager *assetManager = ((android_app*)param)->activity->assetManager;
	AAssetDir *dir = AAssetManager_openDir(assetManager, path);

	Log::println(String((void*)dir));

	const char *name = nullptr;

	while ((name = AAssetDir_getNextFileName(dir)) != nullptr) {
		Log::println(String("res/") + name);
	}

	AAssetDir_close(dir);

Doesn't do anything for path = "" or "res" (assets/res), but when I pass in a directory with files "res/shaders" (assets/res/shaders) it does work.

EDIT: https://android.googlesource.com/platform/frameworks/base.git/+/1cadc07dd1e3711fb1e57548038e3026682c5ef8/native/android/asset_manager.cpp#135

@Nielsbishere
Copy link
Author

Nielsbishere commented Jan 2, 2019

AssetDir supports this, why is this removed by AAssetManager?
EDIT: This implementation stores AssetDir into the struct at offset 0, is this always the case (or does it vary per Android version/device), because then I can obtain it directly?

@ggfan
Copy link
Contributor

ggfan commented Jan 9, 2019

this needs some digging into the history and not sure what will be outcome either:

// Find the next regular file; explicitly don't report directories even if the
// underlying implementation changes to report them.  At that point we can add
// a more general iterator to this native interface set if appropriate.
   while ((index < max) && (assetDir->mAssetDir->getFileType(index) != kFileTypeRegular)) {
   index++;
  }

How about post this question to https://groups.google.com/forum/#!forum/android-ndk?

@ggfan
Copy link
Contributor

ggfan commented Jan 10, 2019

closing down this one as this seems to be work as expected ( designed that way ):

  • only return files
  • give the full path for asset, it will work ( open, read file )

This is one of many things that Native side does not have the same level of services, I like to pass this: if you really need it, please raise a strong voice on the link above.

@ggfan ggfan closed this as completed Jan 10, 2019
@Nielsbishere
Copy link
Author

@Nielsbishere
Copy link
Author

Still no response after 3 months.

@MulattoKid
Copy link

Is there any way to circumvent this lack of functionality? The alternative in my case is to have all files in one folder, and that's a highly unattractive solution...

@Nielsbishere
Copy link
Author

The only way I've heard about is interfacing with the SDK or possibly finding the .apk on disk and opening it as a zip; which are both very unnecessary imo. This is still an issue that I've not fixed yet, as adding a Java backend just for this seems a bit much.

@ggfan
Copy link
Contributor

ggfan commented Sep 5, 2019

Apologies for the delay, may you upvote or comment on this one: https://issuetracker.google.com/issues/140538113

@marcel303
Copy link

marcel303 commented May 15, 2020

As a workaround, you could use AssetManager.list() on the Java-side, which does NOT filter regular files vs directories. See here for a more extensive usage, which uses list_assets to recursively copy the assets folder to the filesystem. https://github.com/marcel303/framework/blob/master/users/marcel/ovr-cubes1/main3.cpp

Hope this helps someone out!

static std::vector<std::string> list_assets(android_app * app, const char * asset_path)
{
	std::vector<std::string> result;

	JNIEnv * env = nullptr;
	app->activity->vm->AttachCurrentThread(&env, nullptr);

	auto context_object = app->activity->clazz;
	auto getAssets_method = env->GetMethodID(env->GetObjectClass(context_object), "getAssets", "()Landroid/content/res/AssetManager;");
	auto assetManager_object = env->CallObjectMethod(context_object, getAssets_method);
	auto list_method = env->GetMethodID(env->GetObjectClass(assetManager_object), "list", "(Ljava/lang/String;)[Ljava/lang/String;");

	jstring path_object = env->NewStringUTF(asset_path);

	auto files_object = (jobjectArray)env->CallObjectMethod(assetManager_object, list_method, path_object);

	env->DeleteLocalRef(path_object);

	auto length = env->GetArrayLength(files_object);

	for (int i = 0; i < length; i++)
	{
		jstring jstr = (jstring)env->GetObjectArrayElement(files_object, i);

		const char * filename = env->GetStringUTFChars(jstr, nullptr);

		if (filename != nullptr)
		{
			result.push_back(filename);
			env->ReleaseStringUTFChars(jstr, filename);
		}

		env->DeleteLocalRef(jstr);
	}

	app->activity->vm->DetachCurrentThread();

	return result;
}

@Nielsbishere
Copy link
Author

This is disgusting but I love that it exists, thanks; it is useful. I have decided to go with storing only the files and having an init file that links them all together by making virtual directories and parenting them in there. I know it's not perfect, but to make it work on linux I'd need to do something along those lines anyways.

@marcel303
Copy link

marcel303 commented May 15, 2020

Yeah, I agree. The JVM stuff makes the code seem rather horrendous. Also, if there was a native implementation like say AAssetDir_getNextDirName or similar, I'm sure it would be much faster as well. But.. gotta say.. the assets sync completes in no time at all for the few directories and the some 100+ files I sync at startup.. so I'm not concerned about the performance right now.

You mean you have a file that's like an index file describing all of your files? I was contemplating something similar, but different, where I'd index all of the directories inside the merged assets folder. I'm using Gradle to manage the build, and was thinking to add a task for this. Didn't get it to work though. I haven't done any Gradle scripting at all, and to be honest I try to stay clear from it as much as possible. So I ended up implementing the Java way. Hopefully in a few days I will forget this exists, and it just becomes an abstraction. ;-)

@Nielsbishere
Copy link
Author

Basically just hashing the name from C++ through the asset baker (& c++ project) and doing that for every file and then linking them together by just giving them a parent, file & folder hint that allows me to iterate through them; that will be the .root file in binary format. I don't really need their names anyways, just an input string that maps to a file handle. But if it works and it's abstracted away, that's pretty good as well, won't work for my new abstraction tho. Nice to see there is a 'javaless' solution; although unfortunately not through the ndk

@sogaiu
Copy link

sogaiu commented Feb 27, 2022

@marcel303 Thanks a lot for your code -- it was indeed helpful :)

On a side note, I think the relevant link has changed slightly and is now: https://github.com/marcel303/framework/blob/master/users/marcel/ovr-cubes/main3.cpp

@XX
Copy link

XX commented Jul 25, 2022

This is my asset dirs copy implementation for Rust with use jni,ndk and ndk-glue crates:

use std::{
    error::Error,
    ffi::{CStr, CString},
    fs, io,
    path::{Path, PathBuf},
};

use jni::{objects::JObject, JNIEnv, JavaVM};
use ndk::asset::Asset;

pub type CopyResult<T> = Result<T, Box<dyn Error>>;

pub fn copy(asset_dirs: impl IntoIterator<Item = impl AsRef<Path>>, destination: impl AsRef<Path>) -> CopyResult<()> {
    // Create a VM for executing Java calls
    let ctx = ndk_context::android_context();
    let vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }?;
    let env = vm.attach_current_thread()?;

    // Query the Asset Manager
    let asset_manager = env
        .call_method(
            ctx.context().cast(),
            "getAssets",
            "()Landroid/content/res/AssetManager;",
            &[],
        )?
        .l()?;

    // Copy assets
    for asset_dir in asset_dirs {
        copy_recursively(
            *env,
            asset_manager,
            asset_dir.as_ref().to_path_buf(),
            destination.as_ref().join(asset_dir),
        )?;
    }

    Ok(())
}

fn copy_recursively(
    env: JNIEnv,
    asset_manager: JObject,
    asset_dir: PathBuf,
    destination_dir: PathBuf,
) -> CopyResult<()> {
    for asset_filename in list(env, asset_manager, &asset_dir)? {
        let asset_path = asset_dir.join(&asset_filename);
        if let Some(asset) = open_asset(&CString::new(asset_path.to_string_lossy().as_ref())?) {
            copy_asset(asset, asset_filename, &destination_dir)?;
        } else {
            copy_recursively(env, asset_manager, asset_path, destination_dir.join(asset_filename))?;
        }
    }
    Ok(())
}

fn list(env: JNIEnv, asset_manager: JObject, asset_dir: &Path) -> CopyResult<Vec<String>> {
    let asset_array = env
        .call_method(asset_manager, "list", "(Ljava/lang/String;)[Ljava/lang/String;", &[env
            .new_string(asset_dir.to_string_lossy())?
            .into()])?
        .l()?
        .into_inner();

    let mut assets = Vec::new();
    for index in 0..env.get_array_length(asset_array)? {
        let asset: String = env
            .get_string(env.get_object_array_element(asset_array, index)?.into())?
            .into();
        assets.push(asset);
    }

    Ok(assets)
}

fn open_asset(asset_path: &CStr) -> Option<Asset> {
    #[allow(deprecated)]
    ndk_glue::native_activity().asset_manager().open(asset_path)
}

fn copy_asset(mut asset: Asset, filename: impl AsRef<Path>, destination_dir: impl AsRef<Path>) -> CopyResult<()> {
    fs::create_dir_all(destination_dir.as_ref())?;
    let mut file = fs::File::options()
        .create(true)
        .write(true)
        .open(destination_dir.as_ref().join(filename))?;

    io::copy(&mut asset, &mut file)?;
    Ok(())
}

source

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants