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