Merge pull request #110 from tesselode/fixes
[wdl/wdl-ol.git] / WDL / swell / swell-ini.cpp
blobda89b19a8eb5175b2983ec8cd67c32460cb27448
1 /* Cockos SWELL (Simple/Small Win32 Emulation Layer for Linux/OSX)
2 Copyright (C) 2006 and later, Cockos, Inc.
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely, subject to the following restrictions:
12 1. The origin of this software must not be misrepresented; you must not
13 claim that you wrote the original software. If you use this software
14 in a product, an acknowledgment in the product documentation would be
15 appreciated but is not required.
16 2. Altered source versions must be plainly marked as such, and must not be
17 misrepresented as being the original software.
18 3. This notice may not be removed or altered from any source distribution.
20 This file implements basic win32 GetPrivateProfileString / etc support.
21 It works by caching reads, but writing through on every write that is required (to ensure
22 that updates take, especially when being updated from multiple modules who have their own
23 cache of the .ini file).
25 It is threadsafe, but in theory if two processes are trying to access the same ini,
26 results may go a bit unpredictable (but in general the file should NOT get corrupted,
27 we hope).
31 #ifndef SWELL_PROVIDED_BY_APP
34 #include "swell.h"
35 #include "../assocarray.h"
36 #include "../wdlcstring.h"
37 #include "../mutex.h"
38 #include "../queue.h"
39 #include <sys/stat.h>
40 #include <sys/file.h>
41 #include <sys/types.h>
43 static void deleteStringKeyedArray(WDL_StringKeyedArray<char *> *p) { delete p; }
45 struct iniFileContext
47 iniFileContext() : m_sections(false,deleteStringKeyedArray)
49 m_curfn=NULL;
50 m_lastaccesscnt=0;
51 m_curfn_time=0;
52 m_curfn_sz=0;
54 ~iniFileContext() { }
56 WDL_UINT64 m_lastaccesscnt;
57 time_t m_curfn_time;
58 int m_curfn_sz;
59 char *m_curfn;
61 WDL_StringKeyedArray< WDL_StringKeyedArray<char *> * > m_sections;
65 #define NUM_OPEN_CONTEXTS 32
66 static iniFileContext s_ctxs[NUM_OPEN_CONTEXTS];
67 static WDL_Mutex m_mutex;
69 static time_t getfileupdtimesize(const char *fn, int *szOut)
71 struct stat st;
72 *szOut = 0;
73 if (!fn || !fn[0] || stat(fn,&st)) return 0;
74 *szOut = (int)st.st_size;
75 return st.st_mtime;
78 static bool fgets_to_typedbuf(WDL_TypedBuf<char> *buf, FILE *fp)
80 int rdpos=0;
81 while (rdpos < 1024*1024*32)
83 if (buf->GetSize()<rdpos+8192) buf->Resize(rdpos+8192);
84 if (buf->GetSize()<rdpos+4) break; // malloc fail, erg
85 char *p = buf->Get()+rdpos;
86 *p=0;
87 fgets(p,buf->GetSize()-rdpos,fp);
88 if (!*p) break;
89 while (*p) p++;
90 if (p[-1] == '\r' || p[-1] == '\n') break;
92 rdpos = (int) (p - buf->Get());
94 return buf->GetSize()>0 && buf->Get()[0];
97 // return true on success
98 static iniFileContext *GetFileContext(const char *name)
100 static WDL_UINT64 acc_cnt;
101 int best_z = 0;
102 char fntemp[512];
103 if (!name || !strstr(name,"/"))
105 extern char *g_swell_defini;
106 if (g_swell_defini)
108 lstrcpyn_safe(fntemp,g_swell_defini,sizeof(fntemp));
110 else
112 const char *p = getenv("HOME");
113 snprintf(fntemp,sizeof(fntemp),"%s/.libSwell.ini",
114 p && *p ? p : "/tmp");
116 if (name && *name)
118 WDL_remove_fileext(fntemp);
119 snprintf_append(fntemp,sizeof(fntemp),"_%s%s",name,
120 stricmp(WDL_get_fileext(name),".ini")?".ini":"");
122 name = fntemp;
126 int w;
127 WDL_UINT64 bestcnt = 0;
128 bestcnt--;
130 for (w=0;w<NUM_OPEN_CONTEXTS;w++)
132 if (!s_ctxs[w].m_curfn || !stricmp(s_ctxs[w].m_curfn,name))
134 // we never clear m_curfn, so we'll always find an item in cache before an unused cache entry
135 best_z=w;
136 break;
139 if (s_ctxs[w].m_lastaccesscnt < bestcnt) { best_z = w; bestcnt = s_ctxs[w].m_lastaccesscnt; }
143 iniFileContext *ctx = &s_ctxs[best_z];
144 ctx->m_lastaccesscnt=++acc_cnt;
146 int sz=0;
147 if (!ctx->m_curfn || stricmp(ctx->m_curfn,name) || ctx->m_curfn_time != getfileupdtimesize(ctx->m_curfn,&sz) || sz != ctx->m_curfn_sz)
149 ctx->m_sections.DeleteAll();
150 // printf("reinitting to %s\n",name);
151 if (!ctx->m_curfn || stricmp(ctx->m_curfn,name))
153 free(ctx->m_curfn);
154 ctx->m_curfn=strdup(name);
156 FILE *fp = fopen(name,"r");
158 if (!fp)
160 ctx->m_curfn_time=0;
161 ctx->m_curfn_sz=0;
162 return ctx; // allow to proceed (empty file)
165 flock(fileno(fp),LOCK_SH);
167 // parse .ini file
168 WDL_StringKeyedArray<char *> *cursec=NULL;
170 int lcnt=0;
171 for (;;)
173 static WDL_TypedBuf<char> _buf;
174 if (!fgets_to_typedbuf(&_buf,fp)) break;
176 char *buf = _buf.Get();
177 if (!ctx->m_sections.GetSize())
179 lcnt += strlen(buf);
180 if (lcnt > 256*1024) break; // dont bother reading more than 256kb if no section encountered
182 char *p=buf;
184 while (*p) p++;
186 if (p>buf)
188 p--;
189 while (p >= buf && (*p==' ' || *p == '\r' || *p == '\n' || *p == '\t')) p--;
190 p[1]=0;
192 p=buf;
193 while (*p == ' ' || *p == '\t') p++;
194 if (p[0] == '[')
196 char *p2=p;
197 while (*p2 && *p2 != ']') p2++;
198 if (*p2)
200 *p2=0;
201 if (cursec) cursec->Resort();
203 if (p[1])
205 cursec = ctx->m_sections.Get(p+1);
206 if (!cursec)
208 cursec = new WDL_StringKeyedArray<char *>(false,WDL_StringKeyedArray<char *>::freecharptr);
209 ctx->m_sections.Insert(p+1,cursec);
211 else cursec->DeleteAll();
213 else cursec=0;
216 else if (cursec)
218 char *t=strstr(p,"=");
219 if (t)
221 *t++=0;
222 if (*p)
223 cursec->AddUnsorted(p,strdup(t));
227 ctx->m_curfn_time = getfileupdtimesize(name,&ctx->m_curfn_sz);
228 flock(fileno(fp),LOCK_UN);
229 fclose(fp);
231 if (cursec) cursec->Resort();
233 return ctx;
236 static void WriteBackFile(iniFileContext *ctx)
238 if (!ctx||!ctx->m_curfn) return;
239 char newfn[1024];
240 lstrcpyn_safe(newfn,ctx->m_curfn,sizeof(newfn)-8);
242 char *p=newfn;
243 while (*p) p++;
244 while (p>newfn && p[-1] != '/') p--;
245 char lc = '.';
246 while (*p)
248 char c = *p;
249 *p++ = lc;
250 lc = c;
252 *p++ = lc;
253 strcpy(p,".new");
256 FILE *fp = fopen(newfn,"w");
257 if (!fp) return;
259 flock(fileno(fp),LOCK_EX);
261 int x;
262 for (x = 0; ; x ++)
264 const char *secname=NULL;
265 WDL_StringKeyedArray<char *> * cursec = ctx->m_sections.Enumerate(x,&secname);
266 if (!cursec || !secname) break;
268 fprintf(fp,"[%s]\n",secname);
269 int y;
270 for (y=0;;y++)
272 const char *keyname = NULL;
273 const char *keyvalue = cursec->Enumerate(y,&keyname);
274 if (!keyvalue || !keyname) break;
275 if (*keyname) fprintf(fp,"%s=%s\n",keyname,keyvalue);
277 fprintf(fp,"\n");
280 fflush(fp);
281 flock(fileno(fp),LOCK_UN);
282 fclose(fp);
284 if (!rename(newfn,ctx->m_curfn))
286 ctx->m_curfn_time = getfileupdtimesize(ctx->m_curfn,&ctx->m_curfn_sz);
288 else
290 // error updating, hmm how to handle this?
294 BOOL WritePrivateProfileSection(const char *appname, const char *strings, const char *fn)
296 if (!appname) return FALSE;
297 WDL_MutexLock lock(&m_mutex);
298 iniFileContext *ctx = GetFileContext(fn);
299 if (!ctx) return FALSE;
301 WDL_StringKeyedArray<char *> * cursec = ctx->m_sections.Get(appname);
302 if (!cursec)
304 if (!*strings) return TRUE;
306 cursec = new WDL_StringKeyedArray<char *>(false,WDL_StringKeyedArray<char *>::freecharptr);
307 ctx->m_sections.Insert(appname,cursec);
309 else cursec->DeleteAll();
311 if (*strings)
313 while (*strings)
315 char buf[8192];
316 lstrcpyn_safe(buf,strings,sizeof(buf));
317 char *p = buf;
318 while (*p && *p != '=') p++;
319 if (*p)
321 *p++=0;
322 cursec->Insert(buf,strdup(strings + (p-buf)));
325 strings += strlen(strings)+1;
328 WriteBackFile(ctx);
330 return TRUE;
334 BOOL WritePrivateProfileString(const char *appname, const char *keyname, const char *val, const char *fn)
336 if (!appname || (keyname && !*keyname)) return FALSE;
337 // printf("writing %s %s %s %s\n",appname,keyname,val,fn);
338 WDL_MutexLock lock(&m_mutex);
340 iniFileContext *ctx = GetFileContext(fn);
341 if (!ctx) return FALSE;
343 if (!keyname)
345 if (ctx->m_sections.Get(appname))
347 ctx->m_sections.Delete(appname);
348 WriteBackFile(ctx);
351 else
353 WDL_StringKeyedArray<char *> * cursec = ctx->m_sections.Get(appname);
354 if (!val)
356 if (cursec && cursec->Get(keyname))
358 cursec->Delete(keyname);
359 WriteBackFile(ctx);
362 else
364 const char *p;
365 if (!cursec || !(p=cursec->Get(keyname)) || strcmp(p,val))
367 if (!cursec)
369 cursec = new WDL_StringKeyedArray<char *>(false,WDL_StringKeyedArray<char *>::freecharptr);
370 ctx->m_sections.Insert(appname,cursec);
372 cursec->Insert(keyname,strdup(val));
373 WriteBackFile(ctx);
379 return TRUE;
382 static void lstrcpyn_trimmed(char* dest, const char* src, int len)
384 if (len<1) return;
385 // Mimic Win32 behavior of stripping quotes and whitespace
386 while (*src==' ' || *src=='\t') ++src; // Strip beginning whitespace
388 const char *end = src;
389 if (*end) while (end[1]) end++;
391 while (end >= src && (*end==' ' || *end=='\t')) --end; // Strip end whitespace
393 if (end > src && ((*src=='\"' && *end=='\"') || (*src=='\'' && *end=='\'')))
395 // Strip initial set of "" or ''
396 ++src;
397 --end;
400 int newlen = (int) (end-src+2);
401 if (newlen < 1) newlen = 1;
402 else if (newlen > len) newlen = len;
404 lstrcpyn_safe(dest, src, newlen);
407 DWORD GetPrivateProfileSection(const char *appname, char *strout, DWORD strout_len, const char *fn)
409 WDL_MutexLock lock(&m_mutex);
411 if (!strout || strout_len<2)
413 if (strout && strout_len==1) *strout=0;
414 return 0;
416 iniFileContext *ctx= GetFileContext(fn);
417 int szOut=0;
418 WDL_StringKeyedArray<char *> *cursec = ctx ? ctx->m_sections.Get(appname) : NULL;
420 if (ctx && cursec)
422 int x;
423 for(x=0;x<cursec->GetSize();x++)
425 const char *kv = NULL;
426 const char *val = cursec->Enumerate(x,&kv);
427 if (val && kv)
429 int l;
431 #define WRSTR(v) \
432 l = (int)strlen(v); \
433 if (l > (int)strout_len - szOut - 2) l = (int)strout_len - 2 - szOut; \
434 if (l>0) { memcpy(strout+szOut,v,l); szOut+=l; }
436 WRSTR(kv)
437 WRSTR("=")
438 #undef WRSTR
440 lstrcpyn_trimmed(strout+szOut, val, (int)strout_len - szOut - 2);
441 szOut += strlen(strout+szOut);
443 l=1;
444 if (l > (int)strout_len - szOut - 1) l = (int)strout_len - 1 - szOut;
445 if (l>0) { memset(strout+szOut,0,l); szOut+=l; }
446 if (szOut >= (int)strout_len-1)
448 strout[strout_len-1]=0;
449 return strout_len-2;
454 strout[szOut]=0;
455 if (!szOut) strout[1]=0;
456 return szOut;
459 DWORD GetPrivateProfileString(const char *appname, const char *keyname, const char *def, char *ret, int retsize, const char *fn)
461 WDL_MutexLock lock(&m_mutex);
463 // printf("getprivateprofilestring: %s\n",fn);
464 iniFileContext *ctx= GetFileContext(fn);
466 if (ctx)
468 if (!appname||!keyname)
470 WDL_Queue tmpbuf;
471 if (!appname)
473 int x;
474 for (x = 0;; x ++)
476 const char *secname=NULL;
477 if (!ctx->m_sections.Enumerate(x,&secname) || !secname) break;
478 if (*secname) tmpbuf.Add(secname,(int)strlen(secname)+1);
481 else
483 WDL_StringKeyedArray<char *> *cursec = ctx->m_sections.Get(appname);
484 if (cursec)
486 int y;
487 for (y = 0; ; y ++)
489 const char *k=NULL;
490 if (!cursec->Enumerate(y,&k)||!k) break;
491 if (*k) tmpbuf.Add(k,(int)strlen(k)+1);
496 int sz=tmpbuf.GetSize()-1;
497 if (sz<0)
499 ret[0]=ret[1]=0;
500 return 0;
502 if (sz > retsize-2) sz=retsize-2;
503 memcpy(ret,tmpbuf.Get(),sz);
504 ret[sz]=ret[sz+1]=0;
506 return (DWORD)sz;
509 WDL_StringKeyedArray<char *> *cursec = ctx->m_sections.Get(appname);
510 if (cursec)
512 const char *val = cursec->Get(keyname);
513 if (val)
515 lstrcpyn_trimmed(ret,val,retsize);
516 return (DWORD)strlen(ret);
520 // printf("def %s %s %s %s\n",appname,keyname,def,fn);
521 lstrcpyn_safe(ret,def?def:"",retsize);
522 return (DWORD)strlen(ret);
525 int GetPrivateProfileInt(const char *appname, const char *keyname, int def, const char *fn)
527 char buf[512];
528 GetPrivateProfileString(appname,keyname,"",buf,sizeof(buf),fn);
529 if (buf[0])
531 int a=atoi(buf);
532 if (a||buf[0]=='0') return a;
534 return def;
537 static bool __readbyte(char *src, unsigned char *out)
539 unsigned char cv=0;
540 int s=4;
541 while(s>=0)
543 if (*src >= '0' && *src <= '9') cv += (*src-'0')<<s;
544 else if (*src >= 'a' && *src <= 'f') cv += (*src-'a' + 10)<<s;
545 else if (*src >= 'A' && *src <= 'F') cv += (*src-'A' + 10)<<s;
546 else return false;
547 src++;
548 s-=4;
551 *out=cv;
552 return true;
555 BOOL GetPrivateProfileStruct(const char *appname, const char *keyname, void *buf, int bufsz, const char *fn)
557 if (!appname || !keyname || bufsz<0) return 0;
558 char *tmp=(char *)malloc((bufsz+1)*2+16);
559 if (!tmp) return 0;
561 BOOL ret=0;
562 GetPrivateProfileString(appname,keyname,"",tmp,(bufsz+1)*2+15,fn);
563 if (strlen(tmp) == (size_t) (bufsz+1)*2)
565 unsigned char sum=0;
566 unsigned char *bufout=(unsigned char *)buf;
567 char *src=tmp;
568 unsigned char cv;
569 while (bufsz-->0)
571 if (!__readbyte(src,&cv)) break;
572 *bufout++ = cv;
573 sum += cv;
574 src+=2;
576 ret = bufsz<0 && __readbyte(src,&cv) && cv==sum;
578 free(tmp);
579 //printf("getprivateprofilestruct returning %d\n",ret);
580 return ret;
583 BOOL WritePrivateProfileStruct(const char *appname, const char *keyname, const void *buf, int bufsz, const char *fn)
585 if (!keyname || !buf) return WritePrivateProfileString(appname,keyname,(const char *)buf,fn);
586 char *tmp=(char *)malloc((bufsz+1)*2+1);
587 if (!tmp) return 0;
588 char *p = tmp;
589 unsigned char sum=0;
590 unsigned char *src=(unsigned char *)buf;
591 while (bufsz-- > 0)
593 sprintf(p,"%02X",*src);
594 sum+=*src++;
595 p+=2;
597 sprintf(p,"%02X",sum);
599 BOOL ret=WritePrivateProfileString(appname,keyname,tmp,fn);
600 free(tmp);
601 return ret;
604 #endif