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