1252
|
1 const CONFIGHOTRELOAD {.booldefine.}: bool = not defined(release)
|
|
2 const CONFIGHOTRELOADINTERVAL {.intdefine.}: int = 1000
|
|
3 const CONFIGROOT {.strdefine.}: string = "."
|
|
4 const CONFIGEXTENSION {.strdefine.}: string = "ini"
|
|
5
|
1226
|
6
|
|
7 when CONFIGHOTRELOAD:
|
|
8 var
|
|
9 configUpdates: Channel[(string, string)]
|
|
10 configUpdates.open()
|
|
11
|
|
12 # runtime configuration
|
|
13 # =====================
|
|
14 # namespace is the path from the CONFIGROOT to the according settings file without the file extension
|
|
15 # a settings file must always have the extension CONFIGEXTENSION
|
|
16 # a fully qualified settings identifier can be in the form {namespace}.{section}.{key}
|
|
17 # {key} and {section} may not contain dots
|
|
18
|
|
19 # a "namespace" is the path from the settings root to an *.CONFIGEXTENSION file, without the file extension
|
|
20 # settings is a namespace <-> settings mapping
|
|
21 var allsettings: Table[string, Config]
|
|
22
|
|
23 proc configRoot(): string =
|
|
24 joinPath(absolutePath(getAppDir()), CONFIGROOT)
|
|
25
|
|
26 proc getFile(namespace: string): string =
|
|
27 joinPath(configRoot(), namespace & "." & CONFIGEXTENSION)
|
|
28
|
|
29 iterator walkConfigNamespaces(): string =
|
|
30 for file in walkDirRec(dir = configRoot(), relative = true, checkDir = true):
|
|
31 if file.endsWith("." & CONFIGEXTENSION):
|
|
32 yield file[0 ..< ^(CONFIGEXTENSION.len + 1)]
|
|
33
|
|
34 proc loadAllConfig(): Table[string, Config] =
|
|
35 for ns in walkConfigNamespaces():
|
|
36 result[ns] = ns.getFile().loadConfig()
|
|
37
|
|
38 proc ReloadSettings*() =
|
|
39 allsettings = loadAllConfig()
|
|
40
|
|
41 proc configStr(key, section, namespace: string): string =
|
|
42 when CONFIGHOTRELOAD:
|
|
43 while configUpdates.peek() > 0:
|
|
44 let (updatedNamespace, updatedConfig) = configUpdates.recv()
|
|
45 allsettings[updatedNamespace] = loadConfig(newStringStream(updatedConfig))
|
|
46 if not allsettings.hasKey(namespace):
|
|
47 raise newException(Exception, &"Settings {namespace}.{section}.{key} was not found")
|
|
48 allsettings[namespace].getSectionValue(section, key)
|
|
49
|
|
50 proc Setting*[T: int|float|string](key, section, namespace: string): T =
|
|
51 when T is int:
|
|
52 let value = configStr(key, section, namespace)
|
|
53 if parseInt(value, result) == 0:
|
|
54 raise newException(Exception, &"Unable to parse int from settings {namespace}.{section}.{key}: {value}")
|
|
55 elif T is float:
|
|
56 let value = configStr(key, section, namespace)
|
|
57 if parseFloat(value, result) == 0:
|
|
58 raise newException(Exception, &"Unable to parse float from settings {namespace}.{section}.{key}: {value}")
|
|
59 else:
|
|
60 result = configStr(key, section, namespace)
|
|
61
|
|
62 proc Setting*[T: int|float|string](identifier: string): T =
|
|
63 # identifier can be in the form:
|
|
64 # {namespace}.{key}
|
|
65 # {namespace}.{section}.{key}
|
|
66 let parts = identifier.rsplit(".")
|
|
67 if parts.len == 1:
|
|
68 raise newException(Exception, &"Setting with name {identifier} has no namespace")
|
|
69 if parts.len == 2: result = Setting[T](parts[1], "", parts[0])
|
|
70 else: result = Setting[T](parts[^1], parts[^2], joinPath(parts[0 .. ^3]))
|
|
71
|
|
72 proc HadConfigUpdate*(): bool =
|
|
73 when CONFIGHOTRELOAD == true:
|
|
74 result = configUpdates.peek() > 0
|
|
75
|
|
76 allsettings = loadAllConfig()
|
|
77
|
|
78 when CONFIGHOTRELOAD == true:
|
|
79 proc configFileWatchdog() {.thread.} =
|
|
80 var configModTimes: Table[string, times.Time]
|
|
81 while true:
|
|
82 for namespace in walkConfigNamespaces():
|
|
83 if not (namespace in configModTimes):
|
|
84 configModTimes[namespace] = times.Time()
|
|
85 let lastMod = namespace.getFile().getLastModificationTime()
|
|
86 if lastMod > configModTimes[namespace]:
|
|
87 configModTimes[namespace] = lastMod
|
|
88 let configStr = newFileStream(namespace.getFile()).readAll()
|
|
89 configUpdates.send((namespace, configStr))
|
|
90 sleep CONFIGHOTRELOADINTERVAL
|
|
91 var thethread: Thread[void]
|
|
92 createThread(thethread, configFileWatchdog)
|