1 /** 2 * OS-specific configuration storage. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.sys.config; 15 16 import std.ascii; 17 18 import ae.sys.file; 19 import ae.sys.paths; 20 21 /// Cross-platform API for storing and retrieving 22 /// the application's configuration. 23 /// On Windows, the Windows registry is used. 24 /// On POSIX, configuration is stored in the XDG 25 /// configuration directory (`"~/.config"`). 26 class Config 27 { 28 version (Windows) 29 { 30 import std.exception; 31 import std.utf; 32 import std.array; 33 34 import ae.sys.windows.imports; 35 mixin(importWin32!q{windef}); 36 mixin(importWin32!q{winreg}); 37 38 // On Windows, just keep the registry key open and read/write values directly. 39 this(string appName = null, string companyName = null) 40 { 41 if (!appName) 42 appName = getExecutableName(); 43 if (companyName) 44 appName = companyName ~ `\` ~ appName; 45 46 enforce(RegCreateKeyExW( 47 HKEY_CURRENT_USER, 48 toUTFz!LPCWSTR(`Software\` ~ appName), 49 0, 50 null, 51 0, 52 KEY_READ | KEY_WRITE, 53 null, 54 &key, 55 null) == ERROR_SUCCESS, "RegCreateKeyEx failed"); 56 } /// 57 58 ~this() @nogc 59 { 60 if (key) 61 RegCloseKey(key); 62 } 63 64 private T readImpl(T)(string name, T defaultValue) 65 { 66 try 67 { 68 static if (is(T : const(char[]))) // strings 69 { 70 uint bytes = getSize(name); 71 enforce(bytes % 2 == 0); 72 wchar[] ws = new wchar[bytes / 2]; 73 readRaw(name, ws); 74 enforce(ws[$-1]==0); // should be null-terminated 75 return to!T(ws[0..$-1]); 76 } 77 else 78 static if (is(T == long) || is(T == ulong)) 79 { 80 T value; 81 readRaw(name, (&value)[0..1]); 82 return value; 83 } 84 else 85 static if (is(T : uint) || is(T : bool)) 86 { 87 uint value; 88 readRaw(name, (&value)[0..1]); 89 return cast(T)value; 90 } 91 else 92 static assert(0, "Can't read values of type " ~ T.stringof); 93 } 94 catch (Throwable e) // FIXME 95 return defaultValue; 96 } 97 98 private void writeImpl(T)(string name, T value) 99 { 100 static if (is(T : const(char[]))) // strings 101 { 102 wstring ws = to!wstring(value ~ '\0'); 103 writeRaw(name, ws, REG_SZ); 104 } 105 else 106 static if (is(T == long) || is(T == ulong)) 107 writeRaw(name, (&value)[0..1], REG_QWORD); 108 else 109 static if (is(T : uint) || is(T : bool)) 110 { 111 uint dwordValue = cast(uint)value; 112 writeRaw(name, (&dwordValue)[0..1], REG_DWORD); 113 } 114 else 115 static assert(0, "Can't write values of type " ~ T.stringof); 116 } 117 118 void save() {} /// Flush unwritten changes. 119 120 private: 121 HKEY key; 122 123 void readRaw(string name, void[] dest) 124 { 125 enforce(getSize(name) == dest.length, "Invalid registry value length for " ~ name); 126 DWORD size = cast(DWORD)dest.length; 127 enforce(RegQueryValueExW(key, toUTFz!LPCWSTR(name), null, null, cast(ubyte*)dest.ptr, &size) == ERROR_SUCCESS, "RegQueryValueEx failed"); 128 enforce(size == dest.length, "Not enough data read"); 129 } 130 131 void writeRaw(string name, const(void)[] dest, DWORD type) 132 { 133 enforce(RegSetValueExW(key, toUTFz!LPCWSTR(name), 0, type, cast(ubyte*)dest.ptr, cast(uint)dest.length) == ERROR_SUCCESS, "RegSetValueEx failed"); 134 } 135 136 uint getSize(string name) 137 { 138 DWORD size; 139 enforce(RegQueryValueExW(key, toUTFz!LPCWSTR(name), null, null, null, &size) == ERROR_SUCCESS); 140 return size; 141 } 142 } 143 else // POSIX 144 { 145 import std.string; 146 import std.stdio; 147 import std.file; 148 import std.path; 149 import std.conv; 150 151 // Cache values from memory, and save them to disk when the program exits. 152 this(string appName = null, string companyName = null) 153 { 154 fileName = getConfigDir(appName) ~ "/config"; 155 if (!exists(fileName)) 156 return; 157 foreach (line; File(fileName, "rt").byLine()) 158 if (line.length>0 && line[0]!='#') 159 { 160 auto p = indexOf(line, '='); 161 if (p>0) 162 values[line[0..p].idup] = line[p+1..$].idup; 163 } 164 instances ~= this; 165 } /// 166 167 ~this() @nogc 168 { 169 assert(!dirty, "Dirty config destruction"); 170 } 171 172 private T readImpl(T)(string name, T defaultValue = T.init) 173 { 174 auto pvalue = name in values; 175 if (pvalue) 176 return to!T(*pvalue); 177 else 178 return defaultValue; 179 } 180 181 private void writeImpl(T)(string name, T value) 182 if (is(typeof(to!string(T.init)))) 183 { 184 values[name] = to!string(value); 185 dirty = true; 186 } 187 188 /// Flush unwritten changes. 189 void save() 190 { 191 if (!dirty) 192 return; 193 ensurePathExists(fileName); 194 auto f = File(fileName, "wt"); 195 foreach (name, value; values) 196 f.writefln("%s=%s", name, value); 197 dirty = false; 198 } 199 200 private: 201 string[string] values; 202 string fileName; 203 bool dirty; 204 205 static Config[] instances; 206 207 static ~this() 208 { 209 foreach (instance; instances) 210 instance.save(); 211 } 212 } 213 214 /// Read value. 215 T read(T)(string name, T defaultValue = T.init) 216 { 217 static if (is(T==struct)) 218 { 219 T v = defaultValue; 220 auto prefix = name ~ "."; 221 foreach (i, field; v.tupleof) 222 v.tupleof[i] = readImpl(prefix ~ Capitalize!(v.tupleof[i].stringof[2..$]), v.tupleof[i]); 223 return v; 224 } 225 else 226 static if (is(typeof(readImpl(name, defaultValue)))) 227 return readImpl(name, defaultValue); 228 else 229 static if (is(typeof(to!T(string.init)))) 230 { 231 auto s = readImpl(name, string.init); 232 return s is string.init ? defaultValue : to!T(s); 233 } 234 else 235 static assert(0, "Can't read values of type " ~ T.stringof); 236 } 237 238 /// Write value. 239 void write(T)(string name, T v) 240 { 241 static if (is(T==struct)) 242 { 243 auto prefix = name ~ "."; 244 foreach (i, field; v.tupleof) 245 writeImpl(prefix ~ Capitalize!(v.tupleof[i].stringof[2..$]), v.tupleof[i]); 246 } 247 else 248 static if (is(typeof(writeImpl(name, v)))) 249 return writeImpl(name, v); 250 else 251 static if (is(typeof(to!string(v)))) 252 writeImpl(name, to!string(v)); 253 else 254 static assert(0, "Can't write values of type " ~ T.stringof); 255 } 256 } 257 258 private template Capitalize(string s) 259 { 260 enum Capitalize = s.length ? cast(char)toUpper(s[0]) ~ s[1..$] : s; 261 }