1 | // MIT License |
2 | |
3 | // Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com |
4 | |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | // of this software and associated documentation files (the "Software"), to deal |
7 | // in the Software without restriction, including without limitation the rights |
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | // copies of the Software, and to permit persons to whom the Software is |
10 | // furnished to do so, subject to the following conditions: |
11 | |
12 | // The above copyright notice and this permission notice shall be included in all |
13 | // copies or substantial portions of the Software. |
14 | |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
21 | // SOFTWARE. |
22 | |
23 | #include "studio.h" |
24 | #include "fs.h" |
25 | #include "net.h" |
26 | |
27 | #if defined(BAREMETALPI) || defined(_3DS) |
28 | #ifdef EN_DEBUG |
29 | #define dbg(...) printf(__VA_ARGS__) |
30 | #else |
31 | #define dbg(...) |
32 | #endif |
33 | #endif |
34 | |
35 | #if defined(BAREMETALPI) |
36 | #include "../../circle-stdlib/libs/circle/addon/fatfs/ff.h" |
37 | #else |
38 | #include <dirent.h> |
39 | #include <sys/stat.h> |
40 | #include <sys/types.h> |
41 | #endif |
42 | |
43 | #if defined(TIC_BUILD_WITH_LUA) |
44 | |
45 | #include <lua.h> |
46 | #include <lauxlib.h> |
47 | #include <lualib.h> |
48 | |
49 | #endif |
50 | |
51 | #if defined(__TIC_WINDOWS__) |
52 | #include <direct.h> |
53 | #include <windows.h> |
54 | #else |
55 | #include <unistd.h> |
56 | #endif |
57 | |
58 | #if defined(__EMSCRIPTEN__) |
59 | #include <emscripten.h> |
60 | #endif |
61 | |
62 | static const char* PublicDir = TIC_HOST; |
63 | |
64 | struct tic_fs |
65 | { |
66 | char dir[TICNAME_MAX]; |
67 | char work[TICNAME_MAX]; |
68 | tic_net* net; |
69 | }; |
70 | |
71 | #if defined(__EMSCRIPTEN__) |
72 | void syncfs() |
73 | { |
74 | EM_ASM({Module.syncFSRequests++;}); |
75 | } |
76 | #endif |
77 | |
78 | const char* tic_fs_pathroot(tic_fs* fs, const char* name) |
79 | { |
80 | static char path[TICNAME_MAX]; |
81 | |
82 | snprintf(path, sizeof path, "%s%s" , fs->dir, name); |
83 | |
84 | #if defined(__TIC_WINDOWS__) |
85 | char* ptr = path; |
86 | while (*ptr) |
87 | { |
88 | if (*ptr == '/') *ptr = '\\'; |
89 | ptr++; |
90 | } |
91 | #endif |
92 | |
93 | return path; |
94 | } |
95 | |
96 | const char* tic_fs_path(tic_fs* fs, const char* name) |
97 | { |
98 | static char path[TICNAME_MAX]; |
99 | |
100 | if(*name == '/') |
101 | strncpy(path, name + 1, sizeof path); |
102 | else if(strlen(fs->work)) |
103 | snprintf(path, sizeof path, "%s/%s" , fs->work, name); |
104 | else |
105 | strncpy(path, name, sizeof path); |
106 | |
107 | return tic_fs_pathroot(fs, path); |
108 | } |
109 | |
110 | static bool isRoot(tic_fs* fs) |
111 | { |
112 | return fs->work[0] == '\0'; |
113 | } |
114 | |
115 | static bool isPublicRoot(tic_fs* fs) |
116 | { |
117 | return strcmp(fs->work, PublicDir) == 0; |
118 | } |
119 | |
120 | static bool isPublic(tic_fs* fs) |
121 | { |
122 | return memcmp(fs->work, PublicDir, STRLEN(PublicDir)) == 0; |
123 | } |
124 | |
125 | bool tic_fs_ispubdir(tic_fs* fs) |
126 | { |
127 | return isPublic(fs); |
128 | } |
129 | |
130 | #if defined(__TIC_WINDOWS__) |
131 | |
132 | typedef wchar_t FsString; |
133 | |
134 | #define __S(x) L ## x |
135 | #define _S(x) __S(x) |
136 | |
137 | static const FsString* utf8ToString(const char* str) |
138 | { |
139 | FsString* wstr = malloc(TICNAME_MAX * sizeof(FsString)); |
140 | |
141 | MultiByteToWideChar(CP_UTF8, 0, str, TICNAME_MAX, wstr, TICNAME_MAX); |
142 | |
143 | return wstr; |
144 | } |
145 | |
146 | static const char* stringToUtf8(const FsString* wstr) |
147 | { |
148 | char* str = malloc(TICNAME_MAX * sizeof(char)); |
149 | |
150 | WideCharToMultiByte(CP_UTF8, 0, wstr, TICNAME_MAX, str, TICNAME_MAX, 0, 0); |
151 | |
152 | return str; |
153 | } |
154 | |
155 | #define freeString(S) free((void*)S) |
156 | |
157 | |
158 | #define TIC_DIR _WDIR |
159 | #define tic_dirent _wdirent |
160 | #define tic_stat_struct _stat |
161 | |
162 | #define tic_opendir _wopendir |
163 | #define tic_readdir _wreaddir |
164 | #define tic_closedir _wclosedir |
165 | #define tic_rmdir _wrmdir |
166 | #define tic_stat _wstat |
167 | #define tic_remove _wremove |
168 | #define tic_fopen _wfopen |
169 | #define tic_mkdir(name) _wmkdir(name) |
170 | #define tic_strncpy wcsncpy |
171 | #define tic_strncat wcsncat |
172 | |
173 | #else |
174 | |
175 | typedef char FsString; |
176 | |
177 | #define _S(x) (x) |
178 | |
179 | #define utf8ToString(S) (S) |
180 | #define stringToUtf8(S) (S) |
181 | |
182 | #define freeString(S) |
183 | |
184 | #define TIC_DIR DIR |
185 | #define tic_dirent dirent |
186 | #define tic_stat_struct stat |
187 | |
188 | #define tic_opendir opendir |
189 | #define tic_readdir readdir |
190 | #define tic_closedir closedir |
191 | #define tic_rmdir rmdir |
192 | #define tic_stat stat |
193 | #define tic_remove remove |
194 | #define tic_fopen fopen |
195 | #define tic_mkdir(name) mkdir(name, 0700) |
196 | #define tic_strncpy strncpy |
197 | #define tic_strncat strncat |
198 | |
199 | #endif |
200 | |
201 | typedef struct |
202 | { |
203 | fs_list_callback item; |
204 | fs_done_callback done; |
205 | void* data; |
206 | } NetDirData; |
207 | |
208 | #if defined(TIC_BUILD_WITH_LUA) |
209 | |
210 | static lua_State* netLuaInit(u8* buffer, s32 size) |
211 | { |
212 | if (buffer && size) |
213 | { |
214 | char* script = calloc(1, size + 1); |
215 | memcpy(script, buffer, size); |
216 | |
217 | lua_State* lua = luaL_newstate(); |
218 | |
219 | if(lua) |
220 | { |
221 | if(luaL_loadstring(lua, script) == LUA_OK && lua_pcall(lua, 0, LUA_MULTRET, 0) == LUA_OK) |
222 | return lua; |
223 | |
224 | else lua_close(lua); |
225 | } |
226 | |
227 | free(script); |
228 | } |
229 | |
230 | return NULL; |
231 | } |
232 | |
233 | static void onDirResponse(const net_get_data* netData) |
234 | { |
235 | NetDirData* netDirData = (NetDirData*)netData->calldata; |
236 | |
237 | if(netData->type == net_get_done) |
238 | { |
239 | lua_State* lua = netLuaInit(netData->done.data, netData->done.size); |
240 | |
241 | if (lua) |
242 | { |
243 | { |
244 | lua_getglobal(lua, "folders" ); |
245 | |
246 | if (lua_type(lua, -1) == LUA_TTABLE) |
247 | { |
248 | s32 count = (s32)lua_rawlen(lua, -1); |
249 | |
250 | for (s32 i = 1; i <= count; i++) |
251 | { |
252 | lua_geti(lua, -1, i); |
253 | |
254 | { |
255 | lua_getfield(lua, -1, "name" ); |
256 | if (lua_isstring(lua, -1)) |
257 | netDirData->item(lua_tostring(lua, -1), NULL, NULL, 0, netDirData->data, true); |
258 | |
259 | lua_pop(lua, 1); |
260 | } |
261 | |
262 | lua_pop(lua, 1); |
263 | } |
264 | } |
265 | |
266 | lua_pop(lua, 1); |
267 | } |
268 | |
269 | { |
270 | lua_getglobal(lua, "files" ); |
271 | |
272 | if (lua_type(lua, -1) == LUA_TTABLE) |
273 | { |
274 | s32 count = (s32)lua_rawlen(lua, -1); |
275 | |
276 | for (s32 i = 1; i <= count; i++) |
277 | { |
278 | lua_geti(lua, -1, i); |
279 | |
280 | char hash[TICNAME_MAX]; |
281 | char name[TICNAME_MAX]; |
282 | char title[TICNAME_MAX]; |
283 | |
284 | { |
285 | lua_getfield(lua, -1, "hash" ); |
286 | if (lua_isstring(lua, -1)) |
287 | strncpy(hash, lua_tostring(lua, -1), sizeof hash); |
288 | |
289 | lua_pop(lua, 1); |
290 | } |
291 | |
292 | { |
293 | lua_getfield(lua, -1, "filename" ); |
294 | |
295 | if (lua_isstring(lua, -1)) |
296 | strncpy(name, lua_tostring(lua, -1), sizeof name); |
297 | |
298 | lua_pop(lua, 1); |
299 | } |
300 | |
301 | { |
302 | lua_getfield(lua, -1, "name" ); |
303 | |
304 | if (lua_isstring(lua, -1)) |
305 | strncpy(title, lua_tostring(lua, -1), sizeof title); |
306 | |
307 | lua_pop(lua, 1); |
308 | } |
309 | |
310 | { |
311 | lua_getfield(lua, -1, "id" ); |
312 | |
313 | if (lua_isinteger(lua, -1)) |
314 | netDirData->item(name, title, hash, (s32)lua_tointeger(lua, -1), netDirData->data, false); |
315 | |
316 | lua_pop(lua, 1); |
317 | } |
318 | |
319 | lua_pop(lua, 1); |
320 | } |
321 | } |
322 | |
323 | lua_pop(lua, 1); |
324 | } |
325 | |
326 | lua_close(lua); |
327 | } |
328 | } |
329 | |
330 | switch (netData->type) |
331 | { |
332 | case net_get_done: |
333 | case net_get_error: |
334 | netDirData->done(netDirData->data); |
335 | free(netDirData); |
336 | break; |
337 | default: break; |
338 | } |
339 | } |
340 | |
341 | #endif |
342 | |
343 | static void enumFiles(tic_fs* fs, const char* path, fs_list_callback callback, void* data) |
344 | { |
345 | #if defined(BAREMETALPI) |
346 | dbg("enumFiles %s" , path); |
347 | |
348 | if (path && *path) { |
349 | // ok |
350 | } |
351 | else |
352 | { |
353 | return; |
354 | } |
355 | |
356 | static char path2[TICNAME_MAX]; |
357 | strncpy(path2, path, sizeof path2); |
358 | |
359 | if (path2[strlen(path2) - 1] == '/') // one character |
360 | path2[strlen(path2) - 1] = 0; |
361 | |
362 | dbg("enumFiles Real %s" , path2); |
363 | |
364 | |
365 | DIR Directory; |
366 | FILINFO FileInfo; |
367 | FRESULT Result = f_findfirst (&Directory, &FileInfo, path2, "*" ); |
368 | dbg("enumFilesRes %d" , Result); |
369 | |
370 | for (unsigned i = 0; Result == FR_OK && FileInfo.fname[0]; i++) |
371 | { |
372 | if (!(FileInfo.fattrib & (AM_HID | AM_SYS))) |
373 | { |
374 | bool result = callback(FileInfo.fname, NULL, NULL, 0, data, FileInfo.fattrib & AM_DIR); |
375 | |
376 | if (!result) |
377 | { |
378 | break; |
379 | } |
380 | } |
381 | |
382 | Result = f_findnext (&Directory, &FileInfo); |
383 | } |
384 | |
385 | #else |
386 | TIC_DIR *dir = NULL; |
387 | struct tic_dirent* ent = NULL; |
388 | |
389 | const FsString* pathString = utf8ToString(path); |
390 | |
391 | if ((dir = tic_opendir(pathString)) != NULL) |
392 | { |
393 | FsString fullPath[TICNAME_MAX]; |
394 | struct tic_stat_struct s; |
395 | |
396 | while ((ent = tic_readdir(dir)) != NULL) |
397 | { |
398 | if(*ent->d_name != _S('.')) |
399 | { |
400 | tic_strncpy(fullPath, pathString, COUNT_OF(fullPath)); |
401 | tic_strncat(fullPath, ent->d_name, COUNT_OF(fullPath)); |
402 | |
403 | if(tic_stat(fullPath, &s) == 0) |
404 | { |
405 | const char* name = stringToUtf8(ent->d_name); |
406 | bool result = callback(name, NULL, NULL, 0, data, S_ISDIR(s.st_mode)); |
407 | freeString(name); |
408 | |
409 | if(!result) break; |
410 | } |
411 | } |
412 | } |
413 | |
414 | tic_closedir(dir); |
415 | } |
416 | |
417 | freeString(pathString); |
418 | #endif |
419 | } |
420 | |
421 | void tic_fs_enum(tic_fs* fs, fs_list_callback onItem, fs_done_callback onDone, void* data) |
422 | { |
423 | if (isRoot(fs) && !onItem(PublicDir, NULL, NULL, 0, data, true)) |
424 | { |
425 | onDone(data); |
426 | return; |
427 | } |
428 | |
429 | #if defined(TIC_BUILD_WITH_LUA) && defined(BUILD_EDITORS) |
430 | if(isPublic(fs)) |
431 | { |
432 | char request[TICNAME_MAX]; |
433 | snprintf(request, sizeof request, "/api?fn=dir&path=%s" , fs->work + sizeof(TIC_HOST)); |
434 | |
435 | NetDirData netDirData = { onItem, onDone, data }; |
436 | tic_net_get(fs->net, request, onDirResponse, MOVE(netDirData)); |
437 | |
438 | return; |
439 | } |
440 | #endif |
441 | |
442 | const char* path = tic_fs_path(fs, "" ); |
443 | |
444 | enumFiles(fs, path, onItem, data); |
445 | |
446 | onDone(data); |
447 | } |
448 | |
449 | bool tic_fs_deldir(tic_fs* fs, const char* name) |
450 | { |
451 | #if defined(BAREMETALPI) |
452 | // TODO BAREMETALPI |
453 | dbg("tic_fs_deldir %s" , name); |
454 | return 0; |
455 | #else |
456 | #if defined(__TIC_WINDOWS__) |
457 | const char* path = tic_fs_path(fs, name); |
458 | |
459 | const FsString* pathString = utf8ToString(path); |
460 | bool result = tic_rmdir(pathString); |
461 | freeString(pathString); |
462 | |
463 | #else |
464 | bool result = rmdir(tic_fs_path(fs, name)); |
465 | #endif |
466 | |
467 | #if defined(__EMSCRIPTEN__) |
468 | syncfs(); |
469 | #endif |
470 | |
471 | return result; |
472 | #endif |
473 | } |
474 | |
475 | bool tic_fs_delfile(tic_fs* fs, const char* name) |
476 | { |
477 | #if defined(BAREMETALPI) |
478 | dbg("tic_fs_delfile %s" , name); |
479 | // TODO BAREMETALPI |
480 | return false; |
481 | #else |
482 | const char* path = tic_fs_path(fs, name); |
483 | |
484 | const FsString* pathString = utf8ToString(path); |
485 | bool result = tic_remove(pathString); |
486 | freeString(pathString); |
487 | |
488 | #if defined(__EMSCRIPTEN__) |
489 | syncfs(); |
490 | #endif |
491 | |
492 | return result; |
493 | #endif |
494 | } |
495 | |
496 | void tic_fs_homedir(tic_fs* fs) |
497 | { |
498 | memset(fs->work, 0, sizeof fs->work); |
499 | } |
500 | |
501 | void tic_fs_dirback(tic_fs* fs) |
502 | { |
503 | if(isPublicRoot(fs)) |
504 | { |
505 | tic_fs_homedir(fs); |
506 | return; |
507 | } |
508 | |
509 | char* start = fs->work; |
510 | char* ptr = start + strlen(fs->work); |
511 | |
512 | while (ptr > start && *ptr != '/') ptr--; |
513 | while (*ptr) *ptr++ = '\0'; |
514 | } |
515 | |
516 | void tic_fs_dir(tic_fs* fs, char* dir) |
517 | { |
518 | strncpy(dir, fs->work, TICNAME_MAX); |
519 | } |
520 | |
521 | void tic_fs_changedir(tic_fs* fs, const char* dir) |
522 | { |
523 | if(strlen(fs->work)) |
524 | strncat(fs->work, "/" , TICNAME_MAX); |
525 | |
526 | strcat(fs->work, dir); |
527 | |
528 | #if defined(__TIC_WINDOWS__) |
529 | for(char *ptr = fs->work, *end = ptr + strlen(ptr); ptr < end; ptr++) |
530 | if(*ptr == '\\') |
531 | *ptr = '/'; |
532 | #endif |
533 | } |
534 | |
535 | typedef struct |
536 | { |
537 | char* name; |
538 | bool found; |
539 | fs_isdir_callback done; |
540 | void* data; |
541 | |
542 | } EnumPublicDirsData; |
543 | |
544 | static bool onEnumPublicDirs(const char* name, const char* title, const char* hash, s32 id, void* data, bool dir) |
545 | { |
546 | EnumPublicDirsData* enumPublicDirsData = (EnumPublicDirsData*)data; |
547 | |
548 | if(strcmp(name, enumPublicDirsData->name) == 0) |
549 | { |
550 | enumPublicDirsData->found = true; |
551 | return false; |
552 | } |
553 | |
554 | return true; |
555 | } |
556 | |
557 | static void onEnumPublicDirsDone(void* data) |
558 | { |
559 | EnumPublicDirsData* enumPublicDirsData = data; |
560 | enumPublicDirsData->done(enumPublicDirsData->found, enumPublicDirsData->data); |
561 | free(enumPublicDirsData->name); |
562 | free(enumPublicDirsData); |
563 | } |
564 | |
565 | bool tic_fs_isdir(tic_fs* fs, const char* name) |
566 | { |
567 | if (*name == '.') return false; |
568 | if (isRoot(fs) && strcmp(name, PublicDir) == 0) return true; |
569 | |
570 | #if defined(BAREMETALPI) |
571 | dbg("fsIsDirSync %s\n" , name); |
572 | FILINFO s; |
573 | const char* path = tic_fs_path(fs, name); |
574 | FRESULT res = f_stat(path, &s); |
575 | if (res != FR_OK) return false; |
576 | |
577 | return s.fattrib & AM_DIR; |
578 | #else |
579 | const char* path = tic_fs_path(fs, name); |
580 | struct tic_stat_struct s; |
581 | const FsString* pathString = utf8ToString(path); |
582 | bool ret = tic_stat(pathString, &s) == 0 && S_ISDIR(s.st_mode); |
583 | freeString(pathString); |
584 | |
585 | return ret; |
586 | #endif |
587 | } |
588 | |
589 | void tic_fs_isdir_async(tic_fs* fs, const char* name, fs_isdir_callback callback, void* data) |
590 | { |
591 | if(isPublic(fs)) |
592 | { |
593 | EnumPublicDirsData enumPublicDirsData = { strdup(name), false, callback, data}; |
594 | tic_fs_enum(fs, onEnumPublicDirs, onEnumPublicDirsDone, MOVE(enumPublicDirsData)); |
595 | return; |
596 | } |
597 | |
598 | callback(tic_fs_isdir(fs, name), data); |
599 | } |
600 | |
601 | bool fs_write(const char* name, const void* buffer, s32 size) |
602 | { |
603 | #if defined(BAREMETALPI) |
604 | dbg("fs_write %s\n" , name); |
605 | FIL File; |
606 | FRESULT res = f_open (&File, name, FA_WRITE | FA_CREATE_ALWAYS); |
607 | if (res != FR_OK) |
608 | { |
609 | return false; |
610 | } |
611 | |
612 | u32 written=0; |
613 | |
614 | res = f_write(&File, buffer, size, &written); |
615 | |
616 | f_close(&File); |
617 | if (res != FR_OK) |
618 | { |
619 | return false; |
620 | } |
621 | if(written!=size) |
622 | { |
623 | dbg("Write size diff %d %d" , size, written); |
624 | return false; |
625 | } |
626 | return true; |
627 | #else |
628 | const FsString* pathString = utf8ToString(name); |
629 | FILE* file = tic_fopen(pathString, _S("wb" )); |
630 | freeString(pathString); |
631 | |
632 | if(file) |
633 | { |
634 | fwrite(buffer, 1, size, file); |
635 | fclose(file); |
636 | |
637 | #if defined(__EMSCRIPTEN__) |
638 | syncfs(); |
639 | #endif |
640 | |
641 | return true; |
642 | } |
643 | |
644 | return false; |
645 | #endif |
646 | } |
647 | |
648 | void* fs_read(const char* path, s32* size) |
649 | { |
650 | #if defined(BAREMETALPI) |
651 | dbg("fs_read %s\n" , path); |
652 | FILINFO fi; |
653 | FRESULT res = f_stat(path, &fi); |
654 | if(res!=FR_OK) return NULL; |
655 | FIL file; |
656 | res = f_open (&file, path, FA_READ | FA_OPEN_EXISTING); |
657 | if(res!=FR_OK) return NULL; |
658 | *size = fi.fsize; // size is in output! |
659 | void* buffer = malloc(*size); |
660 | UINT read = 0; |
661 | res = f_read(&file, buffer, fi.fsize, &read); |
662 | |
663 | f_close(&file); |
664 | if(read!=(*size)) return NULL; |
665 | |
666 | return buffer; |
667 | |
668 | #else |
669 | const FsString* pathString = utf8ToString(path); |
670 | FILE* file = tic_fopen(pathString, _S("rb" )); |
671 | freeString(pathString); |
672 | |
673 | void* buffer = NULL; |
674 | |
675 | if(file) |
676 | { |
677 | |
678 | fseek(file, 0, SEEK_END); |
679 | *size = ftell(file); |
680 | fseek(file, 0, SEEK_SET); |
681 | |
682 | if((buffer = malloc(*size)) && fread(buffer, *size, 1, file)) {} |
683 | |
684 | fclose(file); |
685 | } |
686 | |
687 | return buffer; |
688 | |
689 | #endif |
690 | } |
691 | |
692 | bool fs_exists(const char* name) |
693 | { |
694 | #if defined(BAREMETALPI) |
695 | dbg("fs_exists %s\n" , name); |
696 | FILINFO s; |
697 | |
698 | FRESULT res = f_stat(name, &s); |
699 | return res == FR_OK; |
700 | #else |
701 | struct tic_stat_struct s; |
702 | |
703 | const FsString* pathString = utf8ToString(name); |
704 | bool ret = tic_stat(pathString, &s) == 0; |
705 | freeString(pathString); |
706 | |
707 | return ret; |
708 | #endif |
709 | } |
710 | |
711 | bool tic_fs_exists(tic_fs* fs, const char* name) |
712 | { |
713 | return fs_exists(tic_fs_path(fs, name)); |
714 | } |
715 | |
716 | u64 fs_date(const char* path) |
717 | { |
718 | #if defined(BAREMETALPI) |
719 | dbg("fs_date %s\n" , path); |
720 | // TODO BAREMETALPI |
721 | return 0; |
722 | #else |
723 | struct tic_stat_struct s; |
724 | |
725 | const FsString* pathString = utf8ToString(path); |
726 | s32 ret = tic_stat(pathString, &s); |
727 | freeString(pathString); |
728 | |
729 | if(ret == 0 && S_ISREG(s.st_mode)) |
730 | { |
731 | return s.st_mtime; |
732 | } |
733 | |
734 | return 0; |
735 | #endif |
736 | } |
737 | |
738 | bool tic_fs_save(tic_fs* fs, const char* name, const void* data, s32 size, bool overwrite) |
739 | { |
740 | if(!overwrite) |
741 | { |
742 | if(tic_fs_exists(fs, name)) |
743 | return false; |
744 | } |
745 | |
746 | return fs_write(tic_fs_path(fs, name), data, size); |
747 | } |
748 | |
749 | bool tic_fs_saveroot(tic_fs* fs, const char* name, const void* data, s32 size, bool overwrite) |
750 | { |
751 | const char* path = tic_fs_pathroot(fs, name); |
752 | |
753 | if(!overwrite) |
754 | { |
755 | if(fs_exists(path)) |
756 | return false; |
757 | } |
758 | |
759 | return fs_write(path, data, size); |
760 | } |
761 | |
762 | typedef struct |
763 | { |
764 | tic_fs* fs; |
765 | fs_load_callback done; |
766 | void* data; |
767 | char* cachePath; |
768 | } LoadFileByHashData; |
769 | |
770 | static void fileByHashLoaded(const net_get_data* netData) |
771 | { |
772 | LoadFileByHashData* loadFileByHashData = netData->calldata; |
773 | |
774 | if (netData->type == net_get_done) |
775 | { |
776 | tic_fs_saveroot(loadFileByHashData->fs, loadFileByHashData->cachePath, netData->done.data, netData->done.size, false); |
777 | loadFileByHashData->done(netData->done.data, netData->done.size, loadFileByHashData->data); |
778 | } |
779 | |
780 | switch (netData->type) |
781 | { |
782 | case net_get_done: |
783 | case net_get_error: |
784 | |
785 | free(loadFileByHashData->cachePath); |
786 | free(loadFileByHashData); |
787 | break; |
788 | default: break; |
789 | } |
790 | } |
791 | |
792 | void tic_fs_hashload(tic_fs* fs, const char* name, const char* hash, fs_load_callback callback, void* data) |
793 | { |
794 | #if defined(BAREMETALPI) |
795 | // TODO BAREMETALPI |
796 | return; |
797 | #else |
798 | |
799 | char cachePath[TICNAME_MAX]; |
800 | snprintf(cachePath, sizeof cachePath, TIC_CACHE "%s.tic" , hash); |
801 | |
802 | { |
803 | s32 size = 0; |
804 | void* buffer = tic_fs_loadroot(fs, cachePath, &size); |
805 | if (buffer) |
806 | { |
807 | callback(buffer, size, data); |
808 | return; |
809 | } |
810 | } |
811 | |
812 | #if defined(BUILD_EDITORS) |
813 | char path[TICNAME_MAX]; |
814 | snprintf(path, sizeof path, "/cart/%s/%s" , hash, name); |
815 | |
816 | LoadFileByHashData loadFileByHashData = { fs, callback, data, strdup(cachePath) }; |
817 | tic_net_get(fs->net, path, fileByHashLoaded, MOVE(loadFileByHashData)); |
818 | #endif |
819 | |
820 | #endif |
821 | } |
822 | |
823 | void* tic_fs_load(tic_fs* fs, const char* name, s32* size) |
824 | { |
825 | #if defined(BAREMETALPI) |
826 | dbg("tic_fs_load x %s\n" , name); |
827 | dbg("fs.dir %s\n" , fs->dir); |
828 | dbg("fs.work %s\n" , fs->work); |
829 | |
830 | if(isPublic(fs)) |
831 | { |
832 | dbg("Public ??\n" ); |
833 | return NULL; |
834 | } |
835 | else |
836 | { |
837 | dbg("non public \n" ); |
838 | const char* fp = tic_fs_path(fs, name); |
839 | dbg("loading: %s\n" , fp); |
840 | |
841 | |
842 | FILINFO fi; |
843 | FRESULT res = f_stat(fp, &fi); |
844 | dbg("fstat done %d \n" , res); |
845 | |
846 | if(res!=FR_OK) |
847 | { |
848 | dbg("NO F_STAT %d\n" , res); |
849 | return NULL; |
850 | } |
851 | FIL file; |
852 | res = f_open (&file, fp, FA_READ | FA_OPEN_EXISTING); |
853 | if(res!=FR_OK) |
854 | { |
855 | dbg("NO F_OPEN %d\n" , res); |
856 | return NULL; |
857 | } |
858 | dbg("BUFFERING %d\n" , res); |
859 | |
860 | void* buffer = malloc(fi.fsize); |
861 | dbg("BUFFERED %d\n" , fi.fsize); |
862 | |
863 | UINT read = 0; |
864 | res = f_read(&file, buffer, fi.fsize, &read); |
865 | dbg("F_READ %d %ld\n" , res, read); |
866 | |
867 | f_close(&file); |
868 | if(read!=fi.fsize) |
869 | { |
870 | dbg("NO F_READ %d \n" , res); |
871 | return NULL; |
872 | } |
873 | dbg("RETURNING!!\n" ); |
874 | *size = fi.fsize; |
875 | return buffer; |
876 | } |
877 | return NULL; |
878 | #else |
879 | |
880 | const FsString* pathString = utf8ToString(tic_fs_path(fs, name)); |
881 | FILE* file = tic_fopen(pathString, _S("rb" )); |
882 | freeString(pathString); |
883 | |
884 | void* ptr = NULL; |
885 | |
886 | if(file) |
887 | { |
888 | fseek(file, 0, SEEK_END); |
889 | *size = ftell(file); |
890 | fseek(file, 0, SEEK_SET); |
891 | |
892 | u8* buffer = malloc(*size); |
893 | |
894 | if(buffer && fread(buffer, *size, 1, file)) ptr = buffer; |
895 | |
896 | fclose(file); |
897 | } |
898 | |
899 | return ptr; |
900 | |
901 | #endif |
902 | } |
903 | |
904 | void* tic_fs_loadroot(tic_fs* fs, const char* name, s32* size) |
905 | { |
906 | return fs_read(tic_fs_pathroot(fs, name), size); |
907 | } |
908 | |
909 | bool tic_fs_makedir(tic_fs* fs, const char* name) |
910 | { |
911 | #if defined(BAREMETALPI) |
912 | // TODO BAREMETALPI |
913 | dbg("makeDir %s\n" , name); |
914 | |
915 | const FsString* pathString = tic_fs_path(fs, name); |
916 | char* path = strdup(pathString); |
917 | if (path && *path) { // make sure result has at least |
918 | if (path[strlen(path) - 1] == '/') // one character |
919 | path[strlen(path) - 1] = 0; |
920 | } |
921 | |
922 | FRESULT res = f_mkdir(path); |
923 | if(res != FR_OK) |
924 | { |
925 | dbg("Could not mkdir %s\n" , name); |
926 | } |
927 | free(path); |
928 | return (res != FR_OK); |
929 | #else |
930 | |
931 | const FsString* pathString = utf8ToString(tic_fs_path(fs, name)); |
932 | int result = tic_mkdir(pathString); |
933 | freeString(pathString); |
934 | |
935 | #if defined(__EMSCRIPTEN__) |
936 | syncfs(); |
937 | #endif |
938 | return result; |
939 | #endif |
940 | } |
941 | |
942 | void tic_fs_openfolder(tic_fs* fs) |
943 | { |
944 | const char* path = tic_fs_path(fs, "" ); |
945 | |
946 | if(isPublic(fs)) |
947 | path = fs->dir; |
948 | |
949 | tic_sys_open_path(path); |
950 | } |
951 | |
952 | #if defined(__TIC_WINDOWS__) |
953 | #define SEP "\\" |
954 | #else |
955 | #define SEP "/" |
956 | #endif |
957 | |
958 | tic_fs* tic_fs_create(const char* path, tic_net* net) |
959 | { |
960 | tic_fs* fs = (tic_fs*)calloc(1, sizeof(tic_fs)); |
961 | |
962 | strncpy(fs->dir, path, TICNAME_MAX); |
963 | |
964 | if(path[strlen(path) - 1] != SEP[0]) |
965 | strcat(fs->dir, SEP); |
966 | |
967 | fs->net = net; |
968 | |
969 | return fs; |
970 | } |
971 | |