ecere,ecereCOM: support Emscriptem platform. add new Emscripten interface driver.
[sdk] / ecere / src / sys / GlobalAppSettings.ec
1 namespace sys;
2
3 #if defined(__WIN32__)
4 #define WIN32_LEAN_AND_MEAN
5 #define UNICODE
6 #define String String_
7 #define strlen _strlen
8 #include <windows.h>
9 #undef String
10 #undef strlen
11 #endif
12
13 import "System"
14 import "Container"
15
16 public enum GlobalSettingType
17 {
18    integer,
19    singleString,
20    stringList
21 };
22
23 // note: missing application data location support on windows
24 enum SettingsLocationType { none, specified, portable, home, winUserProfile, winHomeDrive, winSystemPath, winAllUsers, nixEtc };
25
26 public enum SettingsIOResult { error, success, fileNotFound, fileNotCompatibleWithDriver };
27
28 public class GlobalSettingsDriver
29 {
30    class_data const char * name;
31
32    class_property const char * name
33    {
34       set { class_data(name) = value; }
35       get { return class_data(name); }
36    }
37
38 public:
39    virtual SettingsIOResult ::Load(File f, GlobalSettings globalSettings);
40    virtual SettingsIOResult ::Save(File f, GlobalSettings globalSettings);
41
42 }
43
44 static subclass(GlobalSettingsDriver) GetGlobalSettingsDriver(const char * driverName)
45 {
46    OldLink link;
47    for(link = class(GlobalSettingsDriver).derivatives.first; link; link = link.next)
48    {
49       subclass(GlobalSettingsDriver) driver = link.data;
50       if(driver.name && !strcmp(driver.name, driverName))
51          return driver;
52    }
53
54    /*{
55       Module module;
56       char moduleName[MAX_LOCATION];
57       sprintf(moduleName, "ecere%s", driverName);
58       if(module = eModule_Load(__thisModule.application, moduleName, publicAccess))
59       {
60          //Class dsdClass = class(GlobalSettingsDriver);
61          Class dsdClass = eSystem_FindClass(module /-__thisModule.application-/, "GlobalSettingsDriver");
62
63          for(link = dsdClass.derivatives.first; link; link = link.next)
64          {
65             subclass(GlobalSettingsDriver) driver = link.data;
66             if(driver.name && !strcmp(driver.name, driverName))
67                return driver;
68          }
69       }
70    }*/
71    return null;
72 }
73
74 public class GlobalSettingsData
75 {
76 }
77
78 public class GlobalSettings
79 {
80 public:
81    property const char * settingsName
82    {
83       set { delete settingsName; if(value && value[0]) settingsName = CopyString(value); }
84       get { return settingsName; }
85    };
86    property const char * settingsExtension
87    {
88       set { delete settingsExtension; if(value && value[0]) settingsExtension = CopyString(value); }
89       get { return settingsExtension; }
90    };
91    property const char * settingsDirectory
92    {
93       set { delete settingsDirectory; if(value && value[0]) settingsDirectory = CopyUnixPath(value); }
94       get { return settingsDirectory; }
95    };
96    property const char * settingsLocation
97    {
98       set { delete settingsLocation; if(value && value[0]) settingsLocation = CopyUnixPath(value); }
99       get { return settingsLocation; }
100    };
101    property const char * settingsFilePath
102    {
103       set { delete settingsFilePath; if(value && value[0]) settingsFilePath = CopyUnixPath(value); }
104       get { return settingsFilePath; }
105    };
106    property bool allowDefaultLocations
107    {
108       set { allowDefaultLocations = value; }
109       get { return allowDefaultLocations; }
110    };
111    property bool allUsers
112    {
113       set { allUsers = value; }
114       get { return allUsers; }
115    };
116    property bool portable
117    {
118       set { portable = value; }
119       get { return portable; }
120    };
121
122    property const String driver
123    {
124       set
125       {
126          driverClass = null;
127          if(value)
128             driverClass = GetGlobalSettingsDriver(value);
129       }
130       get { return driverClass ? driverClass.name : null; }
131    }
132    GlobalSettingsData data;
133    GlobalSettingsData * dataOwner;
134    subclass(GlobalSettingsData) dataClass;
135
136 private:
137    char * settingsName;
138    char * settingsExtension;
139    char * settingsLocation;
140    char * settingsFilePath;
141    bool allowDefaultLocations;
142    bool allUsers;
143    bool portable;
144    bool globalPath;
145    char * settingsDirectory;
146    SettingsLocationType readType;
147    SettingsLocationType writeType;
148
149 #if !defined(__EMSCRIPTEN__)
150    FileMonitor settingsMonitor
151    {
152       this, fileChange = { modified = true };
153
154       bool OnFileNotify(FileChange action, const char * param)
155       {
156          OnAskReloadSettings();
157          return true;
158       }
159    };
160 #endif
161    File f;
162    bool locked;
163
164    subclass(GlobalSettingsDriver) driverClass;
165
166    ~GlobalSettings()
167    {
168       if(f)
169          f.Unlock(0, 0, true);
170       delete f;
171       delete settingsName;
172       delete settingsExtension;
173       delete settingsLocation;
174       delete settingsFilePath;
175       delete settingsDirectory;
176    }
177
178    char * PreparePath(SettingsLocationType type, const char * extension, bool create, bool unixStyle)
179    {
180       char * path = null;
181       char * buffer = new char[MAX_LOCATION];
182       switch(type)
183       {
184          case specified:
185             if(settingsLocation)
186             {
187                buffer[0] = '\0';
188                strcpy(buffer, settingsLocation);
189                path = GetFilePath(buffer, extension, create, false, false);
190             }
191             break;
192          case portable:
193             buffer[0] = '\0';
194             LocateModule(null, buffer);
195             StripLastDirectory(buffer, buffer);
196             path = GetFilePath(buffer, extension, create, false, false);
197             break;
198          case home:
199          {
200             // ~/.apprc
201             char * home = getenv("HOME");
202             if(home && home[0])
203             {
204                strcpy(buffer, home);
205                path = GetFilePath(buffer, extension, create, true, unixStyle);
206             }
207             break;
208          }
209 #if defined(__WIN32__)
210          case winUserProfile:
211          {
212             // Windows attempts: $USERPROFILE/app.ini
213             char * profile = getenv("USERPROFILE");
214             if(profile && profile[0])
215             {
216                strcpy(buffer, profile);
217                path = GetFilePath(buffer, extension, create, false, false);
218             }
219             break;
220          }
221          case winHomeDrive:
222          {
223             const char * homedrive = getenv("HOMEDRIVE");
224             const char * homepath = getenv("HOMEPATH");
225             if(homedrive && homedrive[0] && homepath && homepath[0])
226             {
227                strcpy(buffer, homedrive);
228                PathCatSlash(buffer, homepath);
229                path = GetFilePath(buffer, extension, create, false, false);
230             }
231             break;
232          }
233          case winSystemPath:
234          {
235             uint16 _wfilePath[MAX_LOCATION];
236             buffer[0] = '\0';
237             GetSystemDirectory(_wfilePath, MAX_LOCATION);
238             UTF16toUTF8Buffer(_wfilePath, buffer, MAX_LOCATION);
239             path = GetFilePath(buffer, extension, create, false, false);
240             break;
241          }
242          case winAllUsers:
243          {
244             char * allUsers = getenv("ALLUSERSPROFILE");
245             if(allUsers && allUsers[0])
246             {
247                strcpy(buffer, allUsers);
248                path = GetFilePath(buffer, extension, create, false, false);
249             }
250             break;
251          }
252 #else
253          case nixEtc:
254          {
255             strcpy(buffer, "/etc/");
256             path = GetFilePath(buffer, extension, create, false, false);
257             break;
258          }
259 #endif
260       }
261       if(!path) delete buffer;
262       return path;
263    }
264
265    char * GetFilePath(char * location, const char * extension, bool create, bool dotPrefix, bool runCommandsStyle)
266    {
267       char * path = null;
268       FileAttribs attribs;
269       if(location[0])
270          MakeSlashPath(location);
271       if(location[0] && (attribs = FileExists(location)) && (attribs.isDirectory || attribs.isDrive))
272       {
273          if(settingsDirectory)
274          {
275             if(dotPrefix)
276             {
277                int len = strlen(settingsDirectory);
278                String s = new char[len + 2];
279                s[0] = '.';
280                memcpy(s + 1, settingsDirectory, len + 1);
281                PathCatSlash(location, s);
282                delete s;
283             }
284             else
285                PathCatSlash(location, settingsDirectory);
286             if(create)
287                MakeDir(location);
288             attribs = FileExists(location);
289          }
290          if(attribs.isDirectory || attribs.isDrive)
291          {
292             char * name = new char[strlen(settingsName) + strlen(extension) + 4];
293             if(dotPrefix && !settingsDirectory)
294             {
295                strcpy(name, ".");
296                strcat(name, settingsName);
297             }
298             else
299                strcpy(name, settingsName);
300             if(runCommandsStyle)
301                strcat(name, "rc");
302             else
303             {
304                strcat(name, ".");
305                strcat(name, extension);
306             }
307             PathCatSlash(location, name);
308             path = location;
309             delete name;
310          }
311       }
312       return path;
313    }
314
315    const char * GetExtension()
316    {
317       const char * extension;
318       if(settingsExtension)
319          extension = settingsExtension;
320       else
321 #if defined(__WIN32__)
322          extension = "ini";
323 #else
324          extension = "conf";
325 #endif
326       return extension;
327    }
328
329    void FileOpenTryRead(SettingsLocationType type)
330    {
331       f = FileOpen(settingsFilePath, read);
332       //PrintLn("GlobalSettings::FileOpenTryRead(", type, ") (", settingsFilePath, ") -- ", f ? "SUCCESS" : "FAIL");
333       if(f)
334          readType = type;
335       else
336       {
337                                    // This delete will cover both trying the next possible config location and
338          delete settingsFilePath;  // the case when we're doing a load when the config file is no longer available
339       }                            // and we want to re-try all possible config locations.
340    }
341
342    bool FileOpenTryWrite(SettingsLocationType type, bool shouldDelete, bool * locked)
343    {
344       *locked = false;
345       f = FileOpen(settingsFilePath, readWrite);
346       if(!f)
347       {
348          f = FileOpen(settingsFilePath, writeRead);
349          if(!driverClass)
350          {
351             delete f;
352             f = FileOpen(settingsFilePath, readWrite);
353          }
354       }
355       //PrintLn("GlobalSettings::FileOpenTryWrite(", type, ") (", settingsFilePath, ") -- ", f ? "SUCCESS" : "FAIL");
356       if(f)
357       {
358          writeType = type;
359          // Don't wait for a lock, first one to lock gets to write, other will likely loose changes on a reload.
360          if(f.Lock(exclusive, 0, 0, false))
361          {
362             *locked = true;
363             if(driverClass)
364             {
365                f.Truncate(0);
366                f.Seek(0, start);
367             }
368          }
369       }
370       else if(shouldDelete)
371       {
372          delete settingsFilePath;  // This delete will cover both trying the next possible config location and
373       }                            // allow trying to save to a location where user has permission.
374       return f != null;
375    }
376
377 public:
378    virtual void OnAskReloadSettings();
379
380    bool OpenAndLock(FileSize * fileSize)
381    {
382       SettingsLocationType type = readType;
383       if(!f)
384       {
385 #if !defined(__EMSCRIPTEN__)
386          settingsMonitor.StopMonitoring();
387 #endif
388
389          if(settingsFilePath)
390             FileOpenTryRead(type);
391
392          if(!settingsFilePath && settingsName && settingsName[0])
393          {
394             const char * extension = GetExtension();
395
396             if(!f && (settingsFilePath = PreparePath((type = specified), extension, false, false)))
397                FileOpenTryRead(type);
398             if(!f && (!settingsLocation || allowDefaultLocations))
399             {
400                globalPath = false;
401                if(!f && (settingsFilePath = PreparePath((type = portable), extension, false, false)))
402                   FileOpenTryRead(type);
403                if(f)
404                   portable = true;
405                if(!allUsers)
406                {
407 #if defined(__WIN32__)
408                   if(!f && (settingsFilePath = PreparePath((type = home), extension, false, false)))
409                      FileOpenTryRead(type);
410 #endif
411                   if(!f && (settingsFilePath = PreparePath((type = home), extension, false, true)))
412                      FileOpenTryRead(type);
413                }
414 #if defined(__WIN32__)
415                if(!allUsers)
416                {
417                   if(!f && (settingsFilePath = PreparePath((type = winUserProfile), extension, false, false)))
418                      FileOpenTryRead(type);
419                   if(!f && (settingsFilePath = PreparePath((type = winHomeDrive), extension, false, false)))
420                      FileOpenTryRead(type);
421                }
422                if(!f)
423                   globalPath = true;
424                if(!f && (settingsFilePath = PreparePath((type = winAllUsers), extension, false, false)))
425                   FileOpenTryRead(type);
426
427                if(!f && (settingsFilePath = PreparePath((type = winSystemPath), extension, false, false)))
428                   FileOpenTryRead(type);
429 #else
430                if(!f)
431                   globalPath = true;
432                if(!f && (settingsFilePath = PreparePath((type = nixEtc), extension, false, false)))
433                   FileOpenTryRead(type);
434 #endif
435             }
436          }
437       }
438       if(f)
439       {
440          int c;
441          if(!locked)
442          {
443             // At some point wait was true, it was changed to false and now we do retries.
444             // Could it be because the wait to true was causing blocking behaviors?
445             //if(f && f.Lock(shared, 0, 0, true)) <- I think the wait to true is bad, it wrote blanked out global settings.
446             for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++)
447             {
448                ecere::sys::Sleep(0.01);
449             }
450          }
451
452          if(locked && fileSize)
453             *fileSize = f.GetSize();
454       }
455       return f && locked;
456    }
457
458    virtual SettingsIOResult Load()
459    {
460       SettingsIOResult result = fileNotFound;
461       if(!f || !locked)
462          OpenAndLock(null);
463
464       if(f && locked)
465       {
466          if(driverClass)
467             result = driverClass.Load(f, this);
468          else
469             result = success;
470       }
471       return result;
472    }
473
474    virtual SettingsIOResult Save()
475    {
476       SettingsIOResult result = error;
477       SettingsLocationType type = writeType;
478       if(!f)
479       {
480          locked = false;
481
482 #if !defined(__EMSCRIPTEN__)
483          settingsMonitor.StopMonitoring();
484 #endif
485
486          if(settingsFilePath)
487             // Don't auto delete settingsFilePath because only want to try another path if we were using a global path
488             FileOpenTryWrite(type, false, &locked);
489
490          if((!settingsFilePath || (!f && globalPath)) && settingsName && settingsName[0])
491          {
492             const char * extension = GetExtension();
493             delete settingsFilePath;
494
495             if(!f && (settingsFilePath = PreparePath((type = specified), extension, true, false)))
496                FileOpenTryWrite(type, true, &locked);
497             if(!f && (!settingsLocation || allowDefaultLocations))
498             {
499                globalPath = true;
500                if(!f && portable && (settingsFilePath = PreparePath((type = portable), extension, true, false)))
501                   FileOpenTryWrite(type, true, &locked);
502 #if defined(__WIN32__)
503                if(!f && allUsers && (settingsFilePath = PreparePath((type = winAllUsers), extension, true, false)))
504                   FileOpenTryWrite(type, true, &locked);
505 #else
506                if(!f && allUsers && (settingsFilePath = PreparePath((type = nixEtc), extension, true, false)))
507                   FileOpenTryWrite(type, true, &locked);
508 #endif
509                if(!f && !allUsers)
510                {
511                   globalPath = false;
512                   if(!f && (settingsFilePath = PreparePath((type = home), extension, true,
513 #if defined(__WIN32__)
514                      false
515 #else
516                      true
517 #endif
518                      )))
519                      FileOpenTryWrite(type, true, &locked);
520                }
521 #if defined(__WIN32__)
522                if(!f && !allUsers)
523                {
524                   globalPath = false;
525                   if(!f && (settingsFilePath = PreparePath((type = winUserProfile), extension, true, false)))
526                      FileOpenTryWrite(type, true, &locked);
527                   if(!f && (settingsFilePath = PreparePath((type = winHomeDrive), extension, true, false)))
528                      FileOpenTryWrite(type, true, &locked);
529                }
530                if(!f && (settingsFilePath = PreparePath((type = winSystemPath), extension, true, false)))
531                {
532                   globalPath = true;
533                   FileOpenTryWrite(type, true, &locked);
534                }
535 #endif
536             }
537          }
538          if(f && locked)
539          {
540             if(driverClass)
541                result = driverClass.Save(f, this);
542             else
543                result = success;
544          }
545       }
546       return result;
547    }
548
549    void Close()
550    {
551       if(f)
552       {
553 #if !defined(__EMSCRIPTEN__)
554          settingsMonitor.StopMonitoring();
555 #endif
556          f.Unlock(0,0,true);
557          locked = false;
558          delete f;
559       }
560    }
561
562    void CloseAndMonitor()
563    {
564       Close();
565       if(settingsFilePath && OnAskReloadSettings != GlobalSettings::OnAskReloadSettings)
566       {
567 #if !defined(__EMSCRIPTEN__)
568          settingsMonitor.fileName = settingsFilePath;
569          settingsMonitor.StartMonitoring();
570 #endif
571       }
572    }
573 }
574
575 public class GlobalAppSettings : GlobalSettings
576 {
577 public:
578    bool GetGlobalValue(const char * section, const char * name, GlobalSettingType type, void * value)
579    {
580       bool result = false;
581       if(f)
582       {
583          char line[92048];
584          int lenSection = strlen(section);
585          int lenName = strlen(name);
586          f.Seek(0, start);
587          while(f.GetLine(line, sizeof(line)))
588          {
589             if(line[0] == '[' && !strncmp(line+1, section, lenSection) && line[lenSection+1] == ']')
590                break;
591          }
592          if(!f.Eof())
593          {
594             while(f.GetLine(line, sizeof(line)))
595             {
596                if(!strncmp(line, name, lenName) && line[lenName] == '=')
597                {
598                   char * string = line + lenName + 1;
599                   switch(type)
600                   {
601                      case stringList:
602                      {
603                         int c;
604                         Container<String> list = value;
605                         char * tokens[256];
606                         int numTokens = TokenizeWith(string,
607                            sizeof(tokens) / sizeof(byte *), tokens, " ,", false);
608                         list.Free();
609                         for(c = 0; c<numTokens; c++)
610                         {
611                            list.Add(CopyString(tokens[c]));
612                         }
613                         break;
614                      }
615                      case singleString:
616                      {
617                         char ** thisString = value;
618                         *thisString = CopyString(string);
619                         break;
620                      }
621                      case integer:
622                      {
623                         int * integer = value;
624                         *integer = (int)strtol(string, null, 0);
625                         break;
626                      }
627                   }
628                   result = true;
629                   break;
630                }
631             }
632          }
633       }
634       return result;
635    }
636
637    bool PutGlobalValue(const char * section, const char * name, GlobalSettingType type, const void * value)
638    {
639       bool result = false;
640       if(f)
641       {
642          char line[92048], outputLine[92048] = "";
643          int lenSection = strlen(section);
644          int lenName = strlen(name);
645          int lenOutput;
646          byte * remainingBuffer = null;
647          int remainingLen = 0;
648          uint currentSize = 0, newSize = 0;
649
650          f.Seek(0, start);
651
652          // Prepare the output line
653          strcpy(outputLine, name);
654          strcat(outputLine, "=");
655          switch(type)
656          {
657             case stringList:
658             {
659                Container<String> list = (void *)value;
660                Iterator<String> item { list };
661                item.Next();
662                while(item.pointer)
663                {
664                   strcat(outputLine, "\"");
665                   if(item.data)
666                      strcat(outputLine, item.data);
667                   strcat(outputLine, "\"");
668                   item.Next();
669                   if(item.pointer)
670                      strcat(outputLine, ",");
671                }
672                break;
673             }
674             case singleString:
675                // strcat(outputLine, "\"");
676                if(value)
677                   strcat(outputLine, value);
678                // strcat(outputLine, "\"");
679                break;
680             case integer:
681             {
682                char integer[64];
683                sprintf(integer, "%d", (int)(intptr)value);
684                strcat(outputLine, integer);
685                break;
686             }
687          }
688 #if defined(__WIN32__)
689          strcat(outputLine, "\r\n");
690 #else
691          strcat(outputLine, "\n");
692 #endif
693          lenOutput = strlen(outputLine);
694
695          while(f.GetLine(line, sizeof(line)))
696          {
697             if(line[0] == '[' && !strncmp(line+1, section, lenSection) && line[lenSection+1] == ']')
698                break;
699          }
700          if(!f.Eof())
701          {
702             int posBeforeSection = -1;
703             for(;;)
704             {
705                uint pos = f.Tell();
706                if(!f.GetLine(line, sizeof(line)))
707                   break;
708                if(posBeforeSection == -1 || !line[0])
709                   posBeforeSection = pos;
710
711                if(line[0] == '[')
712                {
713                   // We've already reached next section
714                   f.Seek(posBeforeSection, start);
715
716                   // Remember the rest of the file
717                   while(!f.Eof())
718                   {
719                      remainingBuffer = renew remainingBuffer byte[remainingLen + 65536];
720                      remainingLen += f.Read(remainingBuffer + remainingLen, 1, 65536);
721                   }
722                   // Don't need to truncate. leave currentSize and newSize at 0
723                   f.Seek(posBeforeSection, start);
724                   break;
725                }
726                else if(!strncmp(line, name, lenName) && line[lenName] == '=')
727                {
728                   uint endLinePos = f.Tell();
729
730                   // Remember the rest of the file
731                   while(!f.Eof())
732                   {
733                      remainingBuffer = renew remainingBuffer byte[remainingLen + 65536];
734                      remainingLen += f.Read(remainingBuffer + remainingLen, 1, 65536);
735                   }
736                   currentSize = endLinePos + remainingLen;
737                   newSize = pos + lenOutput + remainingLen;
738
739                   // Go back where we will overwrite this line
740                   f.Seek(pos, start);
741                   break;
742                }
743             }
744          }
745          else
746          {
747             // Add the section
748 #if defined(__WIN32__)
749             f.Printf("\r\n[%s]\r\n", section);
750 #else
751             f.Printf("\n[%s]\n", section);
752 #endif
753          }
754
755          // Write the line
756          f.Write(outputLine, 1, lenOutput);
757
758          // Write back the rest of the file if necessary
759          if(remainingBuffer)
760          {
761             f.Write(remainingBuffer, 1, remainingLen);
762             delete remainingBuffer;
763             if(currentSize != newSize)
764                FileTruncate(settingsFilePath, newSize);
765          }
766          result = true;
767       }
768       return result;
769    }
770 };