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 private: 113 HKEY key; 114 115 void readRaw(string name, void[] dest) 116 { 117 enforce(getSize(name) == dest.length, "Invalid registry value length for " ~ name); 118 DWORD size = cast(DWORD)dest.length; 119 enforce(RegQueryValueExW(key, toUTFz!LPCWSTR(name), null, null, cast(ubyte*)dest.ptr, &size) == ERROR_SUCCESS, "RegQueryValueEx failed"); 120 enforce(size == dest.length, "Not enough data read"); 121 } 122 123 void writeRaw(string name, const(void)[] dest, DWORD type) 124 { 125 enforce(RegSetValueExW(key, toUTFz!LPCWSTR(name), 0, type, cast(ubyte*)dest.ptr, cast(uint)dest.length) == ERROR_SUCCESS, "RegSetValueEx failed"); 126 } 127 128 uint getSize(string name) 129 { 130 DWORD size; 131 enforce(RegQueryValueExW(key, toUTFz!LPCWSTR(name), null, null, null, &size) == ERROR_SUCCESS); 132 return size; 133 } 134 } 135 else // POSIX 136 { 137 import std.string; 138 import std.stdio; 139 import std.file; 140 import std.path; 141 import std.conv; 142 143 // Cache values from memory, and save them to disk when the program exits. 144 this(string appName = null, string companyName = null) 145 { 146 fileName = getConfigDir(appName) ~ "/config"; 147 if (!exists(fileName)) 148 return; 149 foreach (line; File(fileName, "rt").byLine()) 150 if (line.length>0 && line[0]!='#') 151 { 152 auto p = indexOf(line, '='); 153 if (p>0) 154 values[line[0..p].idup] = line[p+1..$].idup; 155 } 156 instances ~= this; 157 } 158 159 ~this() 160 { 161 assert(!dirty, "Dirty config destruction"); 162 } 163 164 T readImpl(T)(string name, T defaultValue = T.init) 165 { 166 auto pvalue = name in values; 167 if (pvalue) 168 return to!T(*pvalue); 169 else 170 return defaultValue; 171 } 172 173 void writeImpl(T)(string name, T value) 174 if (is(typeof(to!string(T.init)))) 175 { 176 values[name] = to!string(value); 177 dirty = true; 178 } 179 180 void save() 181 { 182 if (!dirty) 183 return; 184 auto f = File(fileName, "wt"); 185 foreach (name, value; values) 186 f.writefln("%s=%s", name, value); 187 dirty = false; 188 } 189 190 private: 191 string[string] values; 192 string fileName; 193 bool dirty; 194 195 static Config[] instances; 196 197 static ~this() 198 { 199 foreach (instance; instances) 200 instance.save(); 201 } 202 } 203 204 T read(T)(string name, T defaultValue = T.init) 205 { 206 static if (is(T==struct)) 207 { 208 T v = defaultValue; 209 auto prefix = name ~ "."; 210 foreach (i, field; v.tupleof) 211 v.tupleof[i] = readImpl(prefix ~ Capitalize!(v.tupleof[i].stringof[2..$]), v.tupleof[i]); 212 return v; 213 } 214 else 215 static if (is(typeof(readImpl(name, defaultValue)))) 216 return readImpl(name, defaultValue); 217 else 218 static if (is(typeof(to!T(string.init)))) 219 { 220 auto s = readImpl(name, string.init); 221 return s is string.init ? defaultValue : to!T(s); 222 } 223 else 224 static assert(0, "Can't read values of type " ~ T.stringof); 225 } 226 227 void write(T)(string name, T v) 228 { 229 static if (is(T==struct)) 230 { 231 auto prefix = name ~ "."; 232 foreach (i, field; v.tupleof) 233 writeImpl(prefix ~ Capitalize!(v.tupleof[i].stringof[2..$]), v.tupleof[i]); 234 } 235 else 236 static if (is(typeof(writeImpl(name, v)))) 237 return writeImpl(name, v); 238 else 239 static if (is(typeof(to!string(v)))) 240 writeImpl(name, to!string(v)); 241 else 242 static assert(0, "Can't write values of type " ~ T.stringof); 243 } 244 } 245 246 private template Capitalize(string s) 247 { 248 enum Capitalize = s.length ? cast(char)toUpper(s[0]) ~ s[1..$] : s; 249 }