1 /*
2 * Copyright (c) 2012-2014, Bruno Levy
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * If you modify this software, you should include a notice giving the
30 * name of the person performing the modification, the date of modification,
31 * and the reason for such modification.
32 *
33 * Contact: Bruno Levy
34 *
35 * Bruno.Levy@inria.fr
36 * http://www.loria.fr/~levy
37 *
38 * ALICE Project
39 * LORIA, INRIA Lorraine,
40 * Campus Scientifique, BP 239
41 * 54506 VANDOEUVRE LES NANCY CEDEX
42 * FRANCE
43 *
44 */
45
46 #include <geogram/basic/file_system.h>
47 #include <geogram/basic/line_stream.h>
48 #include <geogram/basic/logger.h>
49 #include <geogram/basic/string.h>
50
51 #include <iostream>
52 #include <fstream>
53 #include <assert.h>
54
55 #ifdef GEO_OS_WINDOWS
56 #include <windows.h>
57 #include <io.h>
58 #include <shlobj.h>
59 #else
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <fcntl.h>
63 #include <dirent.h>
64 #include <unistd.h>
65 #include <stdio.h>
66 #endif
67
68 #ifdef GEO_OS_EMSCRIPTEN
69 #include <emscripten.h>
70 #endif
71
72 namespace {
73 using namespace GEO;
74
75 /******************* Windows file system implementation **********************/
76 #ifdef GEO_OS_WINDOWS
77
78 class FileSystemRootNode : public FileSystem::Node {
79 public:
is_file(const std::string & path)80 bool is_file(const std::string& path) override {
81 WIN32_FIND_DATA file;
82 HANDLE file_handle = FindFirstFile(path.c_str(), &file);
83 if(file_handle == INVALID_HANDLE_VALUE) {
84 return false;
85 }
86 FindClose(file_handle);
87 return (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
88 }
89
is_directory(const std::string & path)90 bool is_directory(const std::string& path) override {
91 WIN32_FIND_DATA file;
92 HANDLE file_handle = FindFirstFile(path.c_str(), &file);
93 if(file_handle == INVALID_HANDLE_VALUE) {
94 return false;
95 }
96 FindClose(file_handle);
97 return (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
98 }
99
create_directory(const std::string & path_in)100 bool create_directory(const std::string& path_in) override {
101 std::vector<std::string> path;
102 String::split_string(path_in, '/', path);
103 std::string current;
104 int start_at = 0;
105 if(path_in.at(1) == ':') {
106 current += path_in.at(0);
107 current += path_in.at(1);
108 start_at = 1;
109 }
110 else if(path_in.at(0) != '/' && path_in.at(0) != '\\') {
111 current += get_current_working_directory();
112 }
113 for(size_t i = size_t(start_at); i < path.size(); i++) {
114 current += "/";
115 current += path[i];
116 if(path[i].at(0) == '.' &&
117 path[i].at(1) == '.' &&
118 path[i].length() == 2
119 ) {
120 continue;
121 }
122 if(!is_directory(current)) {
123 if(!::CreateDirectory(current.c_str(), nullptr)) {
124 Logger::err("OS")
125 << "Could not create directory "
126 << current << std::endl;
127 return false;
128 }
129 }
130 }
131 return true;
132 }
133
delete_directory(const std::string & path)134 bool delete_directory(const std::string& path) override {
135 return ::RemoveDirectory(path.c_str()) != FALSE;
136 }
137
delete_file(const std::string & path)138 bool delete_file(const std::string& path) override {
139 return ::DeleteFile(path.c_str()) != FALSE;
140 }
141
get_directory_entries(const std::string & path,std::vector<std::string> & result)142 bool get_directory_entries(
143 const std::string& path, std::vector<std::string>& result
144 ) override {
145 std::string dirname = path;
146 if(dirname.at(dirname.size() - 1) != '/' &&
147 dirname.at(dirname.size() - 1) != '\\'
148 ) {
149 dirname += '/';
150 }
151
152 std::string current_directory = get_current_working_directory();
153
154 bool dir_found = set_current_working_directory(dirname);
155 if(!dir_found) {
156 return false;
157 }
158
159 WIN32_FIND_DATA file;
160 HANDLE file_handle = FindFirstFile("*.*", &file);
161 if(file_handle != INVALID_HANDLE_VALUE) {
162 do {
163 std::string file_name = file.cFileName;
164 if(file_name != "." && file_name != "..") {
165 file_name = dirname + file_name;
166 flip_slashes(file_name);
167 result.push_back(file_name);
168 }
169 } while(FindNextFile(file_handle, &file));
170 FindClose(file_handle);
171 }
172 set_current_working_directory(current_directory);
173 return true;
174 }
175
get_current_working_directory()176 std::string get_current_working_directory() override {
177 char buf[2048];
178 std::string result = "";
179 if(GetCurrentDirectory(sizeof(buf), buf)) {
180 result = buf;
181 flip_slashes(result);
182 }
183 return result;
184 }
185
set_current_working_directory(const std::string & path_in)186 bool set_current_working_directory(
187 const std::string& path_in
188 ) override {
189 std::string path = path_in;
190 if(
191 path.at(path.size() - 1) != '/' &&
192 path.at(path.size() - 1) != '\\') {
193 path += "/";
194 }
195 return SetCurrentDirectory(path.c_str()) != -1;
196 }
197
rename_file(const std::string & old_name,const std::string & new_name)198 bool rename_file(
199 const std::string& old_name, const std::string& new_name
200 ) override {
201 return ::rename(old_name.c_str(), new_name.c_str()) != -1;
202 }
203
get_time_stamp(const std::string & path)204 Numeric::uint64 get_time_stamp(const std::string& path) override {
205 WIN32_FILE_ATTRIBUTE_DATA infos;
206 if(!GetFileAttributesEx(
207 path.c_str(), GetFileExInfoStandard, &infos)
208 ) {
209 return 0;
210 }
211 return infos.ftLastWriteTime.dwLowDateTime;
212 }
213
set_executable_flag(const std::string & filename)214 bool set_executable_flag(const std::string& filename) override {
215 geo_argused(filename);
216 return false;
217 }
218
touch(const std::string & filename)219 bool touch(const std::string& filename) override {
220 HANDLE hfile = CreateFile(
221 filename.c_str(),
222 GENERIC_READ | GENERIC_WRITE,
223 FILE_SHARE_READ | FILE_SHARE_WRITE,
224 nullptr,
225 OPEN_EXISTING,
226 FILE_ATTRIBUTE_NORMAL,
227 nullptr
228 );
229 if(hfile == INVALID_HANDLE_VALUE) {
230 Logger::err("FileSystem")
231 << "Could not touch file:"
232 << filename
233 << std::endl;
234 return false;
235 }
236 SYSTEMTIME now_system;
237 FILETIME now_file;
238 GetSystemTime(&now_system);
239 SystemTimeToFileTime(&now_system, &now_file);
240 SetFileTime(hfile,nullptr,&now_file,&now_file);
241 CloseHandle(hfile);
242 return true;
243 }
244
normalized_path(const std::string & path_in)245 std::string normalized_path(const std::string& path_in) override {
246 if(path_in == "") {
247 return "";
248 }
249 std::string path = path_in;
250 std::string result;
251 TCHAR buffer[MAX_PATH];
252 GetFullPathName(path.c_str(), MAX_PATH, buffer, nullptr);
253 result = std::string(buffer);
254 flip_slashes(result);
255 return result;
256 }
257
258
home_directory()259 std::string home_directory() override {
260 std::string home;
261 wchar_t folder[MAX_PATH+1];
262 HRESULT hr = SHGetFolderPathW(0, CSIDL_PROFILE, 0, 0, folder);
263 if (SUCCEEDED(hr)) {
264 char result[MAX_PATH+1];
265 wcstombs(result, folder, MAX_PATH);
266 home=std::string(result);
267 flip_slashes(home);
268 }
269 return home;
270 }
271
documents_directory()272 std::string documents_directory() override {
273 std::string home;
274 wchar_t folder[MAX_PATH+1];
275 HRESULT hr = SHGetFolderPathW(0, CSIDL_MYDOCUMENTS, 0, 0, folder);
276 if (SUCCEEDED(hr)) {
277 char result[MAX_PATH+1];
278 wcstombs(result, folder, MAX_PATH);
279 home=std::string(result);
280 flip_slashes(home);
281 }
282 return home;
283 }
284 };
285
286 #else
287
288 /***** Unix/Mac/Android/Emscripten file system implementation ****************/
289
290 class FileSystemRootNode : public FileSystem::Node {
291 public:
is_file(const std::string & path)292 bool is_file(const std::string& path) override {
293 // We use 'stat' and not 'lstat' since
294 // we want to be able to follow symbolic
295 // links (required for instance when testing
296 // input path in GOMGEN, there can be some
297 // symlinks in system includes)
298 struct stat buff;
299 if(stat(path.c_str(), &buff)) {
300 return false;
301 }
302 return S_ISREG(buff.st_mode);
303 }
304
is_directory(const std::string & path)305 bool is_directory(const std::string& path) override {
306 // We use 'stat' and not 'lstat' since
307 // we want to be able to follow symbolic
308 // links (required for instance when testing
309 // input path in GOMGEN, there can be some
310 // symlinks in system includes)
311 struct stat buff;
312 if(stat(path.c_str(), &buff)) {
313 return false;
314 }
315 return S_ISDIR(buff.st_mode);
316 }
317
create_directory(const std::string & path_in)318 bool create_directory(const std::string& path_in) override {
319 std::vector<std::string> path;
320 String::split_string(path_in, '/', path);
321 std::string current;
322 for(size_t i = 0; i < path.size(); i++) {
323 current += "/";
324 current += path[i];
325 if(!is_directory(current)) {
326 if(mkdir(current.c_str(), 0755) != 0) {
327 Logger::err("OS")
328 << "Could not create directory "
329 << current << std::endl;
330 return false;
331 }
332 }
333 }
334 return true;
335 }
336
delete_directory(const std::string & path)337 bool delete_directory(const std::string& path) override {
338 return rmdir(path.c_str()) == 0;
339 }
340
delete_file(const std::string & path)341 bool delete_file(const std::string& path) override {
342 return unlink(path.c_str()) == 0;
343 }
344
get_directory_entries(const std::string & path,std::vector<std::string> & result)345 bool get_directory_entries(
346 const std::string& path, std::vector<std::string>& result
347 ) override {
348 std::string dirname = path;
349 if(dirname[dirname.length() - 1] != '/') {
350 dirname += "/";
351 }
352 DIR* dir = opendir(dirname.c_str());
353 if(dir == nullptr) {
354 Logger::err("OS")
355 << "Could not open directory " << dirname
356 << std::endl;
357 #ifdef GEO_OS_ANDROID
358 static bool first_time = true;
359 if(first_time) {
360 Logger::err("OS")
361 << "You may need to grant the \"Storage\" permission"
362 << std::endl
363 << "to this application"
364 << std::endl;
365 Logger::err("OS")
366 << "(see Parameters/Applications"
367 << std::endl
368 << "in Android perferences)"
369 << std::endl;
370 first_time = false;
371 }
372 #endif
373 return false;
374 }
375 struct dirent* entry = readdir(dir);
376 while(entry != nullptr) {
377 std::string current = std::string(entry->d_name);
378 // Ignore . and ..
379 if(current != "." && current != "..") {
380 if(dirname != "./") {
381 current = dirname + current;
382 }
383 // Ignore symbolic links and other special Unix stuff
384 if(is_file(current) || is_directory(current)) {
385 result.push_back(current);
386 }
387 }
388 entry = readdir(dir);
389 }
390 closedir(dir);
391 return true;
392 }
393
get_current_working_directory()394 std::string get_current_working_directory() override {
395 char buff[4096];
396 return std::string(getcwd(buff, 4096));
397 }
398
set_current_working_directory(const std::string & path)399 bool set_current_working_directory(const std::string& path) override {
400 return chdir(path.c_str()) == 0;
401 }
402
rename_file(const std::string & old_name,const std::string & new_name)403 bool rename_file(
404 const std::string& old_name, const std::string& new_name
405 ) override {
406 if(is_file(new_name)) {
407 return false;
408 }
409 return ::rename(old_name.c_str(), new_name.c_str()) == 0;
410 }
411
get_time_stamp(const std::string & path)412 Numeric::uint64 get_time_stamp(const std::string& path) override {
413 struct stat buffer;
414 if(!stat(path.c_str(), &buffer)) {
415 return Numeric::uint64(buffer.st_mtime);
416 }
417 return 0;
418 }
419
set_executable_flag(const std::string & filename)420 bool set_executable_flag(const std::string& filename) override {
421 geo_argused(filename);
422 if(::chmod(filename.c_str(), 0755) != 0) {
423 Logger::err("FileSyst")
424 << "Could not change file permissions for:"
425 << filename << std::endl;
426 return false;
427 }
428 return true;
429 }
430
touch(const std::string & filename)431 bool touch(const std::string& filename) override {
432 #ifdef GEO_OS_APPLE
433 {
434 struct stat buff;
435 int rc = stat(filename.c_str(), &buff);
436 if(rc != 0) { // FABIEN NOT SURE WE GET THE TOUCH
437 Logger::err("FileSystem")
438 << "Could not touch file:"
439 << filename
440 << std::endl;
441 return false;
442 }
443 return true;
444 }
445 #else
446 {
447 int rc = utimensat(
448 AT_FDCWD,
449 filename.c_str(),
450 nullptr,
451 0
452 );
453 if(rc != 0) {
454 Logger::err("FileSystem")
455 << "Could not touch file:"
456 << filename
457 << std::endl;
458 return false;
459 }
460 return true;
461 }
462 #endif
463 }
464
normalized_path(const std::string & path_in)465 std::string normalized_path(const std::string& path_in) override {
466
467 if(path_in == "") {
468 return "";
469 }
470
471 std::string path = path_in;
472 std::string result;
473
474 // If this is a relative path, prepend "./"
475 if(path[0] != '/') {
476 path = "./" + path;
477 }
478
479 char buffer[PATH_MAX];
480 char* p = realpath(path.c_str(), buffer);
481 if(p != nullptr) {
482 result = std::string(p);
483 } else {
484 // realpath() only works for existing paths and existing file,
485 // therefore we attempt calling it on the input path by adding
486 // one component at a time.
487 size_t pos = 1;
488 while(pos != std::string::npos) {
489 pos = path.find('/',pos);
490 if(pos != std::string::npos) {
491 std::string path_part = path.substr(0,pos);
492 p = realpath(path_part.c_str(), buffer);
493 if(p == nullptr) {
494 break;
495 } else {
496 result = std::string(p) +
497 path.substr(pos, path.length()-pos);
498 }
499 ++pos;
500 if(pos == path.length()) {
501 break;
502 }
503 }
504 }
505 }
506 flip_slashes(result);
507 return result;
508 }
509
510
home_directory()511 std::string home_directory() override {
512 std::string home;
513 #if defined GEO_OS_EMSCRIPTEN
514 home="/";
515 #else
516 char* result = getenv("HOME");
517 if(result != nullptr) {
518 home=result;
519 }
520 #endif
521 return home;
522 }
523
documents_directory()524 std::string documents_directory() override {
525 std::string home;
526 #if defined GEO_OS_EMSCRIPTEN
527 home="/";
528 #elif defined GEO_OS_ANDROID
529 char* result = getenv("EXTERNAL_STORAGE");
530 if(result != nullptr) {
531 home=result;
532 }
533 #else
534 char* result = getenv("HOME");
535 if(result != nullptr) {
536 home=result;
537 }
538 #endif
539 return home;
540 }
541
542 };
543 #endif
544
545 FileSystem::Node_var root_;
546 }
547
548
549 namespace GEO {
550
551 namespace FileSystem {
552
Node()553 Node::Node() {
554 }
555
~Node()556 Node::~Node() {
557 }
558
559 /******* OS-independent functions *************************************/
560
extension(const std::string & path)561 std::string Node::extension(const std::string& path) {
562 size_t len = path.length();
563 if(len != 0) {
564 for(size_t i = len - 1; i != 0; i--) {
565 if(path[i] == '/' || path[i] == '\\') {
566 break;
567 }
568 if(path[i] == '.') {
569 return String::to_lowercase(path.substr(i + 1));
570 }
571 }
572 }
573 return std::string();
574 }
575
base_name(const std::string & path,bool remove_extension)576 std::string Node::base_name(
577 const std::string& path, bool remove_extension
578 ) {
579 long int len = (long int)(path.length());
580 if(len == 0) {
581 return std::string();
582 }
583 long int dot_pos = len;
584 long int i;
585 for(i = len - 1; i >= 0; i--) {
586 if(path[size_t(i)] == '/' || path[size_t(i)] == '\\') {
587 break;
588 }
589 if(remove_extension && path[size_t(i)] == '.') {
590 dot_pos = i;
591 }
592 }
593 return path.substr(size_t(i + 1), size_t(dot_pos - i - 1));
594 }
595
dir_name(const std::string & path)596 std::string Node::dir_name(const std::string& path) {
597 size_t len = path.length();
598 if(len != 0) {
599 for(size_t i = len - 1; i != 0; i--) {
600 if(path[i] == '/' || path[i] == '\\') {
601 return path.substr(0, i);
602 }
603 }
604 }
605 return ".";
606 }
607
get_directory_entries(const std::string & path,std::vector<std::string> & result,bool recursive)608 void Node::get_directory_entries(
609 const std::string& path,
610 std::vector<std::string>& result, bool recursive
611 ) {
612 // TODO: seems to be bugged, enters infinite recursion...
613 get_directory_entries(path, result);
614 if(recursive) {
615 for(size_t i = 0; i < result.size(); i++) {
616 if(is_directory(result[i])) {
617 get_directory_entries(result[i], result, true);
618 }
619 }
620 }
621 }
622
get_files(const std::string & path,std::vector<std::string> & result,bool recursive)623 void Node::get_files(
624 const std::string& path,
625 std::vector<std::string>& result, bool recursive
626 ) {
627 std::vector<std::string> entries;
628 get_directory_entries(path, entries, recursive);
629 for(size_t i = 0; i < entries.size(); i++) {
630 if(is_file(entries[i])) {
631 result.push_back(entries[i]);
632 }
633 }
634 }
635
get_subdirectories(const std::string & path,std::vector<std::string> & result,bool recursive)636 void Node::get_subdirectories(
637 const std::string& path,
638 std::vector<std::string>& result, bool recursive
639 ) {
640 std::vector<std::string> entries;
641 get_directory_entries(path, entries, recursive);
642 for(size_t i = 0; i < entries.size(); i++) {
643 if(is_directory(entries[i])) {
644 result.push_back(entries[i]);
645 }
646 }
647 }
648
flip_slashes(std::string & s)649 void Node::flip_slashes(std::string& s) {
650 for(size_t i = 0; i < s.length(); i++) {
651 if(s[i] == '\\') {
652 s[i] = '/';
653 }
654 }
655 }
656
copy_file(const std::string & from,const std::string & to)657 bool Node::copy_file(const std::string& from, const std::string& to) {
658 FILE* fromf = fopen(from.c_str(), "rb");
659 if(fromf == nullptr) {
660 Logger::err("FileSyst")
661 << "Could not open source file:" << from << std::endl;
662 return false;
663 }
664 FILE* tof = fopen(to.c_str(),"wb");
665 if(tof == nullptr) {
666 Logger::err("FileSyst")
667 << "Could not create file:" << to << std::endl;
668 fclose(fromf);
669 return false;
670 }
671
672 bool result = true;
673 const size_t buff_size = 4096;
674 char buff[buff_size];
675 size_t rdsize = 0;
676 do {
677 rdsize = fread(buff, 1, buff_size, fromf);
678 if(fwrite(buff, 1, rdsize, tof) != rdsize) {
679 Logger::err("FileSyst") << "I/O error when writing to file:"
680 << to << std::endl;
681 result = false;
682 break;
683 }
684 } while(rdsize == 4096);
685
686 fclose(fromf);
687 fclose(tof);
688 return result;
689 }
690
691 /*************** OS-dependent functions *******************************/
692
is_file(const std::string & path)693 bool Node::is_file(const std::string& path) {
694 geo_argused(path);
695 return false;
696 }
697
is_directory(const std::string & path)698 bool Node::is_directory(const std::string& path) {
699 geo_argused(path);
700 return false;
701 }
702
create_directory(const std::string & path)703 bool Node::create_directory(const std::string& path) {
704 geo_argused(path);
705 return false;
706 }
707
delete_directory(const std::string & path)708 bool Node::delete_directory(const std::string& path) {
709 geo_argused(path);
710 return false;
711 }
712
delete_file(const std::string & path)713 bool Node::delete_file(const std::string& path) {
714 geo_argused(path);
715 return false;
716 }
717
get_directory_entries(const std::string & path,std::vector<std::string> & result)718 bool Node::get_directory_entries(
719 const std::string& path, std::vector<std::string>& result
720 ) {
721 geo_argused(path);
722 geo_argused(result);
723 return false;
724 }
725
get_current_working_directory()726 std::string Node::get_current_working_directory() {
727 return "/";
728 }
729
set_current_working_directory(const std::string & path)730 bool Node::set_current_working_directory(const std::string& path) {
731 geo_argused(path);
732 return false;
733 }
734
rename_file(const std::string & old_name,const std::string & new_name)735 bool Node::rename_file(
736 const std::string& old_name, const std::string& new_name
737 ) {
738 geo_argused(old_name);
739 geo_argused(new_name);
740 return false;
741 }
742
get_time_stamp(const std::string & path)743 Numeric::uint64 Node::get_time_stamp(const std::string& path) {
744 geo_argused(path);
745 return 0;
746 }
747
set_executable_flag(const std::string & filename)748 bool Node::set_executable_flag(const std::string& filename) {
749 geo_argused(filename);
750 return false;
751 }
752
touch(const std::string & filename)753 bool Node::touch(const std::string& filename) {
754 geo_argused(filename);
755 return false;
756 }
757
normalized_path(const std::string & path)758 std::string Node::normalized_path(const std::string& path) {
759 std::vector<std::string> components;
760 String::split_string(path, '/', components);
761 std::vector<std::string> new_components;
762 for(auto c: components) {
763 if(c == ".") {
764 } else if(c == "..") {
765 if(new_components.size() != 0) {
766 new_components.pop_back();
767 }
768 } else {
769 new_components.push_back(c);
770 }
771 }
772 std::string result;
773 for(auto c: new_components) {
774 result += "/";
775 result += c;
776 }
777 return result;
778 }
779
home_directory()780 std::string Node::home_directory() {
781 return "/";
782 }
783
documents_directory()784 std::string Node::documents_directory() {
785 return "/";
786 }
787
load_file_as_string(const std::string & path)788 std::string Node::load_file_as_string(const std::string& path) {
789 std::string result;
790 FILE* f = fopen(path.c_str(),"r");
791 if(f != nullptr) {
792 // Get file length
793 fseek(f, 0L, SEEK_END);
794 size_t length = size_t(ftell(f));
795 fseek(f, 0L, SEEK_SET);
796 if(length != 0) {
797 result.resize(length);
798 size_t read_length = fread(&result[0], 1, length, f);
799 if(read_length != length) {
800 Logger::warn("FileSystem")
801 << "Problem occured when reading "
802 << path
803 << std::endl;
804
805 }
806 }
807 fclose(f);
808 }
809 return result;
810 }
811
812 /*********************************************************************/
813
copy_file(const std::string & from,const std::string & to)814 bool MemoryNode::copy_file(
815 const std::string& from, const std::string& to
816 ) {
817 const char* contents = get_file_contents(from);
818 if(contents == nullptr) {
819 return false;
820 }
821 return create_file(to, contents);
822 }
823
load_file_as_string(const std::string & path)824 std::string MemoryNode::load_file_as_string(const std::string& path) {
825 std::string result;
826 std::string subdir;
827 std::string rest;
828 split_path(path, subdir, rest);
829 if(subdir == "") {
830 auto it = files_.find(rest);
831 if(it != files_.end()) {
832 result = std::string(it->second);
833 }
834 } else {
835 auto it = subnodes_.find(subdir);
836 if(it != subnodes_.end()) {
837 result = it->second->load_file_as_string(rest);
838 }
839 }
840 return result;
841 }
842
is_file(const std::string & path)843 bool MemoryNode::is_file(const std::string& path) {
844 std::string result;
845 std::string subdir;
846 std::string rest;
847 split_path(path, subdir, rest);
848 if(subdir == "") {
849 return(files_.find(rest) != files_.end());
850 } else {
851 auto it = subnodes_.find(subdir);
852 if(it == subnodes_.end()) {
853 return false;
854 }
855 return it->second->is_file(rest);
856 }
857 }
858
is_directory(const std::string & path)859 bool MemoryNode::is_directory(const std::string& path) {
860 std::string result;
861 std::string subdir;
862 std::string rest;
863 split_path(path, subdir, rest);
864 if(subdir == "") {
865 return(subnodes_.find(rest) != subnodes_.end());
866 } else {
867 auto it = subnodes_.find(subdir);
868 if(it == subnodes_.end()) {
869 return false;
870 }
871 return it->second->is_directory(rest);
872 }
873 }
874
create_directory(const std::string & path)875 bool MemoryNode::create_directory(const std::string& path) {
876 std::string result;
877 std::string subdir;
878 std::string rest;
879 split_path(path, subdir, rest);
880 if(subdir == "") {
881 if(subnodes_.find(path) != subnodes_.end()) {
882 return false;
883 }
884 subnodes_[path] = new MemoryNode(path_ + path + "/");
885 return true;
886 } else {
887 auto it = subnodes_.find(subdir);
888 if(it == subnodes_.end()) {
889 subnodes_[subdir] = new MemoryNode(path_ + subdir + "/");
890 }
891 return it->second->create_directory(rest);
892 }
893 }
894
delete_directory(const std::string & path)895 bool MemoryNode::delete_directory(const std::string& path) {
896 std::string result;
897 std::string subdir;
898 std::string rest;
899 split_path(path, subdir, rest);
900 if(subdir == "") {
901 auto it = subnodes_.find(rest);
902 if(it == subnodes_.end()) {
903 return false;
904 }
905 subnodes_.erase(it);
906 return true;
907 } else {
908 auto it = subnodes_.find(subdir);
909 if(it == subnodes_.end()) {
910 return false;
911 }
912 return it->second->delete_directory(rest);
913 }
914 }
915
delete_file(const std::string & path)916 bool MemoryNode::delete_file(const std::string& path) {
917 std::string result;
918 std::string subdir;
919 std::string rest;
920 split_path(path, subdir, rest);
921 if(subdir == "") {
922 auto it = files_.find(rest);
923 if(it == files_.end()) {
924 return false;
925 }
926 files_.erase(it);
927 return true;
928 } else {
929 auto it = subnodes_.find(subdir);
930 if(it == subnodes_.end()) {
931 return false;
932 }
933 return it->second->delete_file(rest);
934 }
935 }
936
get_directory_entries(const std::string & path,std::vector<std::string> & result)937 bool MemoryNode::get_directory_entries(
938 const std::string& path, std::vector<std::string>& result
939 ) {
940 std::string subdir;
941 std::string rest;
942 split_path(path, subdir, rest);
943 if(subdir == "" && rest == "") {
944 result.clear();
945 for(auto it : subnodes_) {
946 result.push_back(path_ + it.first);
947 }
948 for(auto it : files_) {
949 result.push_back(path_ + it.first);
950 }
951 return true;
952 } else {
953 if(subdir == "") {
954 subdir = rest;
955 rest = "";
956 }
957 auto it = subnodes_.find(subdir);
958 if(it == subnodes_.end()) {
959 return false;
960 }
961 return it->second->get_directory_entries(rest, result);
962 }
963 }
964
rename_file(const std::string & from,const std::string & to)965 bool MemoryNode::rename_file(
966 const std::string& from, const std::string& to
967 ) {
968 const char* contents = get_file_contents(from);
969 if(contents == nullptr) {
970 return false;
971 }
972 if(!delete_file(from)) {
973 return false;
974 }
975 return create_file(to, contents);
976 }
977
get_file_contents(const std::string & path)978 const char* MemoryNode::get_file_contents(const std::string& path) {
979 std::string subdir;
980 std::string rest;
981 split_path(path, subdir, rest);
982 const char* result = nullptr;
983 if(subdir == "") {
984 auto it = files_.find(rest);
985 if(it != files_.end()) {
986 result = it->second;
987 }
988 } else {
989 auto it = subnodes_.find(subdir);
990 if(it != subnodes_.end()) {
991 result = it->second->get_file_contents(rest);
992 }
993 }
994 return result;
995 }
996
create_file(const std::string & path,const char * content)997 bool MemoryNode::create_file(
998 const std::string& path, const char* content
999 ) {
1000 std::string subdir;
1001 std::string rest;
1002 split_path(path, subdir, rest);
1003 if(subdir == "") {
1004 if(files_.find(rest) != files_.end()) {
1005 return false;
1006 }
1007 files_[rest] = content;
1008 return true;
1009 } else {
1010 SmartPointer<MemoryNode>& n = subnodes_[subdir];
1011 if(n.is_null()) {
1012 n = new MemoryNode(path_ + subdir + "/");
1013 }
1014 return n->create_file(rest, content);
1015 }
1016 }
1017
split_path(const std::string & path,std::string & leadingsubdir,std::string & rest)1018 void MemoryNode::split_path(
1019 const std::string& path, std::string& leadingsubdir,
1020 std::string& rest
1021 ) {
1022 leadingsubdir = "";
1023 rest = "";
1024 std::vector<std::string> components;
1025 String::split_string(path, '/', components);
1026 if(components.size() == 0) {
1027 return;
1028 } else if(components.size() == 1) {
1029 leadingsubdir = "";
1030 rest = components[0];
1031 } else {
1032 leadingsubdir = components[0];
1033 for(size_t i=1; i<components.size(); ++i) {
1034 if(i != 1) {
1035 rest += "/";
1036 }
1037 rest += components[i];
1038 }
1039 }
1040 }
1041
1042 /*********************************************************************/
1043
initialize()1044 void initialize() {
1045 root_ = new FileSystemRootNode;
1046 }
1047
terminate()1048 void terminate() {
1049 root_.reset();
1050 }
1051
is_file(const std::string & path)1052 bool is_file(const std::string& path) {
1053 return root_->is_file(path);
1054 }
1055
is_directory(const std::string & path)1056 bool is_directory(const std::string& path) {
1057 return root_->is_directory(path);
1058 }
1059
create_directory(const std::string & path)1060 bool create_directory(const std::string& path) {
1061 return root_->create_directory(path);
1062 }
1063
delete_directory(const std::string & path)1064 bool delete_directory(const std::string& path) {
1065 return root_->delete_directory(path);
1066 }
1067
delete_file(const std::string & path)1068 bool delete_file(const std::string& path) {
1069 return root_->delete_file(path);
1070 }
1071
get_directory_entries(const std::string & path,std::vector<std::string> & result)1072 bool get_directory_entries(
1073 const std::string& path, std::vector<std::string>& result
1074 ) {
1075 return root_->get_directory_entries(path, result);
1076 }
1077
get_current_working_directory()1078 std::string get_current_working_directory() {
1079 return root_->get_current_working_directory();
1080 }
1081
set_current_working_directory(const std::string & path)1082 bool set_current_working_directory(
1083 const std::string& path
1084 ) {
1085 return root_->set_current_working_directory(path);
1086 }
1087
rename_file(const std::string & old_name,const std::string & new_name)1088 bool rename_file(
1089 const std::string& old_name, const std::string& new_name
1090 ) {
1091 return root_->rename_file(old_name, new_name);
1092 }
1093
get_time_stamp(const std::string & path)1094 Numeric::uint64 get_time_stamp(const std::string& path) {
1095 return root_->get_time_stamp(path);
1096 }
1097
extension(const std::string & path)1098 std::string extension(const std::string& path) {
1099 return root_->extension(path);
1100 }
1101
base_name(const std::string & path,bool remove_extension)1102 std::string base_name(
1103 const std::string& path, bool remove_extension
1104 ) {
1105 return root_->base_name(path, remove_extension);
1106 }
1107
dir_name(const std::string & path)1108 std::string dir_name(const std::string& path) {
1109 return root_->dir_name(path);
1110 }
1111
get_directory_entries(const std::string & path,std::vector<std::string> & result,bool recursive)1112 void get_directory_entries(
1113 const std::string& path,
1114 std::vector<std::string>& result, bool recursive
1115 ) {
1116 return root_->get_directory_entries(path, result, recursive);
1117 }
1118
get_files(const std::string & path,std::vector<std::string> & result,bool recursive)1119 void get_files(
1120 const std::string& path,
1121 std::vector<std::string>& result, bool recursive
1122 ) {
1123 return root_->get_files(path, result, recursive);
1124 }
1125
get_subdirectories(const std::string & path,std::vector<std::string> & result,bool recursive)1126 void get_subdirectories(
1127 const std::string& path,
1128 std::vector<std::string>& result, bool recursive
1129 ) {
1130 return root_->get_subdirectories(path, result, recursive);
1131 }
1132
flip_slashes(std::string & path)1133 void flip_slashes(std::string& path) {
1134 return root_->flip_slashes(path);
1135 }
1136
copy_file(const std::string & from,const std::string & to)1137 bool copy_file(
1138 const std::string& from, const std::string& to
1139 ) {
1140 return root_->copy_file(from, to);
1141 }
1142
set_executable_flag(const std::string & filename)1143 bool set_executable_flag(const std::string& filename) {
1144 return root_->set_executable_flag(filename);
1145 }
1146
touch(const std::string & filename)1147 bool touch(const std::string& filename) {
1148 return root_->touch(filename);
1149 }
1150
normalized_path(const std::string & path)1151 std::string normalized_path(const std::string& path) {
1152 return root_->normalized_path(path);
1153 }
1154
home_directory()1155 std::string home_directory() {
1156 return root_->home_directory();
1157 }
1158
documents_directory()1159 std::string documents_directory() {
1160 return root_->documents_directory();
1161 }
1162
get_root(Node * & root)1163 void get_root(Node*& root) {
1164 root = root_;
1165 }
1166 } // end namespace FileSystem
1167 } // end namespace GEO
1168
1169
1170
1171 #ifdef GEO_OS_EMSCRIPTEN
1172
1173 namespace GEO {
1174 namespace FileSystem {
1175 static void (*file_system_changed_callback_)() = nullptr;
set_file_system_changed_callback(void (* callback)())1176 void set_file_system_changed_callback(void(*callback)()) {
1177 file_system_changed_callback_ = callback;
1178 }
1179 }
1180 }
1181
1182 extern "C" {
1183 void file_system_changed_callback();
1184 }
1185
file_system_changed_callback()1186 EMSCRIPTEN_KEEPALIVE void file_system_changed_callback() {
1187 if(GEO::FileSystem::file_system_changed_callback_ != nullptr) {
1188 (*GEO::FileSystem::file_system_changed_callback_)();
1189 }
1190 }
1191
1192 #endif
1193