Local Database
Relevant source files
The following files were used as context for generating this wiki page:
The "Local Database" in SOFL is not a traditional monolithic database (like SQLite) but rather a file-based persistence system. It consists of a collection of individual JSON files, where each file represents a single game in the user's library. This approach provides a simple, human-readable, and portable way to store game metadata.
The core of this system is the Store class, which acts as an in-memory data registry, and the FileManager, which handles the reading and writing of game data to the filesystem. All game data is stored in a dedicated directory, typically located at ~/.local/share/sofl/games/.
Sources: sofl/main.py:141-147, sofl/store/managers/file_manager.py
Architecture
The local data storage architecture is centered around the Store class, which orchestrates various data Manager classes. For persistence, the FileManager is key. It is responsible for serializing Game objects into JSON files and deserializing them back into objects upon application startup.
This diagram illustrates the main components involved in local data persistence.
- SOFLApplication: The main application class that initializes the
Storeand triggers the initial data load. - Store: A central registry holding all game objects in memory and managing different data processors (Managers).
- FileManager: A specific manager responsible for handling the serialization and deserialization of game data to and from JSON files on disk.
- Game JSON Files: Individual
.jsonfiles, one per game, stored in thegames_dir.
Sources: sofl/main.py:68-71, sofl/store/store.py, sofl/store/managers/file_manager.py
Data Model: The Game Object
The fundamental unit of data is the Game object. Each Game instance holds all metadata for a single game, such as its name, executable path, and source. This object is what gets serialized to a JSON file.
Sources: sofl/game.py
Game Properties
The following table details the key attributes of a Game object, which correspond to the fields in the JSON files.
| Property | Type | Description | Source File |
|---|---|---|---|
game_id | str | A unique identifier for the game (e.g., "imported_1", "steam_440"). | sofl/game.py |
name | str | The display name of the game. | sofl/game.py |
executable | str | The command or path to the game's executable. | sofl/game.py |
developer | Optional[str] | The developer of the game. | sofl/details_dialog.py:202 |
source | str | The origin of the game (e.g., "steam", "lutris", "online-fix", "imported"). | sofl/game.py |
added | int | Timestamp of when the game was added. | sofl/details_dialog.py:236 |
last_played | int | Timestamp of the last time the game was launched. | sofl/main.py:164 |
hidden | bool | A flag to determine if the game is hidden in the UI. | sofl/game.py |
removed | bool | A flag for soft-deleting a game. | sofl/preferences.py:350 |
JSON File Structure
Each game is stored as a JSON file named {game_id}.json. Below is an example of what a manually added game's file might look like.
{
"game_id": "online-fix_1",
"name": "My Awesome Game",
"executable": "/home/user/Games/MyAwesomeGame/start.sh",
"developer": "Indie Dev",
"source": "online-fix",
"added": 1672531200,
"last_played": 1672534800,
"hidden": false,
"removed": false
}
Sources: sofl/store/managers/file_manager.py:33-40, sofl/details_dialog.py:213-240
Data Flow
The processes for loading, creating, and updating game data are managed through a clear flow of operations between the UI, the in-memory Store, and the filesystem.
Loading Games at Startup
When SOFL starts, it populates the in-memory Store with game data from the JSON files on disk.
This sequence diagram shows the application startup process.
Sources: sofl/main.py:141-147
Creating a New Game
New games can be added manually through the "Add New Game" dialog. This process creates a new Game object and persists it as a new JSON file.
Sources: sofl/details_dialog.py:195-240
Updating an Existing Game
When a user edits a game's details, the corresponding Game object in the Store is updated, and the changes are immediately saved to its JSON file.
The game.save() method triggers the FileManager to process the game through its pipeline, which involves writing the object's current state to disk.
# From sofl/game.py
class Game:
# ...
def save(self) -> None:
"""Save the game to disk"""
shared.store.run_pipelines(self)
# From sofl/store/managers/file_manager.py
class FileManager(Manager):
# ...
def process_game(
self, game: Game, _additional_data: dict, callback: Callable
) -> None:
# ...
json.dump(
game.data.to_dict(),
game_file.open("w", encoding="utf-8"),
cls=self.encoder,
)
# ...
Sources: sofl/game.py, sofl/store/managers/file_manager.py:33-40
Data Management and Cleanup
The application provides several ways to manage the local database, primarily through the Preferences dialog.
- Importing Games: The
Importerclass scans sources like Steam and Lutris and creates new game entries. These new games are then persisted as JSON files by theFileManager. - Removing Missing Games: If the "Remove missing games on import" setting is enabled, the
Importerwill mark games asremoved: trueif they are no longer found in their original source during an import. This is a soft delete. - Removing All Games: The "Remove All Games" button in the preferences window iterates through all games in the
Storeand sets theirremovedproperty totrue, effectively hiding them from the library. This action can be undone. - Resetting the Application: A debug option available in development mode, "Reset App", completely wipes the application's data directories, including
~/.local/share/sofl/games, providing a hard reset.
Sources: sofl/importer/importer.py:161-181, sofl/preferences.py:346-358, 369-383
Conclusion
SOFL's local database is a straightforward and robust file-based system. By treating each game as an independent JSON document, it avoids the complexity of a relational database while providing easy access, modification, and portability of user data. The Store and FileManager classes work in tandem to provide a clean separation between in-memory data management and on-disk persistence, making the system both efficient and easy to maintain.