use anyhow::Result;
use colored::*;
#[derive(Debug, Clone)]
pub struct ConfigFile {
pub name: String,
pub content: Vec<u8>,
}
pub fn encrypt_files(config_files: &[ConfigFile]) -> Result<Vec<ConfigFile>> {
use super::encryption::{derive_key_from_password, encrypt_data};
let password = std::env::var("LC_SYNC_PASSWORD").unwrap_or_else(|_| {
rpassword::prompt_password("Enter sync encryption password: ")
.expect("Failed to read password")
});
let key = derive_key_from_password(&password)?;
let mut encrypted_files = Vec::new();
for file in config_files {
let encrypted_content = encrypt_data(&file.content, &key)?;
encrypted_files.push(ConfigFile {
name: file.name.clone(),
content: encrypted_content,
});
}
Ok(encrypted_files)
}
pub fn decrypt_files(encrypted_files: &[ConfigFile]) -> Result<Vec<ConfigFile>> {
use super::encryption::{decrypt_data, derive_key_from_password};
let password = std::env::var("LC_SYNC_PASSWORD").unwrap_or_else(|_| {
rpassword::prompt_password("Enter sync decryption password: ")
.expect("Failed to read password")
});
let key = derive_key_from_password(&password)?;
let mut decrypted_files = Vec::new();
for file in encrypted_files {
let decrypted_content = decrypt_data(&file.content, &key)?;
decrypted_files.push(ConfigFile {
name: file.name.clone(),
content: decrypted_content,
});
}
Ok(decrypted_files)
}
pub async fn handle_sync_providers() -> Result<()> {
println!("{}", "Available sync providers:".bold());
println!(" • {} - Amazon S3 and S3-compatible storage", "s3".cyan());
println!(" • {} - Amazon S3", "amazon-s3".cyan());
println!(" • {} - AWS S3", "aws-s3".cyan());
println!(" • {} - Cloudflare R2", "cloudflare".cyan());
println!(" • {} - Backblaze B2", "backblaze".cyan());
println!(
"\n{}",
"Configure a provider with: lc sync configure <provider>".italic()
);
Ok(())
}
fn validate_sync_provider(provider: &str) -> Result<()> {
match provider.to_lowercase().as_str() {
"s3" | "amazon-s3" | "aws-s3" | "cloudflare" | "backblaze" => Ok(()),
_ => {
anyhow::bail!("Unsupported sync provider: {}", provider);
}
}
}
pub async fn handle_sync_to(provider: &str, encrypted: bool, yes: bool) -> Result<()> {
use std::fs;
use std::io::{self, Write};
println!(
"📤 {} configuration to {}...",
"Syncing".cyan(),
provider.bold()
);
validate_sync_provider(provider)?;
let config_dir = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
.join("lc");
if !config_dir.exists() {
anyhow::bail!("Configuration directory does not exist: {:?}", config_dir);
}
let mut config_files = Vec::new();
for entry in fs::read_dir(&config_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let file_name = path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
let extension = path.extension().and_then(|e| e.to_str());
let should_include = extension.map(|e| e == "toml" || e == "db").unwrap_or(false);
if should_include {
let content = fs::read(&path)?;
config_files.push(ConfigFile {
name: file_name.to_string(),
content,
});
}
}
}
let providers_dir = config_dir.join("providers");
if providers_dir.exists() {
for entry in fs::read_dir(&providers_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("toml") {
let content = fs::read(&path)?;
let name = format!("providers/{}", path.file_name().unwrap().to_string_lossy());
config_files.push(ConfigFile { name, content });
}
}
}
let embeddings_dir = config_dir.join("embeddings");
if embeddings_dir.exists() {
for entry in fs::read_dir(&embeddings_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("db") {
let content = fs::read(&path)?;
let name = format!("embeddings/{}", path.file_name().unwrap().to_string_lossy());
config_files.push(ConfigFile { name, content });
}
}
}
if config_files.is_empty() {
println!("{} No configuration files found to sync", "ℹ️".blue());
return Ok(());
}
println!("Found {} configuration files", config_files.len());
if !yes {
println!("\nFiles to sync:");
for file in &config_files {
println!(" • {}", file.name);
}
print!("\nContinue with sync? [y/N]: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if !input.trim().eq_ignore_ascii_case("y") {
println!("Sync cancelled.");
return Ok(());
}
}
let _files_to_upload = if encrypted {
println!("🔐 Encrypting configuration files...");
encrypt_files(&config_files)?
} else {
config_files
};
#[cfg(feature = "s3-sync")]
{
use super::s3::upload_to_s3_provider;
upload_to_s3_provider(&_files_to_upload, provider, encrypted).await?;
println!("{} Configuration synced successfully!", "✅".green());
return Ok(());
}
#[cfg(not(feature = "s3-sync"))]
{
anyhow::bail!("S3 sync feature not enabled. Build with --features s3-sync");
}
}
pub async fn handle_sync_from(provider: &str, _encrypted: bool, yes: bool) -> Result<()> {
use std::fs;
use std::io::{self, Write};
println!(
"📥 {} configuration from {}...",
"Syncing".cyan(),
provider.bold()
);
validate_sync_provider(provider)?;
let config_dir = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
.join("lc");
if !config_dir.exists() {
fs::create_dir_all(&config_dir)?;
}
if !yes {
println!(
"\n⚠️ {} This will overwrite local configuration files!",
"Warning:".yellow()
);
print!("Continue with sync? [y/N]: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if !input.trim().eq_ignore_ascii_case("y") {
println!("Sync cancelled.");
return Ok(());
}
}
#[cfg(feature = "s3-sync")]
{
use super::s3::download_from_s3_provider;
let _downloaded_files: Vec<ConfigFile> = download_from_s3_provider(provider, _encrypted).await?;
println!("Downloaded {} configuration files", _downloaded_files.len());
let files_to_save = if _encrypted {
println!("🔓 Decrypting configuration files...");
decrypt_files(&_downloaded_files)?
} else {
_downloaded_files
};
for file in files_to_save {
let file_path = config_dir.join(&file.name);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&file_path, &file.content)?;
println!(" ✓ Saved {}", file.name);
}
println!("{} Configuration synced successfully!", "✅".green());
return Ok(());
}
#[cfg(not(feature = "s3-sync"))]
{
anyhow::bail!("S3 sync feature not enabled. Build with --features s3-sync");
}
}