CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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