fix(shares): let add and remove reach a running daemon

shares add wrote the config file and stopped there, so with a daemon running
the folder stayed invisible to the network until the daemon restarted — the
command reported success and nothing had happened. Locally that is the right
meaning, because there is no session yet; a daemon is already the next run.

Both commands now push the new list over shares.set. The e2e test was checked
against a disabled push to confirm it detects the bug.
This commit is contained in:
Michel de Graaf
2026-07-27 12:46:25 +02:00
parent f985850c06
commit d2599c82d3
5 changed files with 138 additions and 7 deletions
+48 -4
View File
@@ -5,11 +5,13 @@
//! what the next run — interactive or scripted — starts from.
use super::{Ctx, Session};
use crate::api::SessionApi;
use crate::directories;
use crate::output::{
CliError, CliResult, ConfigRecord, Out, ShareRecord, ShareStatusRecord,
};
use crate::persist::config::FileConfig;
use crate::remote::{Endpoint, RemoteSession};
use std::path::PathBuf;
/// Where a `shares`/`config` change is written, and what it starts from.
@@ -46,7 +48,42 @@ pub fn shares_list(out: &Out, store: &Store, resolved: &[String]) -> CliResult {
Ok(())
}
pub fn shares_add(out: &Out, store: &Store, directory: &str) -> CliResult {
/// Where a running daemon can be reached, so a share change takes effect now
/// rather than at its next restart.
pub struct LiveDaemon {
pub endpoint: Option<Endpoint>,
pub token: Option<String>,
}
/// Tell a running daemon what this machine now shares.
///
/// Locally, `shares add` means "shared from the next run on", because there is
/// no session yet. A daemon *is* that next run and it is already going, so
/// leaving it on its startup list would make the command look like it had
/// done nothing — the folder configured, and still invisible to the network.
fn push_to_daemon(out: &Out, daemon: &LiveDaemon, directories: Vec<String>) {
let Some(endpoint) = daemon.endpoint.as_ref() else {
return;
};
let token = daemon.token.clone().or_else(crate::daemon::stored_token);
let outcome = RemoteSession::connect(endpoint, token)
.and_then(|session| session.set_shared_directories(directories));
match outcome {
Ok(()) => out.status("the running daemon is re-indexing"),
// Not fatal: the config file is written either way, so the change
// still takes effect when the daemon next starts.
Err(e) => out.warn(&format!(
"the daemon kept its own shares ({e}); restart it to pick this up"
)),
}
}
pub fn shares_add(
out: &Out,
store: &Store,
directory: &str,
daemon: &LiveDaemon,
) -> CliResult {
// Refuse a folder that is not there: sharing it would silently index
// nothing, and the mistake would only surface as "no one can find my
// files" much later.
@@ -63,14 +100,20 @@ pub fn shares_add(out: &Out, store: &Store, directory: &str) -> CliResult {
return Ok(());
}
dirs.push(directory.to_string());
updated.shared_dirs = Some(dirs);
updated.shared_dirs = Some(dirs.clone());
updated.shared_dir = None;
store.save(&updated)?;
out.status(&format!("sharing {directory}"));
push_to_daemon(out, daemon, dirs);
Ok(())
}
pub fn shares_remove(out: &Out, store: &Store, directory: &str) -> CliResult {
pub fn shares_remove(
out: &Out,
store: &Store,
directory: &str,
daemon: &LiveDaemon,
) -> CliResult {
let mut dirs = share_list(&store.config);
let before = dirs.len();
dirs.retain(|existing| existing != directory);
@@ -81,10 +124,11 @@ pub fn shares_remove(out: &Out, store: &Store, directory: &str) -> CliResult {
}
let mut updated = store.config.clone();
updated.shared_dirs = Some(dirs);
updated.shared_dirs = Some(dirs.clone());
updated.shared_dir = None;
store.save(&updated)?;
out.status(&format!("no longer sharing {directory}"));
push_to_daemon(out, daemon, dirs);
Ok(())
}