use anyhow::Result;
use crate::config::{Config, EncryptionConfig};
use crate::crypto::Cipher;
use crate::scanner::ScanFilter;
use crate::store::create_store;
use crate::{extras, manifest, memories, repo_meta, sync, synclog};
use super::init::ensure_repo_readme;
pub struct PushOutput {
pub sessions: u32,
pub skipped: u32,
pub extras: u32,
pub memories: u32,
}
pub struct PullOutput {
pub pulled: u32,
pub merged: u32,
pub skipped: u32,
pub extras: u32,
pub memories: u32,
}
pub fn do_push(do_sync: bool) -> Result<PushOutput> {
let config = Config::load()?;
let cipher = Cipher::from_config(&config.encryption)?;
let store = create_store(&config)?;
let (pushed, skipped, extras, mem) = {
let _lock = store.try_lock()?;
if let Some(path) = config.storage_path() {
repo_meta::RepoMeta::from_config(&config).save(path)?;
}
ensure_repo_readme(&config)?;
auto_migrate_memories(&config, &cipher);
let filter = ScanFilter::default();
let result = sync::push(&config, &cipher, &filter, store.as_ref())?;
let extras_result = extras::push_extras(&config, &cipher)?;
let mem_result = memories::push_memories(&config, &cipher)?;
let mut log = synclog::SyncLogEntry::new("push");
log.sessions_pushed = result.pushed;
log.sessions_skipped = result.skipped;
log.extras = extras_result.pushed + mem_result.pushed;
if let Some(path) = config.storage_path() {
synclog::append(path, &log).ok();
}
let total_extra = extras_result.pushed + mem_result.pushed;
if do_sync && (result.pushed > 0 || total_extra > 0) {
let machine = manifest::get_machine_id();
let mut parts = Vec::new();
if result.pushed > 0 {
parts.push(format!("{} sessions", result.pushed));
}
if total_extra > 0 {
parts.push(format!("{total_extra} extras"));
}
store.sync_up(&format!("clync push ({}) from {machine}", parts.join(", ")))?;
}
(
result.pushed,
result.skipped,
extras_result.pushed,
mem_result.pushed,
)
};
Ok(PushOutput {
sessions: pushed,
skipped,
extras,
memories: mem,
})
}
pub fn do_pull(do_sync: bool) -> Result<PullOutput> {
let config = Config::load()?;
let store = create_store(&config)?;
if do_sync {
store.sync_down()?;
}
let (pulled, merged, skipped, extras, mem) = {
let _lock = store.try_lock()?;
let cipher = Cipher::from_config(&config.encryption)?;
auto_migrate_memories(&config, &cipher);
let filter = ScanFilter::default();
let result = sync::pull(&config, &cipher, &filter, store.as_ref())?;
let extras_result = extras::pull_extras(&config, &cipher)?;
let mem_result = memories::pull_memories(&config, &cipher)?;
let mut log = synclog::SyncLogEntry::new("pull");
log.sessions_pulled = result.pulled;
log.sessions_merged = result.merged;
log.sessions_skipped = result.skipped;
log.extras = extras_result.pulled + mem_result.pulled;
if let Some(path) = config.storage_path() {
synclog::append(path, &log).ok();
}
if do_sync {
let machine = manifest::get_machine_id();
store.sync_up(&format!("clync pull from {machine}")).ok();
}
(
result.pulled,
result.merged,
result.skipped,
extras_result.pulled,
mem_result.pulled,
)
};
Ok(PullOutput {
pulled,
merged,
skipped,
extras,
memories: mem,
})
}
pub fn cmd_push(no_sync: bool, filter: ScanFilter) -> Result<()> {
let config = Config::load()?;
let cipher = Cipher::from_config(&config.encryption)?;
let store = create_store(&config)?;
let do_sync = config.sync.storage.auto_push() && !no_sync;
{
let _lock = store.lock()?;
if let Some(path) = config.storage_path() {
repo_meta::RepoMeta::from_config(&config).save(path)?;
}
ensure_repo_readme(&config)?;
auto_migrate_memories(&config, &cipher);
let result = sync::push(&config, &cipher, &filter, store.as_ref())?;
let verb = if matches!(config.encryption, EncryptionConfig::None) {
"synced"
} else {
"encrypted"
};
println!(
"push: {} sessions {verb}, {} unchanged",
result.pushed, result.skipped
);
let extras_result = extras::push_extras(&config, &cipher)?;
if extras_result.pushed > 0 {
println!("push: {} extra files synced", extras_result.pushed);
}
let mem_result = memories::push_memories(&config, &cipher)?;
if mem_result.pushed > 0 {
println!("push: {} memory files synced", mem_result.pushed);
}
let mut log = synclog::SyncLogEntry::new("push");
log.sessions_pushed = result.pushed;
log.sessions_skipped = result.skipped;
log.extras = extras_result.pushed + mem_result.pushed;
if let Some(path) = config.storage_path() {
synclog::append(path, &log).ok();
}
let total_extra = extras_result.pushed + mem_result.pushed;
if do_sync && (result.pushed > 0 || total_extra > 0) {
let machine = manifest::get_machine_id();
let mut parts = Vec::new();
if result.pushed > 0 {
parts.push(format!("{} sessions", result.pushed));
}
if total_extra > 0 {
parts.push(format!("{total_extra} extras"));
}
store.sync_up(&format!("clync push ({}) from {machine}", parts.join(", ")))?;
}
}
Ok(())
}
pub fn cmd_pull(no_sync: bool, filter: ScanFilter) -> Result<()> {
let config = Config::load()?;
let store = create_store(&config)?;
let do_sync = config.sync.storage.auto_push() && !no_sync;
if do_sync {
store.sync_down()?;
}
{
let _lock = store.lock()?;
let cipher = Cipher::from_config(&config.encryption)?;
auto_migrate_memories(&config, &cipher);
let result = sync::pull(&config, &cipher, &filter, store.as_ref())?;
println!(
"pull: {} new, {} merged, {} unchanged",
result.pulled, result.merged, result.skipped
);
let extras_result = extras::pull_extras(&config, &cipher)?;
if extras_result.pulled > 0 {
println!("pull: {} extra files restored", extras_result.pulled);
}
let mem_result = memories::pull_memories(&config, &cipher)?;
if mem_result.pulled > 0 {
println!("pull: {} memory files restored", mem_result.pulled);
}
let mut log = synclog::SyncLogEntry::new("pull");
log.sessions_pulled = result.pulled;
log.sessions_merged = result.merged;
log.sessions_skipped = result.skipped;
log.extras = extras_result.pulled + mem_result.pulled;
if let Some(path) = config.storage_path() {
synclog::append(path, &log).ok();
}
if do_sync {
let machine = manifest::get_machine_id();
store.sync_up(&format!("clync pull from {machine}")).ok();
}
}
Ok(())
}
pub fn cmd_status(filter: ScanFilter) -> Result<()> {
let config = Config::load()?;
let cipher = Cipher::from_config(&config.encryption)?;
let store = create_store(&config)?;
let result = sync::status(&config, &cipher, &filter, store.as_ref())?;
let total_diff = result.local_only.len() + result.remote_only.len() + result.diverged.len();
if total_diff == 0 {
println!("all {} sessions in sync", result.in_sync);
return Ok(());
}
if !result.local_only.is_empty() {
println!("local only ({}, push to sync):", result.local_only.len());
for s in &result.local_only {
println!(
" + {} [{}] {}B",
super::short_uuid(&s.uuid),
s.project,
s.size
);
}
}
if !result.remote_only.is_empty() {
println!("remote only ({}, pull to sync):", result.remote_only.len());
for s in &result.remote_only {
println!(
" - {} [{}] {}B",
super::short_uuid(&s.uuid),
s.project,
s.size
);
}
}
if !result.diverged.is_empty() {
println!(
"diverged ({}, pull will smart-merge):",
result.diverged.len()
);
for s in &result.diverged {
println!(
" ~ {} [{}] {}B",
super::short_uuid(&s.uuid),
s.project,
s.size
);
}
}
if result.in_sync > 0 {
println!("in sync: {}", result.in_sync);
}
Ok(())
}
pub(crate) fn auto_migrate_memories(config: &Config, cipher: &Cipher) {
let store_path = match config.storage_path() {
Some(p) => p,
None => return,
};
let old_dir = store_path.join("extras").join("memories");
if !old_dir.exists() {
return;
}
match memories::migrate_from_extras(config, cipher) {
Ok((projects, files)) => {
if projects > 0 {
eprintln!(
"migrated {files} memory files across {projects} projects \
from extras/memories/ to memories/"
);
}
}
Err(e) => eprintln!("warning: memory migration failed: {e}"),
}
}