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