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