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