# How the Save Module Structures Persistent State in UAD-ng: Configuration and Backup Architecture

> Discover how the UAD-ng save module structures persistent state using TOML config and timestamped JSON backups. Learn about its robust configuration and backup architecture.

- Repository: [Universal-Debloater-Alliance/universal-android-debloater-next-generation](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation)
- Tags: architecture
- Published: 2026-06-18

---

**UAD-ng employs a dual-layer persistence architecture where global settings serialize to TOML via [`config.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/config.rs) while package snapshots serialize to timestamped JSON via [`save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/save.rs), ensuring both application preferences and device-specific debloating states survive between sessions.**

The Universal Android Debloater Next Generation (UAD-ng) implements a sophisticated save module to maintain persistent state across the `uad-core` crate. This system splits responsibilities between configuration management and backup operations, using distinct serialization formats for each domain. The following analysis examines the source code in [`crates/uad-core/src/config.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/config.rs) and [`crates/uad-core/src/save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/save.rs) to reveal how the application preserves both user preferences and device package states.

## The Dual-Layer Persistence Model

UAD-ng organizes persistent state into two complementary layers that handle different aspects of application data:

- **Configuration Layer**: Stores global settings and per-device preferences as TOML in the user's config directory, managed by the `Config` struct
- **Backup Layer**: Stores snapshots of package states (disabled/uninstalled) as JSON files in `<cache>/backups/<device_id>/`, managed by backup-specific functions

This separation allows the application to manage user preferences independently from potentially large backup datasets while ensuring thread-safe access to configuration files.

## Configuration Persistence with TOML

The configuration system centers on the `Config` struct in [`crates/uad-core/src/config.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/config.rs), which handles serialization to and from [`config.toml`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/config.toml).

### The Config Struct Hierarchy

The `Config` struct contains a `GeneralSettings` block for global preferences (theme, expert mode, backup folder location) and a vector of `DeviceSettings` entries for individualized device configurations. The `DeviceSettings` structure includes a `BackupSettings` field that is explicitly excluded from TOML serialization via `#[serde(skip)]` because backup metadata lives separately in JSON files rather than the configuration file.

### Loading and Saving Operations

**Saving** occurs through `Config::save_device_settings`, which updates existing device entries or appends new ones before writing the entire struct to disk using `toml::to_string`. This method guarantees that the entire configuration state is atomic and consistent.

**Loading** happens via `Config::load_configuration_file`, which reads [`config.toml`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/config.toml), falls back to defaults on error, and rewrites the file to guarantee validity. This lazy-loading approach ensures the application always starts with a valid configuration state even if the file is corrupted or missing.

## Backup Persistence with JSON

While configuration handles preferences, the backup system in [`crates/uad-core/src/save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/save.rs) manages the actual package state snapshots using JSON serialization.

### PhoneBackup and UserBackup Data Structures

The module defines `PhoneBackup` as the top-level container holding a `device_id` and a list of `UserBackup` entries. Each `UserBackup` contains a user ID and a `Vec<CorePackage>` representing the packages backed up for that specific user. These structures derive `Serialize` and `Deserialize` for JSON handling (see lines 12‑22 in [`save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/save.rs)).

```rust
#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct PhoneBackup { 
    // device_id and Vec<UserBackup>
}

#[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct UserBackup { 
    // user ID and Vec<CorePackage>
}

```

### Creating Timestamped Backups

The `backup_phone` function orchestrates snapshot creation. It receives the current users, device ID, and per-user package lists, then constructs a `PhoneBackup` instance. The function serializes this data using `serde_json::to_string_pretty`, determines the backup directory from `Config::load_configuration_file().general.backup_folder`, and writes the file with a timestamped name following the pattern [`YYYY-MM-DD_HH-MM-SS.json`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/YYYY-MM-DD_HH-MM-SS.json) (see lines 46‑63 in [`save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/save.rs)).

```rust
let backup_dir: PathBuf = Config::load_configuration_file().general.backup_folder;
let backup_path = &*backup_dir.join(device_id);
let backup_filename = format!("{}.json", chrono::Local::now().format("%Y-%m-%d_%H-%M-%S"));
fs::write(backup_path.join(backup_filename), json)

```

### Restoring Package State

Restoration begins with `restore_backup`, which reads the selected JSON file and deserializes it into a `PhoneBackup`. The function matches each backed-up user and package against the current device state, constructing `BackupPackage` objects that contain the commands necessary to re-apply saved states. Skipped packages (those no longer present on the device) are counted and reported in the `RestoreResult` (see lines 31‑68 and 111‑185 in [`save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/save.rs)).

## Cross-Module Integration Flow

The save modules interact with the broader application through a specific lifecycle:

1. **Startup**: `Config::load_configuration_file()` populates the in-memory `Config` singleton from [`config.toml`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/config.toml)
2. **Backup Trigger**: The UI calls `backup_phone`, which writes JSON snapshots under the folder defined in `GeneralSettings.backup_folder`
3. **Backup Browsing**: `list_available_backups` and `list_available_backup_user` provide the UI with `DisplayablePath` entries (defined in [`crates/uad-core/src/utils.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/utils.rs)) for presentation
4. **Restore Execution**: `restore_backup` returns a `RestoreResult` containing the command list that the core executor transmits to the Android device via ADB

## Summary

- UAD-ng splits persistent state into **TOML-based configuration** ([`config.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/config.rs)) and **JSON-based backups** ([`save.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/save.rs))
- Configuration manages global settings via `Config::save_device_settings` and `Config::load_configuration_file`
- Backups use timestamped JSON files in `<cache>/backups/<device_id>/` with structures `PhoneBackup` and `UserBackup`
- The `backup_phone` and `restore_backup` functions handle the full lifecycle of package state snapshots
- `BackupSettings` is intentionally excluded from TOML serialization via `#[serde(skip)]` since backup metadata resides in separate JSON files
- `CorePackage` and `User` types (from [`crates/uad-core/src/sync.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/sync.rs)) provide the data models used throughout the save module

## Frequently Asked Questions

### Where does UAD-ng store its configuration files?

UAD-ng writes global configuration to [`config.toml`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/config.toml) in the user's config directory, accessed through `Config::load_configuration_file()` in [`crates/uad-core/src/config.rs`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/crates/uad-core/src/config.rs). This TOML file contains `GeneralSettings` and `DeviceSettings`, but excludes `BackupSettings` which is managed separately in JSON backup files.

### What is the difference between PhoneBackup and UserBackup?

`PhoneBackup` acts as the top-level container for an entire device snapshot, holding the `device_id` and a vector of `UserBackup` entries. Each `UserBackup` represents a specific Android user profile on that device, containing the user ID and the list of `CorePackage` objects backed up for that particular user, allowing per-user restoration capabilities.

### How does UAD-ng handle backup file naming?

The `backup_phone` function generates timestamped filenames using the format [`YYYY-MM-DD_HH-MM-SS.json`](https://github.com/Universal-Debloater-Alliance/universal-android-debloater-next-generation/blob/main/YYYY-MM-DD_HH-MM-SS.json) via `chrono::Local::now().format("%Y-%m-%d_%H-%M-%S")`. These files are stored in the backup directory defined by `GeneralSettings.backup_folder`, organized under subdirectories for each device ID, ensuring chronological sorting and unique filenames.

### Can packages be restored if they no longer exist on the device?

During `restore_backup`, the system matches backed-up packages against the current device state. Packages that no longer exist on the device are skipped and counted in the `RestoreResult`, allowing the restoration process to continue without failing while reporting which packages could not be reinstalled.