1 /*
2 * Filesystem driver for APK contents.
3 *
4 * By Elias Pschernig.
5 */
6
7
8 #include "allegro5/allegro.h"
9 #include "allegro5/allegro_android.h"
10 #include "allegro5/internal/aintern_android.h"
11
12 #include <jni.h>
13
14 ALLEGRO_DEBUG_CHANNEL("android")
15
16 typedef struct ALLEGRO_FS_ENTRY_APK ALLEGRO_FS_ENTRY_APK;
17
18 struct ALLEGRO_FS_ENTRY_APK
19 {
20 ALLEGRO_FS_ENTRY fs_entry; /* must be first */
21 ALLEGRO_PATH *path;
22 const char *path_cstr;
23
24 /* For directory listing. */
25 char *file_list;
26 char *file_list_pos;
27 bool is_dir_open;
28 };
29
30 /* forward declaration */
31 static const ALLEGRO_FS_INTERFACE fs_apk_vtable;
32
33 /* current working directory */
34 /* TODO: free this somewhere */
35 static ALLEGRO_USTR *fs_apk_cwd_ustr;
36
37 static ALLEGRO_FILE *fs_apk_open_file(ALLEGRO_FS_ENTRY *fse, const char *mode);
38
get_fake_cwd(void)39 static ALLEGRO_USTR *get_fake_cwd(void) {
40 if (!fs_apk_cwd_ustr) {
41 fs_apk_cwd_ustr = al_ustr_new("/");
42 }
43 return fs_apk_cwd_ustr;
44 }
45
path_is_absolute(const char * path)46 static bool path_is_absolute(const char *path)
47 {
48 return (path && path[0] == '/');
49 }
50
ensure_trailing_slash(ALLEGRO_USTR * us)51 static void ensure_trailing_slash(ALLEGRO_USTR *us)
52 {
53 int pos = al_ustr_size(us);
54 if (al_ustr_prev_get(us, &pos) != '/') {
55 al_ustr_append_chr(us, '/');
56 }
57 }
58
apply_cwd(const char * path)59 static ALLEGRO_USTR *apply_cwd(const char *path)
60 {
61 ALLEGRO_USTR *us;
62
63 if (path_is_absolute(path)) {
64 return al_ustr_new(path);
65 }
66
67 us = al_ustr_dup(get_fake_cwd());
68 al_ustr_append_cstr(us, path);
69 return us;
70 }
71
fs_apk_create_entry(const char * path)72 static ALLEGRO_FS_ENTRY *fs_apk_create_entry(const char *path)
73 {
74 ALLEGRO_FS_ENTRY_APK *e;
75 ALLEGRO_USTR *us;
76
77 e = al_calloc(1, sizeof *e);
78 if (!e)
79 return NULL;
80 e->fs_entry.vtable = &fs_apk_vtable;
81
82 us = apply_cwd(path);
83 e->path = al_create_path(al_cstr(us));
84 al_ustr_free(us);
85 if (!e->path) {
86 al_free(e);
87 return NULL;
88 }
89 e->path_cstr = al_path_cstr(e->path, '/');
90
91 return &e->fs_entry;
92 }
93
fs_apk_get_current_directory(void)94 static char *fs_apk_get_current_directory(void)
95 {
96 return al_cstr_dup(get_fake_cwd());
97 }
98
fs_apk_change_directory(const char * path)99 static bool fs_apk_change_directory(const char *path)
100 {
101 ALLEGRO_USTR *us;
102 ALLEGRO_USTR *cwd = get_fake_cwd();
103
104 /* Figure out which directory we are trying to change to. */
105 if (path_is_absolute(path))
106 us = al_ustr_new(path);
107 else
108 us = apply_cwd(path);
109
110 ensure_trailing_slash(us);
111
112 al_ustr_assign(cwd, us);
113
114 al_ustr_free(us);
115
116 return true;
117 }
118
fs_apk_remove_filename(const char * path)119 static bool fs_apk_remove_filename(const char *path)
120 {
121 (void)path;
122 return false;
123 }
124
fs_apk_make_directory(const char * path)125 static bool fs_apk_make_directory(const char *path)
126 {
127 (void)path;
128 return false;
129 }
130
fs_apk_entry_name(ALLEGRO_FS_ENTRY * fse)131 static const char *fs_apk_entry_name(ALLEGRO_FS_ENTRY *fse)
132 {
133 ALLEGRO_FS_ENTRY_APK *e = (ALLEGRO_FS_ENTRY_APK *)fse;
134 return al_path_cstr(e->path, '/');
135 }
136
fs_apk_update_entry(ALLEGRO_FS_ENTRY * fse)137 static bool fs_apk_update_entry(ALLEGRO_FS_ENTRY *fse)
138 {
139 (void)fse;
140 return true;
141 }
142
fs_apk_entry_size(ALLEGRO_FS_ENTRY * fse)143 static off_t fs_apk_entry_size(ALLEGRO_FS_ENTRY *fse)
144 {
145 // Only way to determine the size would be to read the file...
146 // we won't do that.
147 (void)fse;
148 return 0;
149 }
150
fs_apk_entry_mode(ALLEGRO_FS_ENTRY * fse)151 static uint32_t fs_apk_entry_mode(ALLEGRO_FS_ENTRY *fse)
152 {
153 ALLEGRO_FS_ENTRY_APK *e = (ALLEGRO_FS_ENTRY_APK *)fse;
154 uint32_t mode = ALLEGRO_FILEMODE_READ;
155 int n = strlen(e->path_cstr);
156 if (e->path_cstr[n - 1] == '/')
157 mode |= ALLEGRO_FILEMODE_ISDIR | ALLEGRO_FILEMODE_EXECUTE;
158 else
159 mode |= ALLEGRO_FILEMODE_ISFILE;
160 return mode;
161 }
162
fs_apk_entry_mtime(ALLEGRO_FS_ENTRY * fse)163 static time_t fs_apk_entry_mtime(ALLEGRO_FS_ENTRY *fse)
164 {
165 (void)fse;
166 return 0;
167 }
168
fs_apk_entry_exists(ALLEGRO_FS_ENTRY * fse)169 static bool fs_apk_entry_exists(ALLEGRO_FS_ENTRY *fse)
170 {
171 ALLEGRO_FILE *f = fs_apk_open_file(fse, "r");
172 if (f) {
173 al_fclose(f);
174 return true;
175 }
176 return false;
177 }
178
fs_apk_remove_entry(ALLEGRO_FS_ENTRY * fse)179 static bool fs_apk_remove_entry(ALLEGRO_FS_ENTRY *fse)
180 {
181 (void)fse;
182 return false;
183 }
184
fs_apk_open_directory(ALLEGRO_FS_ENTRY * fse)185 static bool fs_apk_open_directory(ALLEGRO_FS_ENTRY *fse)
186 {
187 ALLEGRO_FS_ENTRY_APK *e = (ALLEGRO_FS_ENTRY_APK *)fse;
188
189 JNIEnv *jnienv;
190 jnienv = _al_android_get_jnienv();
191 jstring jpath = (*jnienv)->NewStringUTF(jnienv, e->path_cstr);
192 jstring js = _jni_callStaticObjectMethodV(jnienv,
193 _al_android_apk_fs_class(), "list",
194 "(L" ALLEGRO_ANDROID_PACKAGE_NAME_SLASH "/AllegroActivity;Ljava/lang/String;)Ljava/lang/String;",
195 _al_android_activity_object(), jpath);
196
197 const char *cs = _jni_call(jnienv, const char *, GetStringUTFChars, js, NULL);
198 e->file_list = al_malloc(strlen(cs) + 1);
199 strcpy(e->file_list, cs);
200 e->file_list_pos = e->file_list;
201
202 _jni_callv(jnienv, ReleaseStringUTFChars, js, cs);
203 _jni_callv(jnienv, DeleteLocalRef, js);
204 _jni_callv(jnienv, DeleteLocalRef, jpath);
205
206 e->is_dir_open = true;
207 return true;
208 }
209
fs_apk_read_directory(ALLEGRO_FS_ENTRY * fse)210 static ALLEGRO_FS_ENTRY *fs_apk_read_directory(ALLEGRO_FS_ENTRY *fse)
211 {
212 ALLEGRO_FS_ENTRY_APK *e = (ALLEGRO_FS_ENTRY_APK *)fse;
213 ALLEGRO_FS_ENTRY *next;
214 ALLEGRO_USTR *tmp;
215
216 if (!e->file_list_pos)
217 return NULL;
218 if (!*e->file_list_pos)
219 return NULL;
220
221 tmp = al_ustr_new(e->path_cstr);
222 ensure_trailing_slash(tmp);
223 char *name = e->file_list_pos;
224 char *semi = strchr(name, ';');
225 if (semi) {
226 *semi = 0;
227 e->file_list_pos = semi + 1;
228 }
229 else {
230 e->file_list_pos = name + strlen(name);
231 }
232 al_ustr_append_cstr(tmp, name);
233 next = fs_apk_create_entry(al_cstr(tmp));
234 al_ustr_free(tmp);
235
236 return next;
237 }
238
fs_apk_close_directory(ALLEGRO_FS_ENTRY * fse)239 static bool fs_apk_close_directory(ALLEGRO_FS_ENTRY *fse)
240 {
241 ALLEGRO_FS_ENTRY_APK *e = (ALLEGRO_FS_ENTRY_APK *)fse;
242 al_free(e->file_list);
243 e->file_list = NULL;
244 e->is_dir_open = false;
245 return true;
246 }
247
fs_apk_destroy_entry(ALLEGRO_FS_ENTRY * fse)248 static void fs_apk_destroy_entry(ALLEGRO_FS_ENTRY *fse)
249 {
250 ALLEGRO_FS_ENTRY_APK *e = (ALLEGRO_FS_ENTRY_APK *)fse;
251 if (e->is_dir_open)
252 fs_apk_close_directory(fse);
253 al_destroy_path(e->path);
254 al_free(e);
255 }
256
fs_apk_open_file(ALLEGRO_FS_ENTRY * fse,const char * mode)257 static ALLEGRO_FILE *fs_apk_open_file(ALLEGRO_FS_ENTRY *fse, const char *mode)
258 {
259 return al_fopen_interface(_al_get_apk_file_vtable(), fs_apk_entry_name(fse),
260 mode);
261 }
262
fs_apk_filename_exists(const char * path)263 static bool fs_apk_filename_exists(const char *path)
264 {
265 bool ret;
266
267 ALLEGRO_FS_ENTRY *e = fs_apk_create_entry(path);
268 ret = fs_apk_entry_exists(e);
269 fs_apk_destroy_entry(e);
270 return ret;
271 }
272
273 static const ALLEGRO_FS_INTERFACE fs_apk_vtable =
274 {
275 fs_apk_create_entry,
276 fs_apk_destroy_entry,
277 fs_apk_entry_name,
278 fs_apk_update_entry,
279 fs_apk_entry_mode,
280 fs_apk_entry_mtime,
281 fs_apk_entry_mtime,
282 fs_apk_entry_mtime,
283 fs_apk_entry_size,
284 fs_apk_entry_exists,
285 fs_apk_remove_entry,
286
287 fs_apk_open_directory,
288 fs_apk_read_directory,
289 fs_apk_close_directory,
290
291 fs_apk_filename_exists,
292 fs_apk_remove_filename,
293 fs_apk_get_current_directory,
294 fs_apk_change_directory,
295 fs_apk_make_directory,
296
297 fs_apk_open_file
298 };
299
300 /* Function: al_android_set_apk_fs_interface
301 */
al_android_set_apk_fs_interface(void)302 void al_android_set_apk_fs_interface(void)
303 {
304 al_set_fs_interface(&fs_apk_vtable);
305 }
306
307 /* vim: set sts=3 sw=3 et: */
308
309