Skip to content

Conversation

@nxtum
Copy link
Contributor

@nxtum nxtum commented Aug 14, 2025

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)

@nxtum nxtum requested review from pwielders and sebaszm August 15, 2025 11:12
Copy link
Contributor

@pwielders pwielders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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...

#endif

/* static */ const TCHAR* Server::PluginOverrideFile = _T("PluginHost/override.json");
/* static */ const TCHAR* Server::PluginOverrideDirectory = _T("PluginHost/");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

// 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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 pwielders self-requested a review September 6, 2025 11:04
Copy link
Contributor

@pwielders pwielders left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions/Remark:

  1. Why is in the save, the way to create an override a lamda i.s.o. a normal private method?
  2. 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):

  1. 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 :-)
  2. enhance the Core::JSON::VariantContainer class with an bool operator ==(const VariantContainer& ) const and 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...


/* static */ const TCHAR* Server::PluginOverrideFile = _T("PluginHost/override.json");
/* static */ const TCHAR* Server::PluginConfigDirectory = _T("plugins/");
/* static */ const TCHAR* Server::PluginOverrideDirectory = _T("Thunder/plugins/");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use EXPAND_AND_QUOTE(NAMESPACE) instead of using framework name directly.


// 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"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the constant instead of repeating the path name.

Comment on lines 1780 to 1790
// 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))
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 1821 to 1827
// 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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

uint32_t result = Core::ERROR_NONE;

auto CreateOverride = [this, &result](ServiceMap::Iterator& indexService) {
const string currentCallsign = indexService->Callsign();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A copy!!

}

Core::File storage(indexFile->second);
if (storage.Create() == true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +4859 to +4871
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);
Copy link
Contributor

@sebaszm sebaszm Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

@sebaszm
Copy link
Contributor

sebaszm commented Sep 11, 2025

  1. 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 :-)
  2. enhance the Core::JSON::VariantContainer class with an bool operator ==(const VariantContainer& ) const and 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...

  1. does not solve the problem much if Store() is used with no callsign then all plugins will be marked dirty even if not changed.

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.

@sebaszm sebaszm self-requested a review October 1, 2025 13:22
_serverconfig.SetIdleTime(IdleTime.Value());
// Convey the real JSON struct information into the specific services.
ServiceMap::Iterator index(_services.Services());
Clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this clears the whole object (ie. all services). Not only the element you're trying to load. So it seems to me that after all iterations you'll end up only with the last one loaded.

ServiceMap::Iterator index(_services.Services());
Clear();
// Read the file and parse it into this object.
IElement::FromFile(storage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires that the saved JSON has extra level of indirection "servces": { "name": { ... } }.

uint32_t result = Core::ERROR_NONE;

Core::File storage(_fileName);
SavePluginHostConfig();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra pluginhost config is saved each and every time a Save is made to all or any plugin.

if (storage.Create() == true) {

// Clear all currently set values, they might be from the previous run.
Clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again we clear everything up here.

SavePluginHostConfig();

if (storage.Create() == true) {
auto CreateOverride = [this, &result](const ServiceMap::Iterator& indexService) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Perhaps it's more readable if we move this to a meber method insteadof lambda?

}

return (result);
void SavePluginHostConfig() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always saves the elements even if they have not changed from defaults.

Prefix = _serverconfig.Prefix();
IdleTime = _serverconfig.IdleTime();

IElement::ToFile(storage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again I believe this will save all config to this file.

}

Core::JSON::Container Services;
string CreateOverridePath(const string& callsign) const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: I think I would be a lot more readable we simply had persistetFolder + callsign + ".json" :)

Comment on lines +90 to +95
DEPRECATED Core::hresult Persist() { return Persist(Core::OptionalType<string>());}

// @alt storeconfig
// @brief Stores all configuration to the persistent memory
virtual Core::hresult Persist() = 0;
// @brief Stores configuration to the persistent memory
// @param callsign: Callsign to persist
virtual Core::hresult Persist(const Core::OptionalType<string>& callsign) = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not merge the two Persist method the same way Restore is defined?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants