natinusala / borealis

Hardware accelerated, controller and TV oriented UI library for PC and Nintendo Switch (libnx)
Apache License 2.0
255 stars 81 forks source link

Storage system #140

Open natinusala opened 3 years ago

natinusala commented 3 years ago

Storage can be used to store anything on disk between app "sessions". The aim is not for the user to configure the app, so the files don't have to be human readable and most importantly the files don't need to contain every entry with their default value, like a config file would.

This is different from a "config" engine, which purpose is for the user to configure the app. Here, the storage is used for the app to maintain its state between runs, including but not limited to config. For instance, a "has the setup wizard been executed yet" boolean can be kept in the storage, but it's not semantically a config field. A list of favorited entries also belongs in the storage, and can be considered configuration, but it doesn't matter.

A storage file is in XML, to avoid adding yet another language library (sorry TOML).

Each platform implementation will give the path to where the storage files are to be saved and read. On Switch it can be /config/app-name even though it's not technically config, we'll keep the standard.

Each storage file contains one or more "properties", that are strongly typed.

For the API, I want the entries to be typed and checked at compile time, in a declarative manner (so header only). No code should be required to get it going. We can use macros for that.

For instance the following code (types such as bool, string, string list... and how they are defined are only for example purposes):

#include <borealis.hpp>

class SettingsStorage : public brls::StorageFile
{
  BRLS_STORAGE_FILE_INIT("settings");

  BRLS_STORAGE_BOOL(wizardShown, "wizard_shown");

  BRLS_STORAGE_STRING(username, "username");
  BRLS_STORAGE_STRING(password, "password");

  BRLS_STORAGE_STRING_LIST(favorites, "favorites");

  BRLS_STORAGE_STRING_INT_MAP(launchCount, "launch_count");
}

Would be used this way:

SettingsStorage* settings = SettingsStorage::open();

bool wizardShown = settings->wizardShown; // cast operator overload

for (std::string favorite : settings->favorites)
    brls::Logger::info("{}", favorite);

settings->username = "h4ck3rm4n"; // assignment operator overload
settings->username.save(); // to save only one property

settings->favorites.push_back("borealis");

settings->save(); // to save all edited properties

And would generate the following XML in /config/app-name/settings.xml:

<brls:StorageFile>
    <brls:BoolProperty name="wizard_shown" value="true" />

    <brls:BoolProperty name="username" value="h4ck3rm4n" />

    <brls:StringListProperty name="favorites">
        <brls:Value value="borealis" />
    </brls:StringListProperty>
</brls>

This is of course just a draft / example of how I would like it to be. I'm sure typing can be handled better than that.

Automatic enum conversion would be nice to have too.

EmreTech commented 3 years ago

Just a suggestion: I like the idea, but maybe we could give people the option of using JSON files instead of XML files.

natinusala commented 3 years ago

Well the library is already packed with dependencies, I don't want to add yet another fat library. I'll remove nlohmann json so that everything uses XML.

Plus, the user should not know or care what's behind a StorageFile since they are not supposed to tamper with it anyway. It could be an SQLite database or a Discord message for all they care.

EmreTech commented 3 years ago

That's fair enough. XML is a good choice, I thought JSON could be simpler to read.

natinusala commented 3 years ago

If it was meant for users I would have used TOML ;)

EmreTech commented 3 years ago

I looked at TOML, and yeah I see why that would be the case. Just by looking at one example file, it looks user-friendly.

natinusala commented 3 years ago

In progress via #146