ecere/gui/Window: Prevent uninitialized values if base Window methods not overridden...
[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 && settingsDirectory[0] != '.')
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 && settingsName[0] != '.')
294             {
295                strcpy(name, ".");
296                strcat(name, settingsName);
297             }
298             else
299                strcpy(name, settingsName);
300             if(!settingsExtension && 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    property bool isGlobalPath { get { return globalPath; } }
381
382    bool OpenAndLock(FileSize * fileSize)
383    {
384       SettingsLocationType type = readType;
385       if(!f)
386       {
387 #if !defined(__EMSCRIPTEN__)
388          settingsMonitor.StopMonitoring();
389 #endif
390
391          if(settingsFilePath)
392             FileOpenTryRead(type);
393
394          if(!settingsFilePath && settingsName && settingsName[0])
395          {
396             const char * extension = GetExtension();
397
398             if(!f && (settingsFilePath = PreparePath((type = specified), extension, false, false)))
399                FileOpenTryRead(type);
400             if(!f && (!settingsLocation || allowDefaultLocations))
401             {
402                globalPath = false;
403                if(!f && (settingsFilePath = PreparePath((type = portable), extension, false, false)))
404                   FileOpenTryRead(type);
405                if(f)
406                   portable = true;
407                if(!allUsers)
408                {
409 #if defined(__WIN32__)
410                   if(!f && (settingsFilePath = PreparePath((type = home), extension, false, false)))
411                      FileOpenTryRead(type);
412 #endif
413                   if(!f && (settingsFilePath = PreparePath((type = home), extension, false, true)))
414                      FileOpenTryRead(type);
415                }
416 #if defined(__WIN32__)
417                if(!allUsers)
418                {
419                   if(!f && (settingsFilePath = PreparePath((type = winUserProfile), extension, false, false)))
420                      FileOpenTryRead(type);
421                   if(!f && (settingsFilePath = PreparePath((type = winHomeDrive), extension, false, false)))
422                      FileOpenTryRead(type);
423                }
424                if(!f)
425                   globalPath = true;
426                if(!f && (settingsFilePath = PreparePath((type = winAllUsers), extension, false, false)))
427                   FileOpenTryRead(type);
428
429                if(!f && (settingsFilePath = PreparePath((type = winSystemPath), extension, false, false)))
430                   FileOpenTryRead(type);
431 #else
432                if(!f)
433                   globalPath = true;
434                if(!f && (settingsFilePath = PreparePath((type = nixEtc), extension, false, false)))
435                   FileOpenTryRead(type);
436 #endif
437             }
438          }
439       }
440       if(f)
441       {
442          int c;
443          if(!locked)
444          {
445             // At some point wait was true, it was changed to false and now we do retries.
446             // Could it be because the wait to true was causing blocking behaviors?
447             //if(f && f.Lock(shared, 0, 0, true)) <- I think the wait to true is bad, it wrote blanked out global settings.
448             for(c = 0; c < 10 && !(locked = f.Lock(shared, 0, 0, false)); c++)
449             {
450                ecere::sys::Sleep(0.01);
451             }
452          }
453
454          if(locked && fileSize)
455             *fileSize = f.GetSize();
456       }
457       return f && locked;
458    }
459
460    virtual SettingsIOResult Load()
461    {
462       SettingsIOResult result = fileNotFound;
463       if(!f || !locked)
464          OpenAndLock(null);
465
466       if(f && locked)
467       {
468          if(driverClass)
469             result = driverClass.Load(f, this);
470          else
471             result = success;
472       }
473       return result;
474    }
475
476    virtual SettingsIOResult Save()
477    {
478       SettingsIOResult result = error;
479       SettingsLocationType type = writeType;
480       if(!f)
481       {
482          locked = false;
483
484 #if !defined(__EMSCRIPTEN__)
485          settingsMonitor.StopMonitoring();
486 #endif
487
488          if(settingsFilePath)
489             // Don't auto delete settingsFilePath because only want to try another path if we were using a global path
490             FileOpenTryWrite(type, false, &locked);
491
492          if((!settingsFilePath || (!f && globalPath)) && settingsName && settingsName[0])
493          {
494             const char * extension = GetExtension();
495             delete settingsFilePath;
496
497             if(!f && (settingsFilePath = PreparePath((type = specified), extension, true, false)))
498                FileOpenTryWrite(type, true, &locked);
499             if(!f && (!settingsLocation || allowDefaultLocations))
500             {
501                globalPath = true;
502                if(!f && portable && (settingsFilePath = PreparePath((type = portable), extension, true, false)))
503                   FileOpenTryWrite(type, true, &locked);
504 #if defined(__WIN32__)
505                if(!f && allUsers && (settingsFilePath = PreparePath((type = winAllUsers), extension, true, false)))
506                   FileOpenTryWrite(type, true, &locked);
507 #else
508                if(!f && allUsers && (settingsFilePath = PreparePath((type = nixEtc), extension, true, false)))
509                   FileOpenTryWrite(type, true, &locked);
510 #endif
511                if(!f && !allUsers)
512                {
513                   globalPath = false;
514                   if(!f && (settingsFilePath = PreparePath((type = home), extension, true,
515 #if defined(__WIN32__)
516                      false
517 #else
518                      true
519 #endif
520                      )))
521                      FileOpenTryWrite(type, true, &locked);
522                }
523 #if defined(__WIN32__)
524                if(!f && !allUsers)
525                {
526                   globalPath = false;
527                   if(!f && (settingsFilePath = PreparePath((type = winUserProfile), extension, true, false)))
528                      FileOpenTryWrite(type, true, &locked);
529                   if(!f && (settingsFilePath = PreparePath((type = winHomeDrive), extension, true, false)))
530                      FileOpenTryWrite(type, true, &locked);
531                }
532                if(!f && (settingsFilePath = PreparePath((type = winSystemPath), extension, true, false)))
533                {
534                   globalPath = true;
535                   FileOpenTryWrite(type, true, &locked);
536                }
537 #endif
538             }
539          }
540          if(f && locked)
541          {
542             if(driverClass)
543                result = driverClass.Save(f, this);
544             else
545                result = success;
546          }
547       }
548       return result;
549    }
550
551    void Close()
552    {
553       if(f)
554       {
555 #if !defined(__EMSCRIPTEN__)
556          settingsMonitor.StopMonitoring();
557 #endif
558          f.Unlock(0,0,true);
559          locked = false;
560          delete f;
561       }
562    }
563
564    void CloseAndMonitor()
565    {
566       Close();
567       if(settingsFilePath && OnAskReloadSettings != GlobalSettings::OnAskReloadSettings)
568       {
569 #if !defined(__EMSCRIPTEN__)
570          settingsMonitor.fileName = settingsFilePath;
571          settingsMonitor.StartMonitoring();
572 #endif
573       }
574    }
575 }
576
577 public class GlobalAppSettings : GlobalSettings
578 {
579 public:
580    bool GetGlobalValue(const char * section, const char * name, GlobalSettingType type, void * value)
581    {
582       bool result = false;
583       if(f)
584       {
585          char line[92048];
586          int lenSection = strlen(section);
587          int lenName = strlen(name);
588          f.Seek(0, start);
589          while(f.GetLine(line, sizeof(line)))
590          {
591             if(line[0] == '[' && !strncmp(line+1, section, lenSection) && line[lenSection+1] == ']')
592                break;
593          }
594          if(!f.Eof())
595          {
596             while(f.GetLine(line, sizeof(line)))
597             {
598                if(!strncmp(line, name, lenName) && line[lenName] == '=')
599                {
600                   char * string = line + lenName + 1;
601                   switch(type)
602                   {
603                      case stringList:
604                      {
605                         int c;
606                         Container<String> list = value;
607                         char * tokens[256];
608                         int numTokens = TokenizeWith(string,
609                            sizeof(tokens) / sizeof(byte *), tokens, " ,", false);
610                         list.Free();
611                         for(c = 0; c<numTokens; c++)
612                         {
613                            list.Add(CopyString(tokens[c]));
614                         }
615                         break;
616                      }
617                      case singleString:
618                      {
619                         char ** thisString = value;
620                         *thisString = CopyString(string);
621                         break;
622                      }
623                      case integer:
624                      {
625                         int * integer = value;
626                         *integer = (int)strtol(string, null, 0);
627                         break;
628                      }
629                   }
630                   result = true;
631                   break;
632                }
633             }
634          }
635       }
636       return result;
637    }
638
639    bool PutGlobalValue(const char * section, const char * name, GlobalSettingType type, const void * value)
640    {
641       bool result = false;
642       if(f)
643       {
644          char line[92048], outputLine[92048] = "";
645          int lenSection = strlen(section);
646          int lenName = strlen(name);
647          int lenOutput;
648          byte * remainingBuffer = null;
649          int remainingLen = 0;
650          uint currentSize = 0, newSize = 0;
651
652          f.Seek(0, start);
653
654          // Prepare the output line
655          strcpy(outputLine, name);
656          strcat(outputLine, "=");
657          switch(type)
658          {
659             case stringList:
660             {
661                Container<String> list = (void *)value;
662                Iterator<String> item { list };
663                item.Next();
664                while(item.pointer)
665                {
666                   strcat(outputLine, "\"");
667                   if(item.data)
668                      strcat(outputLine, item.data);
669                   strcat(outputLine, "\"");
670                   item.Next();
671                   if(item.pointer)
672                      strcat(outputLine, ",");
673                }
674                break;
675             }
676             case singleString:
677                // strcat(outputLine, "\"");
678                if(value)
679                   strcat(outputLine, value);
680                // strcat(outputLine, "\"");
681                break;
682             case integer:
683             {
684                char integer[64];
685                sprintf(integer, "%d", (int)(intptr)value);
686                strcat(outputLine, integer);
687                break;
688             }
689          }
690 #if defined(__WIN32__)
691          strcat(outputLine, "\r\n");
692 #else
693          strcat(outputLine, "\n");
694 #endif
695          lenOutput = strlen(outputLine);
696
697          while(f.GetLine(line, sizeof(line)))
698          {
699             if(line[0] == '[' && !strncmp(line+1, section, lenSection) && line[lenSection+1] == ']')
700                break;
701          }
702          if(!f.Eof())
703          {
704             int posBeforeSection = -1;
705             for(;;)
706             {
707                uint pos = f.Tell();
708                if(!f.GetLine(line, sizeof(line)))
709                   break;
710                if(posBeforeSection == -1 || !line[0])
711                   posBeforeSection = pos;
712
713                if(line[0] == '[')
714                {
715                   // We've already reached next section
716                   f.Seek(posBeforeSection, start);
717
718                   // Remember the rest of the file
719                   while(!f.Eof())
720                   {
721                      remainingBuffer = renew remainingBuffer byte[remainingLen + 65536];
722                      remainingLen += f.Read(remainingBuffer + remainingLen, 1, 65536);
723                   }
724                   // Don't need to truncate. leave currentSize and newSize at 0
725                   f.Seek(posBeforeSection, start);
726                   break;
727                }
728                else if(!strncmp(line, name, lenName) && line[lenName] == '=')
729                {
730                   uint endLinePos = f.Tell();
731
732                   // Remember the rest of the file
733                   while(!f.Eof())
734                   {
735                      remainingBuffer = renew remainingBuffer byte[remainingLen + 65536];
736                      remainingLen += f.Read(remainingBuffer + remainingLen, 1, 65536);
737                   }
738                   currentSize = endLinePos + remainingLen;
739                   newSize = pos + lenOutput + remainingLen;
740
741                   // Go back where we will overwrite this line
742                   f.Seek(pos, start);
743                   break;
744                }
745             }
746          }
747          else
748          {
749             // Add the section
750 #if defined(__WIN32__)
751             f.Printf("\r\n[%s]\r\n", section);
752 #else
753             f.Printf("\n[%s]\n", section);
754 #endif
755          }
756
757          // Write the line
758          f.Write(outputLine, 1, lenOutput);
759
760          // Write back the rest of the file if necessary
761          if(remainingBuffer)
762          {
763             f.Write(remainingBuffer, 1, remainingLen);
764             delete remainingBuffer;
765             if(currentSize != newSize)
766                FileTruncate(settingsFilePath, newSize);
767          }
768          result = true;
769       }
770       return result;
771    }
772 };