It could just be a wrapper around dconf, but dconf is owned by GNOME. Not only is this not cross platform (we'd need another implementation for other systems), but depending on anything GNOME related is a horrible idea. Additionally, holding all settings within a single file is kind of a bad idea.
The configuration system should:
Categorize application configurations by application ID
Have system and user-level configurations for each application
System level configurations specify default values
User level configurations override system level values
Be able to handle multiple locations for system-level config dirs to support XDG base dirs
Just one location for the user-level config
Each dir contains a single config file
Have a user-readable, editable file format
Preserve unused values
Store values losslessly in all cases
Be primarily key/value structured
Alert the application when a setting has changed, either by use of an API, or by the user or an external program
Watch all files
When a file gets changed, reload it and find out what value(s) were altered
Have some global Nasin settings that apply to all applications, possibly with the ID xyz.holanet.Nasin
This would be read by Nasin itself and used to read things like the theme, scaling, etc.
The handle to this must be made available through a function
Ideally not depend on dbus
It could just be a wrapper around dconf, but dconf is owned by GNOME. Not only is this not cross platform (we'd need another implementation for other systems), but depending on anything GNOME related is a horrible idea. Additionally, holding all settings within a single file is kind of a bad idea.
The configuration system should:
- Categorize application configurations by application ID
- Have system and user-level configurations for each application
- System level configurations specify default values
- User level configurations override system level values
- Be able to handle multiple locations for system-level config dirs to support XDG base dirs
- Just one location for the user-level config
- Each dir contains a single config file
- Have a user-readable, editable file format
- Preserve unused values
- Store values losslessly in all cases
- Be primarily key/value structured
- Alert the application when a setting has changed, either by use of an API, or by the user or an external program
- Watch all files
- When a file gets changed, reload it and find out what value(s) were altered
- Have some global Nasin settings that apply to all applications, possibly with the ID xyz.holanet.Nasin
- This would be read by Nasin itself and used to read things like the theme, scaling, etc.
- The handle to this must be made available through a function
- Ideally not depend on dbus
typeConfigstruct{...}// Set sets a value in the user-level config file.func(*Config)Set(keystring,valueany)error// Get gets a value, considering all config files.func(*Config)Get(keystring,fallbackany)(any,error)// Reset removes the value from the user-level config file, resetting it to// what is described by the system-level config files.func(*Config)Reset(keystring)error// OnChange specifies a function to be called whenever a value is changed.// The callback is always run within the backend's event loop using tomo.Do.func(*Config)OnChange(func(keystring))// Close closes the config, causing it to stop watching for changes. Reads// or writes to the config after this will return an error.func(*Config)Close()error// ApplicationConfig returns a new config for the application in question.// It must be closed after use unless it is supposed to remain open for the// entire duration the application is running.funcApplicationConfig(ApplicationDescription)*Config// ApplicationConfig returns the global config. This should not be closed.funcGlobalConfig()*Config
The API could look like:
```go
type Config struct { ... }
// Set sets a value in the user-level config file.
func (*Config) Set (key string, value any) error
// Get gets a value, considering all config files.
func (*Config) Get (key string, fallback any) (any, error)
// Reset removes the value from the user-level config file, resetting it to
// what is described by the system-level config files.
func (*Config) Reset (key string) error
// OnChange specifies a function to be called whenever a value is changed.
// The callback is always run within the backend's event loop using tomo.Do.
func (*Config) OnChange (func (key string))
// Close closes the config, causing it to stop watching for changes. Reads
// or writes to the config after this will return an error.
func (*Config) Close () error
// ApplicationConfig returns a new config for the application in question.
// It must be closed after use unless it is supposed to remain open for the
// entire duration the application is running.
func ApplicationConfig (ApplicationDescription) *Config
// ApplicationConfig returns the global config. This should not be closed.
func GlobalConfig () *Config
```
Alternatively, the pattern used for canvases and textures could be used, providing more explicit ownership of resources (in this case a goroutine):
```go
type Config interface {
Set (key string, value any) error
Get (key string, fallback any) (any, error)
Reset (key string) error
OnChange (func (key string))
}
type ConfigCloser interface {
Config
io.Closer
}
func ApplicationConfig (ApplicationDescription) ConfigCloser
func GlobalConfig () Config
```
One thing I didn't consider is that any callback given in OnChange will run in the backend's event loop. Now, this is the Tomo application framework, but having a system running in another goroutine magically be able to run callbacks in the main event loop is ridiculous and will cause problems. What if the backend is not running? It should not depend on Tomo at all, ideally. Perhaps OnChange could be replaced with a Changes function that returns a channel of names:
Changes()<-chanstring
Each time a value is modified, the name of that value would be sent along the channel. Each time this function is called, a new channel will be returned, and all of them will get the same information. When a receiver closes a channel, the Config will remove it from the list and stop sending to it. When the Config is stopped, all returned channels are closed.
One thing I didn't consider is that any callback given in OnChange will run in the backend's event loop. Now, this is the Tomo application framework, but having a system running in another goroutine magically be able to run callbacks in the main event loop is ridiculous and will cause problems. What if the backend is not running? It should not depend on Tomo at all, ideally. Perhaps OnChange could be replaced with a Changes function that returns a channel of names:
```go
Changes () <- chan string
```
Each time a value is modified, the name of that value would be sent along the channel. Each time this function is called, a new channel will be returned, and all of them will get the same information. When a receiver closes a channel, the Config will remove it from the list and stop sending to it. When the Config is stopped, all returned channels are closed.
Never mind that last comment. When there is no backend, tomo.Do simply does nothing. The application ought not to be running while there is no backend anyways. The simplicity gains from having it call tomo.Do outweigh the cursedness. What gosh darned application developer wants to manage goroutines just to stay updated on config values?
Never mind that last comment. When there is no backend, tomo.Do simply does nothing. The application ought not to be running while there is no backend anyways. The simplicity gains from having it call tomo.Do outweigh the cursedness. What gosh darned application developer wants to manage goroutines just to stay updated on config values?
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
It could just be a wrapper around dconf, but dconf is owned by GNOME. Not only is this not cross platform (we'd need another implementation for other systems), but depending on anything GNOME related is a horrible idea. Additionally, holding all settings within a single file is kind of a bad idea.
The configuration system should:
The API could look like:
Alternatively, the pattern used for canvases and textures could be used, providing more explicit ownership of resources (in this case a goroutine):
One thing I didn't consider is that any callback given in OnChange will run in the backend's event loop. Now, this is the Tomo application framework, but having a system running in another goroutine magically be able to run callbacks in the main event loop is ridiculous and will cause problems. What if the backend is not running? It should not depend on Tomo at all, ideally. Perhaps OnChange could be replaced with a Changes function that returns a channel of names:
Each time a value is modified, the name of that value would be sent along the channel. Each time this function is called, a new channel will be returned, and all of them will get the same information. When a receiver closes a channel, the Config will remove it from the list and stop sending to it. When the Config is stopped, all returned channels are closed.
Never mind that last comment. When there is no backend, tomo.Do simply does nothing. The application ought not to be running while there is no backend anyways. The simplicity gains from having it call tomo.Do outweigh the cursedness. What gosh darned application developer wants to manage goroutines just to stay updated on config values?