CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Common/File/AndroidStorage.cpp
Views: 1401
#include <inttypes.h>12#include "Common/File/AndroidStorage.h"3#include "Common/StringUtils.h"4#include "Common/Log.h"5#include "Common/TimeUtil.h"67#include "android/jni/app-android.h"89#if PPSSPP_PLATFORM(ANDROID) && !defined(__LIBRETRO__)1011static jmethodID openContentUri;12static jmethodID listContentUriDir;13static jmethodID contentUriCreateFile;14static jmethodID contentUriCreateDirectory;15static jmethodID contentUriCopyFile;16static jmethodID contentUriMoveFile;17static jmethodID contentUriRemoveFile;18static jmethodID contentUriRenameFileTo;19static jmethodID contentUriGetFileInfo;20static jmethodID contentUriFileExists;21static jmethodID contentUriGetFreeStorageSpace;22static jmethodID filePathGetFreeStorageSpace;23static jmethodID isExternalStoragePreservedLegacy;24static jmethodID computeRecursiveDirectorySize;2526static jobject g_nativeActivity;2728void Android_StorageSetNativeActivity(jobject nativeActivity) {29g_nativeActivity = nativeActivity;30}3132void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) {33openContentUri = env->GetMethodID(env->GetObjectClass(obj), "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I");34_dbg_assert_(openContentUri);35listContentUriDir = env->GetMethodID(env->GetObjectClass(obj), "listContentUriDir", "(Ljava/lang/String;)[Ljava/lang/String;");36_dbg_assert_(listContentUriDir);37contentUriCreateDirectory = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateDirectory", "(Ljava/lang/String;Ljava/lang/String;)I");38_dbg_assert_(contentUriCreateDirectory);39contentUriCreateFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateFile", "(Ljava/lang/String;Ljava/lang/String;)I");40_dbg_assert_(contentUriCreateFile);41contentUriCopyFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriCopyFile", "(Ljava/lang/String;Ljava/lang/String;)I");42_dbg_assert_(contentUriCopyFile);43contentUriRemoveFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriRemoveFile", "(Ljava/lang/String;)I");44_dbg_assert_(contentUriRemoveFile);45contentUriMoveFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriMoveFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I");46_dbg_assert_(contentUriMoveFile);47contentUriRenameFileTo = env->GetMethodID(env->GetObjectClass(obj), "contentUriRenameFileTo", "(Ljava/lang/String;Ljava/lang/String;)I");48_dbg_assert_(contentUriRenameFileTo);49contentUriGetFileInfo = env->GetMethodID(env->GetObjectClass(obj), "contentUriGetFileInfo", "(Ljava/lang/String;)Ljava/lang/String;");50_dbg_assert_(contentUriGetFileInfo);51contentUriFileExists = env->GetMethodID(env->GetObjectClass(obj), "contentUriFileExists", "(Ljava/lang/String;)Z");52_dbg_assert_(contentUriFileExists);53contentUriGetFreeStorageSpace = env->GetMethodID(env->GetObjectClass(obj), "contentUriGetFreeStorageSpace", "(Ljava/lang/String;)J");54_dbg_assert_(contentUriGetFreeStorageSpace);55filePathGetFreeStorageSpace = env->GetMethodID(env->GetObjectClass(obj), "filePathGetFreeStorageSpace", "(Ljava/lang/String;)J");56_dbg_assert_(filePathGetFreeStorageSpace);57isExternalStoragePreservedLegacy = env->GetMethodID(env->GetObjectClass(obj), "isExternalStoragePreservedLegacy", "()Z");58_dbg_assert_(isExternalStoragePreservedLegacy);59computeRecursiveDirectorySize = env->GetMethodID(env->GetObjectClass(obj), "computeRecursiveDirectorySize", "(Ljava/lang/String;)J");60_dbg_assert_(computeRecursiveDirectorySize);61}6263bool Android_IsContentUri(std::string_view filename) {64return startsWith(filename, "content://");65}6667int Android_OpenContentUriFd(std::string_view filename, Android_OpenContentUriMode mode) {68if (!g_nativeActivity) {69return -1;70}7172std::string fname(filename);73// PPSSPP adds an ending slash to directories before looking them up.74// TODO: Fix that in the caller (or don't call this for directories).75if (fname.back() == '/')76fname.pop_back();7778auto env = getEnv();79const char *modeStr = "";80switch (mode) {81case Android_OpenContentUriMode::READ: modeStr = "r"; break;82case Android_OpenContentUriMode::READ_WRITE: modeStr = "rw"; break;83case Android_OpenContentUriMode::READ_WRITE_TRUNCATE: modeStr = "rwt"; break;84}85jstring j_filename = env->NewStringUTF(fname.c_str());86jstring j_mode = env->NewStringUTF(modeStr);87int fd = env->CallIntMethod(g_nativeActivity, openContentUri, j_filename, j_mode);88return fd;89}9091StorageError Android_CreateDirectory(const std::string &rootTreeUri, const std::string &dirName) {92if (!g_nativeActivity) {93return StorageError::UNKNOWN;94}95auto env = getEnv();96jstring paramRoot = env->NewStringUTF(rootTreeUri.c_str());97jstring paramDirName = env->NewStringUTF(dirName.c_str());98return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriCreateDirectory, paramRoot, paramDirName));99}100101StorageError Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) {102if (!g_nativeActivity) {103return StorageError::UNKNOWN;104}105auto env = getEnv();106jstring paramRoot = env->NewStringUTF(parentTreeUri.c_str());107jstring paramFileName = env->NewStringUTF(fileName.c_str());108return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriCreateFile, paramRoot, paramFileName));109}110111StorageError Android_CopyFile(const std::string &fileUri, const std::string &destParentUri) {112if (!g_nativeActivity) {113return StorageError::UNKNOWN;114}115auto env = getEnv();116jstring paramFileName = env->NewStringUTF(fileUri.c_str());117jstring paramDestParentUri = env->NewStringUTF(destParentUri.c_str());118return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriCopyFile, paramFileName, paramDestParentUri));119}120121StorageError Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri) {122if (!g_nativeActivity) {123return StorageError::UNKNOWN;124}125auto env = getEnv();126jstring paramFileName = env->NewStringUTF(fileUri.c_str());127jstring paramSrcParentUri = env->NewStringUTF(srcParentUri.c_str());128jstring paramDestParentUri = env->NewStringUTF(destParentUri.c_str());129return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriMoveFile, paramFileName, paramSrcParentUri, paramDestParentUri));130}131132StorageError Android_RemoveFile(const std::string &fileUri) {133if (!g_nativeActivity) {134return StorageError::UNKNOWN;135}136auto env = getEnv();137jstring paramFileName = env->NewStringUTF(fileUri.c_str());138return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriRemoveFile, paramFileName));139}140141StorageError Android_RenameFileTo(const std::string &fileUri, const std::string &newName) {142if (!g_nativeActivity) {143return StorageError::UNKNOWN;144}145auto env = getEnv();146jstring paramFileUri = env->NewStringUTF(fileUri.c_str());147jstring paramNewName = env->NewStringUTF(newName.c_str());148return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriRenameFileTo, paramFileUri, paramNewName));149}150151// NOTE: Does not set fullName - you're supposed to already know it.152static bool ParseFileInfo(const std::string &line, File::FileInfo *fileInfo) {153std::vector<std::string> parts;154SplitString(line, '|', parts);155if (parts.size() != 4) {156ERROR_LOG(Log::FileSystem, "Bad format (1): %s", line.c_str());157return false;158}159fileInfo->name = std::string(parts[2]);160fileInfo->isDirectory = parts[0][0] == 'D';161fileInfo->exists = true;162if (1 != sscanf(parts[1].c_str(), "%" PRIu64, &fileInfo->size)) {163ERROR_LOG(Log::FileSystem, "Bad format (2): %s", line.c_str());164return false;165}166fileInfo->isWritable = true; // TODO: Should be passed as part of the string.167// TODO: For read-only mappings, reflect that here, similarly as with isWritable.168// Directories are normally executable (0111) which means they're traversable.169fileInfo->access = fileInfo->isDirectory ? 0777 : 0666;170171uint64_t lastModifiedMs = 0;172if (1 != sscanf(parts[3].c_str(), "%" PRIu64, &lastModifiedMs)) {173ERROR_LOG(Log::FileSystem, "Bad format (3): %s", line.c_str());174return false;175}176177// Convert from milliseconds178uint32_t lastModified = lastModifiedMs / 1000;179180// We don't have better information, so let's just spam lastModified into all the date/time fields.181fileInfo->mtime = lastModified;182fileInfo->ctime = lastModified;183fileInfo->atime = lastModified;184return true;185}186187bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *fileInfo) {188if (!g_nativeActivity) {189return false;190}191auto env = getEnv();192jstring paramFileUri = env->NewStringUTF(fileUri.c_str());193194jstring str = (jstring)env->CallObjectMethod(g_nativeActivity, contentUriGetFileInfo, paramFileUri);195if (!str) {196return false;197}198const char *charArray = env->GetStringUTFChars(str, 0);199bool retval = ParseFileInfo(std::string(charArray), fileInfo);200fileInfo->fullName = Path(fileUri);201202env->DeleteLocalRef(str);203return retval && fileInfo->exists;204}205206bool Android_FileExists(const std::string &fileUri) {207if (!g_nativeActivity) {208return false;209}210auto env = getEnv();211jstring paramFileUri = env->NewStringUTF(fileUri.c_str());212bool exists = env->CallBooleanMethod(g_nativeActivity, contentUriFileExists, paramFileUri);213return exists;214}215216std::vector<File::FileInfo> Android_ListContentUri(const std::string &path, bool *exists) {217if (!g_nativeActivity) {218*exists = false;219return std::vector<File::FileInfo>();220}221auto env = getEnv();222*exists = true;223224double start = time_now_d();225226jstring param = env->NewStringUTF(path.c_str());227jobject retval = env->CallObjectMethod(g_nativeActivity, listContentUriDir, param);228229jobjectArray fileList = (jobjectArray)retval;230std::vector<File::FileInfo> items;231int size = env->GetArrayLength(fileList);232for (int i = 0; i < size; i++) {233jstring str = (jstring)env->GetObjectArrayElement(fileList, i);234const char *charArray = env->GetStringUTFChars(str, 0);235if (charArray) { // paranoia236std::string line = charArray;237File::FileInfo info{};238if (line == "X") {239// Indicates an exception thrown, path doesn't exist.240*exists = false;241} else if (ParseFileInfo(line, &info)) {242// We can just reconstruct the URI.243info.fullName = Path(path) / info.name;244items.push_back(info);245}246}247env->ReleaseStringUTFChars(str, charArray);248env->DeleteLocalRef(str);249}250env->DeleteLocalRef(fileList);251252double elapsed = time_now_d() - start;253double threshold = 0.1;254if (elapsed >= threshold) {255INFO_LOG(Log::FileSystem, "Listing directory on content URI '%s' took %0.3f s (%d files, log threshold = %0.3f)", path.c_str(), elapsed, (int)items.size(), threshold);256}257return items;258}259260int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) {261if (!g_nativeActivity) {262return false;263}264auto env = getEnv();265266jstring param = env->NewStringUTF(uri.c_str());267return env->CallLongMethod(g_nativeActivity, contentUriGetFreeStorageSpace, param);268}269270int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) {271if (!g_nativeActivity) {272return false;273}274auto env = getEnv();275276jstring param = env->NewStringUTF(filePath.c_str());277return env->CallLongMethod(g_nativeActivity, filePathGetFreeStorageSpace, param);278}279280int64_t Android_ComputeRecursiveDirectorySize(const std::string &uri) {281if (!g_nativeActivity) {282return false;283}284auto env = getEnv();285286jstring param = env->NewStringUTF(uri.c_str());287288double start = time_now_d();289int64_t size = env->CallLongMethod(g_nativeActivity, computeRecursiveDirectorySize, param);290double elapsed = time_now_d() - start;291292INFO_LOG(Log::IO, "ComputeRecursiveDirectorySize(%s) in %0.3f s", uri.c_str(), elapsed);293return size;294}295296bool Android_IsExternalStoragePreservedLegacy() {297if (!g_nativeActivity) {298return false;299}300auto env = getEnv();301return env->CallBooleanMethod(g_nativeActivity, isExternalStoragePreservedLegacy);302}303304const char *Android_ErrorToString(StorageError error) {305switch (error) {306case StorageError::SUCCESS: return "SUCCESS";307case StorageError::UNKNOWN: return "UNKNOWN";308case StorageError::NOT_FOUND: return "NOT_FOUND";309case StorageError::DISK_FULL: return "DISK_FULL";310case StorageError::ALREADY_EXISTS: return "ALREADY_EXISTS";311default: return "(UNKNOWN)";312}313}314315#else316317// These strings should never appear except on Android.318// Very hacky.319std::string g_extFilesDir = "(IF YOU SEE THIS THERE'S A BUG)";320std::string g_externalDir = "(IF YOU SEE THIS THERE'S A BUG (2))";321std::string g_nativeLibDir = "(IF YOU SEE THIS THERE'S A BUG (3))";322323#endif324325326