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