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