Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Thunderbird/docs/developer/   (Thunderbird Email Client Version 5.6©)  Datei vom 22.9.2026 mit Größe 6 kB image not shown  

Quelle  preference-migration-guide.md  Sprache: unbekannt

 
Spracherkennung für: .md vermutete Sprache: Abap {ABAP[88] SPS[52] Delphi[48]} [Methode: maximale Elemente, drei Dimensionen]

# Preference Migration Guide

This document explains how to update and migrate preferences in Thunderbird for Android.

## Overview

Thunderbird for Android uses a dedicated SQLite database to store its preferences as key-value pairs.
Both the database file and the primary table are named `preferences_storage`. This is managed by the
`K9StoragePersister`.

- **Database Name:** `preferences_storage`
- **Storage Table:** `preferences_storage`
- **Columns:** `primkey` (TEXT), `value` (TEXT)
- **Persister:** `legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java`
- **Migration Manager:** `legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt`

## When to use a Preference Migration

You must implement a preference migration whenever you:

- Rename a preference key.
- Change the format of a preference value.
- Move a preference to a different storage location.
- Delete an obsolete preference.

## Adding a New Preference

If you are adding a completely new preference, you don't necessarily need a migration unless you need to populate it
with a default value that depends on other existing settings.

To add a new preference:

1. Define the key in the relevant component.
2. Use the `GeneralSettingsManager` or a specific preference manager to read and write the value.
3. If using the legacy `Storage` system directly, use `StorageEditor` to save the value.

> [!NOTE]
> If the new preference requires a default value that depends on existing settings, you must follow the
> [migration guide](#step-by-step-guide-for-migrations) below and bump the database version.

```kotlin
val editor = preferences.createStorageEditor()
editor.putBoolean("my_new_preference", true)
editor.commit()
```

## Step-by-Step Guide for Migrations

### 1. Bump the Preference Database Version

The `DB_VERSION` constant in `K9StoragePersister.java` must be incremented by exactly **1**. This version increase is what triggers the migration process.

```java
// legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java
public class K9StoragePersister implements StoragePersister {
    private static final int DB_VERSION = 29; // Increment this
    // ...
}
```

### 2. Create a New Migration Class

Create a new Kotlin class in `legacy/storage/src/main/java/com/fsck/k9/preferences/migration/` named `StorageMigrationToXX.kt`, where `XX` is the new version number.

Use the `StorageMigrationHelper` to interact with the database.

```kotlin
/**
 * Describe the purpose of the migration here.
 */

class StorageMigrationToXX(
    private val db: SQLiteDatabase,
    private val migrationsHelper: StorageMigrationHelper,
) {
    fun runMigration() {
        val oldValue = migrationsHelper.readValue(db, "old_key")
        if (oldValue != null) {
            // Perform transformation if needed
            migrationsHelper.insertValue(db, "new_key", oldValue)
            migrationsHelper.writeValue(db, "old_key", null) // Deletes old_key
        }
    }
}
```

### 3. Register the Migration

Add the new migration to the `StorageMigrations.upgradeDatabase()` method.

```kotlin
// legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt
internal object StorageMigrations {
    @JvmStatic
    fun upgradeDatabase(db: SQLiteDatabase, migrationsHelper: StorageMigrationHelper) {
        val oldVersion = db.version

        // ... existing migrations ...
        if (oldVersion < XX) StorageMigrationToXX(db, migrationsHelper).runMigration()
    }
}
```

## Requirements

- **No Application Logic:** Do not use `Preferences` or other high-level application classes inside migrations, as their behavior might change over time. Use `StorageMigrationHelper` for direct database access.
- **No Network Calls:** Do not perform network calls during the migration, as they may fail due to external factors. In case it is really necessary, properly handle network failures to avoid data wipeout.
- **Testing:** Always write unit tests for your migrations. See the [Writing Unit Tests](#writing-unit-tests) section for details.
- **Idempotency:** Migrations should be safe to run multiple times, although the framework typically ensures they run only once.
- **Self-contained:** Keep the migration logic simple and contained within its class.

## Writing Unit Tests

Writing unit tests for preference migrations is mandatory to ensure data integrity and prevent regressions. These tests use Robolectric to provide a realistic Android environment.

### Test Setup

Your test class should be located in `legacy/storage/src/test/java/com/fsck/k9/preferences/migration/` and named
`StorageMigrationToXXTest.kt`.

Basic template for a migration test:

```kotlin
@RunWith(RobolectricTestRunner::class)
class StorageMigrationToXXTest {
    private val database = createPreferencesDatabase()
    private val migrationHelper = DefaultStorageMigrationHelper()
    private val migration = StorageMigrationToXX(database, migrationHelper)

    @After
    fun tearDown() {
        database.close()
    }

    @Test
    fun `migration should rename old_key to new_key`() {
        // Arrange: Insert old data into the database
        migrationHelper.insertValue(database, "old_key", "some value")

        // Act: Run the migration
        migration.runMigration()

        // Assert: Verify the results
        val values = migrationHelper.readAllValues(database)
        assertThat(values).key("new_key").isEqualTo("some value")
        assertThat(values).doesNotContainKey("old_key")
    }
}
```

### Key Considerations for Testing

- **Initial State:** Always start with a known state by inserting the necessary keys and values into the database using `migrationHelper.insertValue()`.
- **Edge Cases:** Test scenarios where keys might be missing, have unexpected values, or are in an old format.
- **Data Integrity:** Verify that values are correctly transformed if the migration involves format changes.
- **Cleanup:** Ensure the database is closed in the `@After` method to avoid resource leaks between tests.

## Common Tasks

### Renaming a Key

Use `readValue` to get the old value, `insertValue` to set the new key, and `writeValue(db, key, null)` to remove the old key.

### Account-specific Preferences

Account preferences are prefixed with the account UUID. You can find all account UUIDs by reading the `accountUuids` key.

```kotlin
val accountUuids = migrationsHelper.readValue(db, "accountUuids")?.split(",") ?: emptyList()
for (uuid in accountUuids) {
    val key = "$uuid.some_preference"
    // ...
}
```

### Deleting Obsolete Keys

Simply use `migrationsHelper.writeValue(db, "obsolete_key", null)`.

[Dauer der Verarbeitung: 0.2 Sekunden, vorverarbeitet 2026-10-24]