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