Files
sync/src-tauri/src/handlers/config/mod.rs

186 lines
7.2 KiB
Rust

use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use crate::structs::{FanslyFollowersResponse, Subscription};
const CURRENT_VERSION: i32 = 2; // Set the current version of the config
#[derive(Debug, Serialize, Deserialize)]
pub struct SyncData {
pub followers: Vec<FanslyFollowersResponse>,
pub subscribers: Vec<Subscription>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub version: i32, // Add a version field to the config (1, 2, 3, etc.)
pub is_first_run: bool,
pub fansly_token: String,
pub auto_sync_enabled: bool,
pub sync_token: String,
pub sync_interval: u64,
pub last_sync: u64,
pub last_sync_data: SyncData,
}
impl Default for Config {
fn default() -> Self {
Config {
version: CURRENT_VERSION, // Version is set to CURRENT_VERSION by default
is_first_run: true, // First run is set to true by default
fansly_token: String::new(), // Fansly token is stored as a string
sync_interval: 1, // Every hour - sync interval is interpreted as hours
last_sync: 0, // Last sync time is stored as a UNIX timestamp
auto_sync_enabled: false, // Auto sync is disabled by default
sync_token: String::new(), // Sync token is stored as a string
last_sync_data: SyncData {
followers: Vec::new(),
subscribers: Vec::new(),
}, // Last sync data is stored as a list of followers and subscribers
}
}
}
impl Config {
pub fn load_or_create(path: &Path) -> io::Result<Self> {
if path.exists() {
let config_result: Result<Self, _> =
serde_json::from_str(&std::fs::read_to_string(path)?);
let config = match config_result {
Ok(config) => config,
Err(_) => {
// Load raw JSON and attempt to parse it as a JSON object
let config_raw = std::fs::read_to_string(path)?;
let config_json: serde_json::Value = serde_json::from_str(&config_raw)?;
log::info!("[config::migrate] Migrating config file to latest version...");
log::debug!(
"[config::migrate] [DEBUG] config is_object: {}",
config_json.is_object()
);
// Check if the JSON object is valid, if not, return an error
if !config_json.is_object() {
log::error!(
"[config::migrate] [ERROR] Found invalid JSON object in config file"
);
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Tried to migrate a config file, but found an invalid JSON object",
));
}
// Get the version field from the JSON object
let version = config_json["version"].as_i64().unwrap_or(0) as i32;
// Check if the version field is a valid integer, if not, return an error
if version == 0 {
log::error!(
"[config::migrate] [ERROR] Found invalid version field in config JSON"
);
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Tried to migrate a config file, but found an invalid version field",
));
}
log::info!(
"[config::migrate] Found version field in config JSON: {}",
version
);
// Now create a new Config object and set the version field to the value we found
let mut config = Config::default();
config.version = version;
// Retain important fields from the JSON object
config.is_first_run = config_json["is_first_run"].as_bool().unwrap_or(true);
config.fansly_token = config_json["fansly_token"]
.as_str()
.unwrap_or("")
.to_string();
config.sync_token =
config_json["sync_token"].as_str().unwrap_or("").to_string();
config.sync_interval =
config_json["sync_interval"].as_i64().unwrap_or(1) as u64;
// Run migrations on the config object and save it
config = config.migrate()?;
config.save(path)?;
log::info!(
"[config::migrate] Successfully migrated config file to latest version"
);
// Recursively call load_or_create to load the migrated config
return Config::load_or_create(path);
}
};
if config.version != CURRENT_VERSION {
// Should have been migrated by now, error out because it wasn't
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Config version mismatch: expected {}, got {}. Please try removing the config file and restarting the application.",
CURRENT_VERSION, config.version
),
));
}
Ok(config)
} else {
let saved_config = Config::default().save(path);
saved_config
.and_then(|_| Config::load_or_create(path))
.or_else(|e| Err(e))
}
}
fn migrate(mut self) -> io::Result<Self> {
while self.version < CURRENT_VERSION {
self = match self.version {
1 => {
// Migrate from version 1 to version 2
self.version = 2;
self.auto_sync_enabled = false;
self.sync_token = String::new();
self.sync_interval = 1;
self
}
_ => {
// If we don't have a migration path, return an error
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("No migration path for version {}", self.version),
));
}
};
}
Ok(self)
}
pub fn save(&self, path: &Path) -> io::Result<()> {
let mut file = File::create(path)?;
file.write_all(serde_json::to_string_pretty(self).unwrap().as_bytes())?;
// Return the saved config
Ok(())
}
}
pub fn get_config_path() -> io::Result<PathBuf> {
let mut config_dir = dirs::config_dir().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"Could not determine user's config directory",
)
})?;
config_dir.push("FanslySync");
fs::create_dir_all(&config_dir)?;
config_dir.push("config.json");
Ok(config_dir)
}