IController - Persist and Restore #1920
Conversation
pwielders
left a comment
There was a problem hiding this comment.
An overall remark. I guess in the past, because all plugins where in 1 file, we added an empty config ('{}') for plugins that had no override or where destroyed. However now the configs are single entries per callsign. Whenever you "add" a file it probably takes up a sector on flash storage per file (much more than two byes to signal {} in the one file approach). I guess it is worth the effort to not store an override file if there are no changes to to config. So suggest to add this optimization in the code.
No changes on the plugin, no override file in the thunder/plugins directpry o persitent storage space to safe flash sectors...
Source/Thunder/PluginServer.cpp
Outdated
| #endif | ||
|
|
||
| /* static */ const TCHAR* Server::PluginOverrideFile = _T("PluginHost/override.json"); | ||
| /* static */ const TCHAR* Server::PluginOverrideDirectory = _T("PluginHost/"); |
There was a problem hiding this comment.
Might be good to move the directory now to Thunder/plugins and store the configs there, it is in the same line as the fixed configs are. Maybe you could even swap this line than with the nex line and use the default of the plugins config from there so place this:
/* static */ const TCHAR* Server::PluginOverrideDirectory = _T("Thunder/") + Server::PluginConfigDirectory;
on the line "after" the Server::PluginConfigDirectory declaration
Source/Thunder/PluginServer.h
Outdated
| // Create individual plugin configuration files | ||
| string filePath; | ||
| filePath.reserve(persistentFolder.size() + name.size() + sizeof("Override.json") - 1); | ||
| filePath.append(persistentFolder).append(name).append("Override.json"); |
There was a problem hiding this comment.
If we put them all in a folder (e.g. previous remark Thunder/plugins. I guess we could than use the same sematics as we do in thunder an only append the name with json, so:
filePath.append(persistentFolder).append(name).append(".json");
than it is consistent with the configs we find in /etc/Thunder/plugins as the startin point. Consistency is key ;-)
pwielders
left a comment
There was a problem hiding this comment.
Questions/Remark:
- Why is in the save, the way to create an override a lamda i.s.o. a normal private method?
- Comparing if the configs are different might be a bit more tricky. Cause officially:
{ "fieldA": 1, "fieldB" : 2 } === { "fieldB": 2, "fieldA": 1}
This is now detected as different configs.
And probbaly even worse:
{ "field": 1 } === {"field":1}
Is already detected as a different config :-)
got 2 suggestions to avoid this (or make it more clear):
- Use a flag on the service that if the "config" is set through a method, flag it as dirty and consider the config to be changed (even if it was not changed :-)
- enhance the Core::JSON::VariantContainer class with an
bool operator ==(const VariantContainer& ) constand load each string into the VariantContainer class and compare those (field availability and there corresponding values)
I have a preference for the second option enhance the JSON class :-) might be usefull in more places and the VariantContainer is already used anway, somewhere in the PluginServer code but aI am also interested in @sebaszm opinion here...
Source/Thunder/PluginServer.cpp
Outdated
|
|
||
| /* static */ const TCHAR* Server::PluginOverrideFile = _T("PluginHost/override.json"); | ||
| /* static */ const TCHAR* Server::PluginConfigDirectory = _T("plugins/"); | ||
| /* static */ const TCHAR* Server::PluginOverrideDirectory = _T("Thunder/plugins/"); |
There was a problem hiding this comment.
Use EXPAND_AND_QUOTE(NAMESPACE) instead of using framework name directly.
Source/Thunder/PluginServer.cpp
Outdated
|
|
||
| // See if the persitent path for our-selves exist, if not we will create it :-) | ||
| Core::File persistentPath(_config.PersistentPath() + _T("PluginHost")); | ||
| Core::File persistentPath(_config.PersistentPath() + _T("Thunder/plugins")); |
There was a problem hiding this comment.
Use the constant instead of repeating the path name.
Source/Thunder/PluginServer.h
Outdated
| // Create individual plugin configuration files | ||
| string filePath; | ||
| filePath.reserve(persistentFolder.size() + name.size() + sizeof(".json") - 1); | ||
| filePath.append(persistentFolder).append(name).append(".json"); | ||
|
|
||
| _pluginOverrideFiles.emplace( | ||
| std::piecewise_construct, | ||
| std::forward_as_tuple(name), | ||
| std::forward_as_tuple(std::move(filePath)) | ||
| ); | ||
|
|
There was a problem hiding this comment.
Do we really need to remember the filenames for each callsign? if we only stored the base folder having a callsing we can every time find the right path. I don't think configs are being overiden so frequent to justify this flamboyance with memory.
Source/Thunder/PluginServer.h
Outdated
| // Clear all currently set values, they might be from the previous run. | ||
| Clear(); | ||
|
|
||
| Callsigns::const_iterator current(_callsigns.find(index->Callsign())); | ||
| // Read the file and parse it into this object. | ||
| IElement::FromFile(storage); | ||
| _serverconfig.SetPrefix(Prefix.Value()); | ||
| _serverconfig.SetIdleTime(IdleTime.Value()); |
There was a problem hiding this comment.
This part shouldn't be repeated for each plugin config. This clears and looks for prefix and idletime in each of the plugins' config while this is Thunder property.
Source/Thunder/PluginServer.h
Outdated
| uint32_t result = Core::ERROR_NONE; | ||
|
|
||
| auto CreateOverride = [this, &result](ServiceMap::Iterator& indexService) { | ||
| const string currentCallsign = indexService->Callsign(); |
Source/Thunder/PluginServer.h
Outdated
| } | ||
|
|
||
| Core::File storage(indexFile->second); | ||
| if (storage.Create() == true) { |
There was a problem hiding this comment.
Let's not write the override config at all if it's not changed! Let the absence of the override file mean there is no override.
| Override infoBlob(_config, _services, Configuration().PersistentPath() + PluginOverrideDirectory); | ||
| return (infoBlob.Save(callsign)); | ||
| } | ||
|
|
||
| return (infoBlob.Save()); | ||
| uint32_t Restore(const Core::OptionalType<string>& callsign) | ||
| { | ||
| Override infoBlob(_config, _services, Configuration().PersistentPath() + PluginOverrideDirectory); | ||
| return (infoBlob.Destroy(callsign)); | ||
| } | ||
|
|
||
| uint32_t Load() | ||
| { | ||
| Override infoBlob(_config, _services, Configuration().PersistentPath() + PluginOverrideFile); | ||
|
|
||
| Override infoBlob(_config, _services, Configuration().PersistentPath() + PluginOverrideDirectory); |
There was a problem hiding this comment.
I find this approach inefficient. Even if we are working with a single callsign we still create the Override object and prepare for handling all plugins (ie. saving callsiings, configs, filenames.... etc).
Agree, to do this properly we would need to normalize the JSON first. JSON::Variant would be a perfect starting point to achieve this (it is though behind COMPLEMENTARY_CODE_SET flag). We however will encounter nuance like order of elements in array (is [a,b] same as [b,a]? depends), or explicitly setting a value that is same as a default. I think too that it would be a useful feature, but perhaps we should split this into another PR. |
There was a problem hiding this comment.
Pull request overview
This pull request adds granular control to plugin configuration persistence by allowing users to persist or restore individual plugins rather than all plugins at once. The changes transform the single "persist all" functionality into a flexible system that supports persisting/restoring specific plugins, all plugins, or server-wide configuration.
Changes:
- Extended
Persistmethod to accept an optional callsign parameter, allowing selective persistence of individual plugins or all plugins - Added new
Restoremethod to remove persisted configuration overrides and return plugins to their default state - Refactored the storage system from a single
override.jsonfile to individual per-plugin JSON files in a directory structure
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| Source/plugins/IController.h | Updated IConfiguration interface with parameterized Persist method and new Restore method |
| Source/Thunder/PluginServer.h | Major refactoring of Override class: changed from single-file to directory-based storage, implemented Save/Destroy methods with per-plugin file handling, added PluginHost-specific configuration methods |
| Source/Thunder/PluginServer.cpp | Updated PluginOverrideFile constant to PluginOverrideDirectory, fixed typo in comment |
| Source/Thunder/Controller.h | Added Restore method declaration and updated Persist signature |
| Source/Thunder/Controller.cpp | Implemented new Restore method and updated Persist to forward callsign parameter |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (callsign.IsSet() == true) { | ||
| bool found = false; | ||
| const string& raw = callsign.Value(); | ||
| const string target = raw.empty() ? _T("Controller") : raw; |
There was a problem hiding this comment.
When an empty string is passed as callsign (callsign.IsSet() is true but callsign.Value() is empty), it gets mapped to "Controller" at line 1828. This special case should be documented in the function or the IController interface, as it's not obvious that an empty string means "Controller".
| if (callsign.IsSet() == true) { | ||
| bool found = false; | ||
| const string& raw = callsign.Value(); | ||
| const string target = raw.empty() ? _T("Controller") : raw; |
There was a problem hiding this comment.
When an empty string is passed as callsign (callsign.IsSet() is true but callsign.Value() is empty), it gets mapped to "Controller" at line 1866. This special case should be documented in the function or the IController interface, as it's not obvious that an empty string means "Controller". This duplicates the same logic from the Save method.
Previously there was one Persist function that would save the configs for all plugins without the ability to undo.
Now, you can specify which plugin to persist, as well as remove any/all configs (restore to default)