Add Built-in Configuration Key Migration for Future Changes
Issue Description
Recent updates to the configuration keys in config.ini (e.g., renaming template_folder_album to album_folder_template) required manual migration of existing user configurations. Without a built-in migration mechanism, users must manually update their config.ini files or risk losing custom settings, which can be error-prone and frustrating, especially for non-technical users.
Proposed Solution
To improve user experience, I propose adding a built-in migration feature to automatically handle configuration key changes in future updates. This could:
- Detect old keys in
config.iniand migrate their values to new keys. - Remove deprecated keys.
This feature would ensure seamless upgrades, preserve user customizations, and reduce support overhead.
Example Migration Script
Below is a Python script I wrote to handle the recent configuration changes. It can serve as a reference for implementing a built-in migration feature. The script:
- Accepts a
--configCLI argument (defaults to%UserProfile%\.gamdl\config.inion Windows). - Creates a timestamped backup (e.g.,
config.ini.bak.YYYYMMDD_hhmmss). - Migrates old keys to new ones in the
[gamdl]section, overwriting any existing new keys to preserve user customizations. - Removes deprecated keys (
no_synced_lyrics,synced_lyrics_only). - Outputs a detailed summary of changes.
import argparse
import configparser
import os
import sys
import shutil
from datetime import datetime
def migrate_config(config_path: str) -> None:
"""Migrate old configuration keys to new ones in the INI file."""
if not os.path.exists(config_path):
raise FileNotFoundError(f"Config file not found: {config_path}")
# Create a timestamped backup before any changes
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{config_path}.bak.{timestamp}"
shutil.copy(config_path, backup_path)
print(f"Backup created: {backup_path}")
# Use ConfigParser with interpolation disabled to handle % in values
config = configparser.ConfigParser(interpolation=None)
config.read(config_path)
# Define the key renames based on changes
renames = {
"template_folder_album": "album_folder_template",
"template_folder_compilation": "compilation_folder_template",
"template_file_single_disc": "single_disc_folder_template",
"template_file_multi_disc": "multi_disc_folder_template",
"template_folder_no_album": "no_album_folder_template",
"template_file_no_album": "no_album_file_template",
"template_file_playlist": "playlist_file_template",
"template_date": "date_tag_template",
}
# Deprecated keys to remove (no migration)
deprecated = ["no_synced_lyrics", "synced_lyrics_only"]
# Use the specific section from the attached config
section = "gamdl"
if not config.has_section(section):
print(f"Section [{section}] not found; no migration performed.", file=sys.stderr)
return
changes = []
for old_key, new_key in renames.items():
if config.has_option(section, old_key):
value = config.get(section, old_key)
config.set(section, new_key, value)
config.remove_option(section, old_key)
changes.append(f"Migrated {old_key} to {new_key} with value: {value}")
for dep_key in deprecated:
if config.has_option(section, dep_key):
config.remove_option(section, dep_key)
changes.append(f"Removed deprecated key: {dep_key}")
if changes:
with open(config_path, "w", encoding="utf-8") as configfile:
config.write(configfile)
print("Migration completed.")
else:
print("No migrations needed.")
# Print summary
print("\nSummary of changes:")
if changes:
for change in changes:
print(f"- {change}")
else:
print("- No changes were made.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Migrate old config keys to new ones in config.ini.")
default_path = os.path.expandvars(r"%UserProfile%\.gamdl\config.ini")
parser.add_argument("--config", type=str, default=default_path, help="Path to config.ini file.")
args = parser.parse_args()
try:
migrate_config(args.config)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
Can someone explain the rationale for changing to INI (older, less flexible) from JSON?
Can someone explain the rationale for changing to INI (older, less flexible) from JSON?
It's easier to edit.
Something in your writing of the config keeps overwriting custom values with the defaults. Not migrating the configs when the switch was made, seems like a significant oversight since there was no mention of it in the change logs.