import json from os.path import exists from discord import Guild from config import CONFIG class Storage: """ Static class for managing persisted bot state. """ # discord.Guild.id -> dict __guild_id_to_state = {} def state_for_guild(guild: Guild) -> dict: """ Returns the state for the given guild, loading from disk if necessary. Always returns a dict. """ state: dict = __guild_id_to_state[guild.id] if state is not None: # Already in memory return state # Load from disk if possible __trace(f'No loaded state for guild {guild.id}. Attempting to load from disk.') state = __load_guild_state(guild) __guild_id_to_state[guild.id] = state return state def save_guild_state(guild: Guild) -> NoneType: """ Saves state for the given guild to disk, if any exists. """ state: dict = __guild_id_to_state[guild.id] if state is None: # Nothing to save return __save_guild_state(guild, state) def __save_guild_state(guild: Guild, state: dict) -> NoneType: """ Saves state for a guild to a JSON file on disk. """ path: str = __guild_path(guild) __trace('Saving state for guild {guild.id} to {path}') with open(path, 'w') as file: # Pretty printing to make more legible for debugging # Sorting keys to help with diffs json.dump(state, file, indent='\t', sort_keys=True) __trace('State saved') def __load_guild_state(guild: Guild) -> dict: """ Loads state for a guild from a JSON file on disk. """ path: str = __guild_path(guild) if not exists(path): __trace(f'No state on disk for guild {guild.id}. Returning {{}}.') return {} __trace('Loading state from disk for guild {guild.id}') with open(path, 'r') as file: state = json.load(file) __trace('State loaded') return state def __guild_path(guild: Guild) -> str: """ Returns the JSON file path where guild state should be written. """ config_value: str = CONFIG['statePath'] path: str = config_value if config_value.endswith('/') else f'{config_value}/' return f'{path}guild_{guild.id}.json' def __trace(message: str) -> NoneType: print(f'{Storage.__name__}: {message}')