Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/File/AndroidStorage.cpp
5654 views
1
#include <inttypes.h>
2
3
#include "Common/File/AndroidStorage.h"
4
#include "Common/StringUtils.h"
5
#include "Common/Log.h"
6
#include "Common/TimeUtil.h"
7
#include "Common/System/System.h"
8
9
#include "android/jni/app-android.h"
10
#include "Common/Thread/ThreadUtil.h"
11
12
#if PPSSPP_PLATFORM(ANDROID) && !defined(__LIBRETRO__)
13
14
static jmethodID openContentUri;
15
static jmethodID listContentUriDir;
16
static jmethodID contentUriCreateFile;
17
static jmethodID contentUriCreateDirectory;
18
static jmethodID contentUriCopyFile;
19
static jmethodID contentUriMoveFile;
20
static jmethodID contentUriRemoveFile;
21
static jmethodID contentUriRenameFileTo;
22
static jmethodID contentUriGetFileInfo;
23
static jmethodID contentUriFileExists;
24
static jmethodID contentUriGetFreeStorageSpace;
25
static jmethodID filePathGetFreeStorageSpace;
26
static jmethodID isExternalStoragePreservedLegacy;
27
static jmethodID computeRecursiveDirectorySize;
28
29
static jobject g_nativeActivity;
30
static jclass g_classContentUri;
31
32
void Android_StorageSetActivity(jobject nativeActivity) {
33
g_nativeActivity = nativeActivity;
34
}
35
36
void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) {
37
jclass localClass = env->FindClass("org/ppsspp/ppsspp/ContentUri");
38
_dbg_assert_(localClass);
39
40
openContentUri = env->GetStaticMethodID(localClass, "openContentUri", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)I");
41
_dbg_assert_(openContentUri);
42
listContentUriDir = env->GetStaticMethodID(localClass, "listContentUriDir", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
43
_dbg_assert_(listContentUriDir);
44
contentUriCreateDirectory = env->GetStaticMethodID(localClass, "contentUriCreateDirectory", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)I");
45
_dbg_assert_(contentUriCreateDirectory);
46
contentUriCreateFile = env->GetStaticMethodID(localClass, "contentUriCreateFile", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)I");
47
_dbg_assert_(contentUriCreateFile);
48
contentUriCopyFile = env->GetStaticMethodID(localClass, "contentUriCopyFile", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)I");
49
_dbg_assert_(contentUriCopyFile);
50
contentUriRemoveFile = env->GetStaticMethodID(localClass, "contentUriRemoveFile", "(Landroid/app/Activity;Ljava/lang/String;)I");
51
_dbg_assert_(contentUriRemoveFile);
52
contentUriMoveFile = env->GetStaticMethodID(localClass, "contentUriMoveFile", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I");
53
_dbg_assert_(contentUriMoveFile);
54
contentUriRenameFileTo = env->GetStaticMethodID(localClass, "contentUriRenameFileTo", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)I");
55
_dbg_assert_(contentUriRenameFileTo);
56
contentUriGetFileInfo = env->GetStaticMethodID(localClass, "contentUriGetFileInfo", "(Landroid/app/Activity;Ljava/lang/String;)Ljava/lang/String;");
57
_dbg_assert_(contentUriGetFileInfo);
58
contentUriFileExists = env->GetStaticMethodID(localClass, "contentUriFileExists", "(Landroid/app/Activity;Ljava/lang/String;)Z");
59
_dbg_assert_(contentUriFileExists);
60
contentUriGetFreeStorageSpace = env->GetStaticMethodID(localClass, "contentUriGetFreeStorageSpace", "(Landroid/app/Activity;Ljava/lang/String;)J");
61
_dbg_assert_(contentUriGetFreeStorageSpace);
62
filePathGetFreeStorageSpace = env->GetStaticMethodID(localClass, "filePathGetFreeStorageSpace", "(Landroid/app/Activity;Ljava/lang/String;)J");
63
_dbg_assert_(filePathGetFreeStorageSpace);
64
isExternalStoragePreservedLegacy = env->GetStaticMethodID(localClass, "isExternalStoragePreservedLegacy", "()Z"); // doesn't need an activity
65
_dbg_assert_(isExternalStoragePreservedLegacy);
66
computeRecursiveDirectorySize = env->GetStaticMethodID(localClass, "computeRecursiveDirectorySize", "(Landroid/app/Activity;Ljava/lang/String;)J");
67
_dbg_assert_(computeRecursiveDirectorySize);
68
69
g_classContentUri = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
70
env->DeleteLocalRef(localClass); // cleanup local ref
71
}
72
73
void Android_UnregisterStorageCallbacks(JNIEnv * env) {
74
if (g_classContentUri) {
75
env->DeleteGlobalRef(g_classContentUri);
76
g_classContentUri = nullptr;
77
}
78
g_nativeActivity = nullptr;
79
openContentUri = nullptr;
80
listContentUriDir = nullptr;
81
contentUriCreateFile = nullptr;
82
contentUriCreateDirectory = nullptr;
83
contentUriCopyFile = nullptr;
84
contentUriMoveFile = nullptr;
85
contentUriRemoveFile = nullptr;
86
contentUriRenameFileTo = nullptr;
87
contentUriGetFileInfo = nullptr;
88
contentUriFileExists = nullptr;
89
contentUriGetFreeStorageSpace = nullptr;
90
filePathGetFreeStorageSpace = nullptr;
91
isExternalStoragePreservedLegacy = nullptr;
92
computeRecursiveDirectorySize = nullptr;
93
}
94
95
bool Android_IsContentUri(std::string_view filename) {
96
return startsWith(filename, "content://");
97
}
98
99
int Android_OpenContentUriFd(std::string_view filename, Android_OpenContentUriMode mode) {
100
if (!g_nativeActivity) {
101
// Hit this in shortcut creation.
102
ERROR_LOG(Log::IO, "Android_OpenContentUriFd: No native activity");
103
return -1;
104
}
105
106
/*
107
// Should breakpoint here to try to find and move as many of these off the EmuThread as possible
108
if (!strcmp(GetCurrentThreadName(), "EmuThread")) {
109
WARN_LOG(Log::IO, "Content URI opened on EmuThread: %.*s", (int)filename.size(), filename.data());
110
}
111
*/
112
113
std::string fname(filename);
114
// PPSSPP adds an ending slash to directories before looking them up.
115
// TODO: Fix that in the caller (or don't call this for directories).
116
if (fname.back() == '/')
117
fname.pop_back();
118
119
auto env = getEnv();
120
const char *modeStr = "";
121
switch (mode) {
122
case Android_OpenContentUriMode::READ: modeStr = "r"; break;
123
case Android_OpenContentUriMode::READ_WRITE: modeStr = "rw"; break;
124
case Android_OpenContentUriMode::READ_WRITE_TRUNCATE: modeStr = "rwt"; break;
125
}
126
jstring j_filename = env->NewStringUTF(fname.c_str());
127
jstring j_mode = env->NewStringUTF(modeStr);
128
int fd = env->CallStaticIntMethod(g_classContentUri, openContentUri, g_nativeActivity, j_filename, j_mode);
129
return fd;
130
}
131
132
StorageError Android_CreateDirectory(const std::string &rootTreeUri, const std::string &dirName) {
133
if (!g_nativeActivity) {
134
return StorageError::UNKNOWN;
135
}
136
auto env = getEnv();
137
jstring paramRoot = env->NewStringUTF(rootTreeUri.c_str());
138
jstring paramDirName = env->NewStringUTF(dirName.c_str());
139
return StorageErrorFromInt(env->CallStaticIntMethod(g_classContentUri, contentUriCreateDirectory, g_nativeActivity, paramRoot, paramDirName));
140
}
141
142
StorageError Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) {
143
if (!g_nativeActivity) {
144
return StorageError::UNKNOWN;
145
}
146
auto env = getEnv();
147
jstring paramRoot = env->NewStringUTF(parentTreeUri.c_str());
148
jstring paramFileName = env->NewStringUTF(fileName.c_str());
149
return StorageErrorFromInt(env->CallStaticIntMethod(g_classContentUri, contentUriCreateFile, g_nativeActivity, paramRoot, paramFileName));
150
}
151
152
StorageError Android_CopyFile(const std::string &fileUri, const std::string &destParentUri) {
153
if (!g_nativeActivity) {
154
return StorageError::UNKNOWN;
155
}
156
auto env = getEnv();
157
jstring paramFileName = env->NewStringUTF(fileUri.c_str());
158
jstring paramDestParentUri = env->NewStringUTF(destParentUri.c_str());
159
return StorageErrorFromInt(env->CallStaticIntMethod(g_classContentUri, contentUriCopyFile, g_nativeActivity, paramFileName, paramDestParentUri));
160
}
161
162
StorageError Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri) {
163
if (!g_nativeActivity) {
164
return StorageError::UNKNOWN;
165
}
166
auto env = getEnv();
167
jstring paramFileName = env->NewStringUTF(fileUri.c_str());
168
jstring paramSrcParentUri = env->NewStringUTF(srcParentUri.c_str());
169
jstring paramDestParentUri = env->NewStringUTF(destParentUri.c_str());
170
return StorageErrorFromInt(env->CallStaticIntMethod(g_classContentUri, contentUriMoveFile, g_nativeActivity, paramFileName, paramSrcParentUri, paramDestParentUri));
171
}
172
173
StorageError Android_RemoveFile(const std::string &fileUri) {
174
if (!g_nativeActivity) {
175
return StorageError::UNKNOWN;
176
}
177
auto env = getEnv();
178
jstring paramFileName = env->NewStringUTF(fileUri.c_str());
179
return StorageErrorFromInt(env->CallStaticIntMethod(g_classContentUri, contentUriRemoveFile, g_nativeActivity, paramFileName));
180
}
181
182
StorageError Android_RenameFileTo(const std::string &fileUri, const std::string &newName) {
183
if (!g_nativeActivity) {
184
return StorageError::UNKNOWN;
185
}
186
auto env = getEnv();
187
jstring paramFileUri = env->NewStringUTF(fileUri.c_str());
188
jstring paramNewName = env->NewStringUTF(newName.c_str());
189
return StorageErrorFromInt(env->CallStaticIntMethod(g_classContentUri, contentUriRenameFileTo, g_nativeActivity, paramFileUri, paramNewName));
190
}
191
192
// NOTE: Does not set fullName - you're supposed to already know it.
193
static bool ParseFileInfo(std::string_view line, File::FileInfo *fileInfo) {
194
std::vector<std::string> parts;
195
SplitString(line, '|', parts);
196
if (parts.size() != 4) {
197
ERROR_LOG(Log::FileSystem, "Bad format (1): %.*s", (int)line.size(), line.data());
198
return false;
199
}
200
fileInfo->name = parts[2];
201
fileInfo->isDirectory = parts[0][0] == 'D';
202
fileInfo->exists = true;
203
if (1 != sscanf(parts[1].c_str(), "%" PRIu64, &fileInfo->size)) {
204
ERROR_LOG(Log::FileSystem, "Bad format (2): %.*s", (int)line.size(), line.data());
205
return false;
206
}
207
fileInfo->isWritable = true; // TODO: Should be passed as part of the string.
208
// TODO: For read-only mappings, reflect that here, similarly as with isWritable.
209
// Directories are normally executable (0111) which means they're traversable.
210
fileInfo->access = fileInfo->isDirectory ? 0777 : 0666;
211
212
uint64_t lastModifiedMs = 0;
213
if (1 != sscanf(parts[3].c_str(), "%" PRIu64, &lastModifiedMs)) {
214
ERROR_LOG(Log::FileSystem, "Bad format (3): %.*s", (int)line.size(), line.data());
215
return false;
216
}
217
218
// Convert from milliseconds
219
uint32_t lastModified = lastModifiedMs / 1000;
220
221
// We don't have better information, so let's just spam lastModified into all the date/time fields.
222
fileInfo->mtime = lastModified;
223
fileInfo->ctime = lastModified;
224
fileInfo->atime = lastModified;
225
return true;
226
}
227
228
bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *fileInfo) {
229
if (!g_nativeActivity) {
230
return false;
231
}
232
auto env = getEnv();
233
jstring paramFileUri = env->NewStringUTF(fileUri.c_str());
234
235
jstring str = (jstring)env->CallStaticObjectMethod(g_classContentUri, contentUriGetFileInfo, g_nativeActivity, paramFileUri);
236
if (!str) {
237
return false;
238
}
239
const char *charArray = env->GetStringUTFChars(str, 0);
240
bool retval = ParseFileInfo(charArray, fileInfo);
241
fileInfo->fullName = Path(fileUri);
242
243
env->DeleteLocalRef(str);
244
return retval && fileInfo->exists;
245
}
246
247
bool Android_FileExists(const std::string &fileUri) {
248
if (!g_nativeActivity) {
249
return false;
250
}
251
auto env = getEnv();
252
jstring paramFileUri = env->NewStringUTF(fileUri.c_str());
253
bool exists = env->CallStaticBooleanMethod(g_classContentUri, contentUriFileExists, g_nativeActivity, paramFileUri);
254
return exists;
255
}
256
257
std::vector<File::FileInfo> Android_ListContentUri(const std::string &uri, const std::string &prefix, bool *exists) {
258
if (!g_nativeActivity) {
259
*exists = false;
260
return {};
261
}
262
auto env = getEnv();
263
*exists = true;
264
265
double start = time_now_d();
266
267
jstring param = env->NewStringUTF(uri.c_str());
268
jstring filenamePrefix = prefix.empty() ? nullptr : env->NewStringUTF(prefix.c_str());
269
jobject retval = env->CallStaticObjectMethod(g_classContentUri, listContentUriDir, g_nativeActivity, param, filenamePrefix);
270
271
jobjectArray fileList = (jobjectArray)retval;
272
std::vector<File::FileInfo> items;
273
int size = env->GetArrayLength(fileList);
274
for (int i = 0; i < size; i++) {
275
jstring str = (jstring)env->GetObjectArrayElement(fileList, i);
276
const char *charArray = env->GetStringUTFChars(str, 0);
277
if (charArray) { // paranoia
278
std::string line = charArray;
279
File::FileInfo info{};
280
if (line == "X") {
281
// Indicates an exception thrown, uri doesn't exist.
282
*exists = false;
283
} else if (ParseFileInfo(line, &info)) {
284
// We can just reconstruct the URI.
285
info.fullName = Path(uri) / info.name;
286
// INFO_LOG(Log::IO, "%s", info.name.c_str());
287
items.push_back(info);
288
}
289
}
290
env->ReleaseStringUTFChars(str, charArray);
291
env->DeleteLocalRef(str);
292
}
293
env->DeleteLocalRef(fileList);
294
295
double elapsed = time_now_d() - start;
296
double threshold = 0.1;
297
if (elapsed >= threshold) {
298
INFO_LOG(Log::IO, "Listing directory on content URI '%s' took %0.3f s (%d files, log threshold = %0.3f)", uri.c_str(), elapsed, (int)items.size(), threshold);
299
}
300
return items;
301
}
302
303
int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) {
304
if (!g_nativeActivity) {
305
return false;
306
}
307
auto env = getEnv();
308
309
jstring param = env->NewStringUTF(uri.c_str());
310
return env->CallStaticLongMethod(g_classContentUri, contentUriGetFreeStorageSpace, g_nativeActivity, param);
311
}
312
313
// Hm, this is never used? We use statvfs instead.
314
int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) {
315
if (!g_nativeActivity) {
316
return false;
317
}
318
auto env = getEnv();
319
320
if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) < 26) {
321
// This is available from Android O.
322
return -1;
323
}
324
325
jstring param = env->NewStringUTF(filePath.c_str());
326
return env->CallStaticLongMethod(g_classContentUri, filePathGetFreeStorageSpace, g_nativeActivity, param);
327
}
328
329
int64_t Android_ComputeRecursiveDirectorySize(const std::string &uri) {
330
if (!g_nativeActivity) {
331
return false;
332
}
333
auto env = getEnv();
334
335
jstring param = env->NewStringUTF(uri.c_str());
336
337
double start = time_now_d();
338
int64_t size = env->CallStaticLongMethod(g_classContentUri, computeRecursiveDirectorySize, g_nativeActivity, param);
339
double elapsed = time_now_d() - start;
340
341
INFO_LOG(Log::IO, "ComputeRecursiveDirectorySize(%s) in %0.3f s", uri.c_str(), elapsed);
342
return size;
343
}
344
345
bool Android_IsExternalStoragePreservedLegacy() {
346
if (!g_nativeActivity) {
347
return false;
348
}
349
auto env = getEnv();
350
// Note: No activity param
351
return env->CallStaticBooleanMethod(g_classContentUri, isExternalStoragePreservedLegacy);
352
}
353
354
const char *Android_ErrorToString(StorageError error) {
355
switch (error) {
356
case StorageError::SUCCESS: return "SUCCESS";
357
case StorageError::UNKNOWN: return "UNKNOWN";
358
case StorageError::NOT_FOUND: return "NOT_FOUND";
359
case StorageError::DISK_FULL: return "DISK_FULL";
360
case StorageError::ALREADY_EXISTS: return "ALREADY_EXISTS";
361
default: return "(UNKNOWN)";
362
}
363
}
364
365
#else
366
367
// These strings should never appear except on Android.
368
// Very hacky.
369
std::string g_extFilesDir = "(IF YOU SEE THIS THERE'S A BUG)";
370
std::string g_externalDir = "(IF YOU SEE THIS THERE'S A BUG (2))";
371
std::string g_nativeLibDir = "(IF YOU SEE THIS THERE'S A BUG (3))";
372
373
#endif
374
375