qBittorrent Settings Migration Between Versions: How the Upgrade System Works

qBittorrent automatically migrates settings between versions by checking a hidden Meta/MigrationVersion key at startup and executing incremental transformation steps defined in src/app/upgrade.cpp before the UI loads.

qBittorrent stores all user-configurable options in native QSettings files (qBittorrent.ini on Windows, qBittorrent.conf on Linux/macOS). When upgrading between releases, the application seamlessly transforms legacy configuration keys into the new schema without requiring manual intervention. This article examines the migration mechanism implemented in the qbittorrent/qBittorrent repository, covering the startup trigger, atomic persistence, and crash-safe design.

The Migration Entry Point in application.cpp

The migration sequence fires early during application initialization. In src/app/application.cpp, the Application::run() method creates the SettingsStorage singleton and immediately invokes the upgrade() function at line 343:

// src/app/application.cpp
int Application::run()
{
    SettingsStorage::initInstance();
    if (!upgrade()) {
        // Migration failed – log and abort
    }
    // Continue with UI initialization...
}

This ensures all settings transformations complete before any UI or network components access configuration values.

Version Tracking and Incremental Upgrades

The migration logic resides in src/app/upgrade.cpp. The system compares the stored migration version against the current constant MIGRATION_VERSION (defined as 10 at line 51).

At line 31, the code reads the stored version using a CachedSettingValue<int> wrapper:

// src/app/upgrade.cpp (lines 29-53)
CachedSettingValue<int> version(u"Meta/MigrationVersion"_s, 0);

if (version != MIGRATION_VERSION) {
    // Execute missing migration steps
}

For each version gap, upgrade() invokes dedicated helper functions such as:

  • exportWebUIHttpsFiles()
  • upgradeTorrentContentLayout()
  • migrateProxySettings()

These functions manipulate the in-memory settings map, and the changes persist automatically via the storage layer’s deferred write mechanism.

Core Components of the Migration System

SettingsStorage Singleton

The SettingsStorage class (defined in src/base/settingsstorage.h and implemented in src/base/settingsstorage.cpp) wraps QSettings with crash-safe persistence. Key responsibilities include:

  • Atomic writes: The writeNativeSettings() method (lines 73-119) writes to a temporary "*_new" file and renames it atomically to prevent corruption during power loss or disk-full conditions
  • Recovery: readNativeSettings() (lines 15-45) detects stray "*_new" files left by previous crashes and renames them back to the final filename (lines 39-56)
  • Type-safe API: Templated loadValue<T>() and storeValue<T>() methods handle conversion between C++ types and QVariant

CachedSettingValue Wrapper

The CachedSettingValue<int> template class caches the migration version in memory to avoid repeated disk reads during the upgrade check.

The Migration Workflow Step-by-Step

The upgrade process follows a strict sequence to ensure data integrity:

  1. Load native settings: SettingsStorage::readNativeSettings() populates an internal QVariantHash named m_data from the INI/CONF file

  2. Determine version gap: The upgrade() function compares the stored Meta/MigrationVersion against MIGRATION_VERSION. If the stored value is lower, it runs the missing steps in ascending order

  3. Execute transformation: Each migration helper reads legacy keys, computes new values, and stores them. For example, converting integer-based proxy types to enum-based values:

// src/app/upgrade.cpp – migrateProxySettingsEnum() (lines 64-84)
void migrateProxySettingsEnum()
{
    auto *settingsStorage = SettingsStorage::instance();
    const auto key = u"Network/Proxy/Type"_s;
    const auto value = settingsStorage->loadValue<QString>(key);
    bool ok = false;
    const auto number = value.toInt(&ok);
    if (ok) {
        switch (number) {
            case 0: settingsStorage->storeValue(key, Net::ProxyType::None); break;
            case 1: settingsStorage->storeValue(key, Net::ProxyType::HTTP); break;
            case 2: settingsStorage->storeValue(key, Net::ProxyType::SOCKS5); break;
            default:
                LogMsg(QCoreApplication::translate("Upgrade",
                    "Invalid value ..."), Log::WARNING);
                settingsStorage->removeValue(key);
        }
    }
}
  1. Defer persistence: Each storeValue() call sets a dirty flag. A single-shot QTimer (5-second delay) triggers SettingsStorage::save(), batching all modifications into one atomic write

  2. Update version marker: After successful migration, line 80 writes the new version back to Meta/MigrationVersion, skipping future migrations:

// src/app/upgrade.cpp (line 80)
version = MIGRATION_VERSION;

Crash Safety and Atomic Persistence

The migration system employs defensive programming to protect configuration integrity:

  • Temporary file pattern: When writing settings, the code creates a "qBittorrent_new.ini" (or equivalent) file and performs an atomic rename. If the application crashes during write, the original file remains untouched
  • Automatic recovery: On the next startup, readNativeSettings() detects the orphaned "*_new" file and completes the replacement (lines 39-56 of settingsstorage.cpp)
  • Batch updates: The 5-second deferred write timer minimizes I/O operations and ensures partial updates cannot occur between migration steps

Summary

  • qBittorrent settings migration runs automatically at startup via upgrade() in src/app/application.cpp before UI initialization
  • The system tracks migration progress using the Meta/MigrationVersion key and the constant MIGRATION_VERSION (currently 10)
  • SettingsStorage provides crash-safe persistence through atomic file replacement and temporary "*_new" files
  • Migration helpers in src/app/upgrade.cpp transform legacy values (such as converting integer proxy types to enums) using type-safe loadValue<T>() and storeValue<T>() APIs
  • Changes persist via a deferred write mechanism using QTimer to batch disk operations

Frequently Asked Questions

Where does qBittorrent store the migration version?

qBittorrent stores the migration version in the Meta/MigrationVersion key within the native QSettings file. This value is read using a CachedSettingValue<int> wrapper at startup in src/app/upgrade.cpp to determine which incremental upgrades to apply.

What happens if qBittorrent crashes during settings migration?

The migration is designed to be crash-safe. SettingsStorage writes to a temporary "*_new" file and renames it atomically only after the write completes. If a crash occurs, the next startup detects the orphaned temporary file in readNativeSettings() (lines 39-56 of src/base/settingsstorage.cpp) and recovers by renaming it to the final configuration file.

How do I manually trigger a settings migration in qBittorrent?

You cannot manually trigger the migration process. The upgrade() function runs automatically when Application::run() detects that the stored Meta/MigrationVersion is lower than the current MIGRATION_VERSION constant. If you need to force a re-migration, you would need to manually edit the configuration file to lower the Meta/MigrationVersion value, which is not recommended for production use.

Are old settings keys deleted after migration?

Some migration functions call removeValue(oldKey) to clean up legacy entries, while others leave them in place for backward compatibility. The specific behavior depends on the individual migration step implemented in src/app/upgrade.cpp. The system prioritizes safety over cleanup, ensuring that unrecognized keys do not cause application errors.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →