How the Save Module Structures Persistent State in UAD-ng: Configuration and Backup Architecture
UAD-ng employs a dual-layer persistence architecture where global settings serialize to TOML via config.rs while package snapshots serialize to timestamped JSON via 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 and 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
Configstruct - 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, which handles serialization to and from 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, 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 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).
#[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 (see lines 46‑63 in save.rs).
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).
Cross-Module Integration Flow
The save modules interact with the broader application through a specific lifecycle:
- Startup:
Config::load_configuration_file()populates the in-memoryConfigsingleton fromconfig.toml - Backup Trigger: The UI calls
backup_phone, which writes JSON snapshots under the folder defined inGeneralSettings.backup_folder - Backup Browsing:
list_available_backupsandlist_available_backup_userprovide the UI withDisplayablePathentries (defined incrates/uad-core/src/utils.rs) for presentation - Restore Execution:
restore_backupreturns aRestoreResultcontaining 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) and JSON-based backups (save.rs) - Configuration manages global settings via
Config::save_device_settingsandConfig::load_configuration_file - Backups use timestamped JSON files in
<cache>/backups/<device_id>/with structuresPhoneBackupandUserBackup - The
backup_phoneandrestore_backupfunctions handle the full lifecycle of package state snapshots BackupSettingsis intentionally excluded from TOML serialization via#[serde(skip)]since backup metadata resides in separate JSON filesCorePackageandUsertypes (fromcrates/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 in the user's config directory, accessed through Config::load_configuration_file() in 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →