mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-10-16 22:19:26 -04:00
feat: Implement CLI daemon management and library commands
This commit is contained in:
1 parent
ce7ada15f0
commit
50b15bbc1c
19 files changed
+206
-73
No files matched your search
@@ -1,4 +1,5 @@
|
||||
use sd_core::client::CoreClient;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||
pub enum OutputFormat {
|
||||
@@ -9,10 +10,12 @@ pub enum OutputFormat {
|
||||
pub struct Context {
|
||||
pub core: CoreClient,
|
||||
pub format: OutputFormat,
|
||||
pub data_dir: PathBuf,
|
||||
pub socket_path: PathBuf,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(core: CoreClient, format: OutputFormat) -> Self {
|
||||
Self { core, format }
|
||||
pub fn new(core: CoreClient, format: OutputFormat, data_dir: PathBuf, socket_path: PathBuf) -> Self {
|
||||
Self { core, format, data_dir, socket_path }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
|
||||
use crate::context::{Context, OutputFormat};
|
||||
use crate::context::Context;
|
||||
|
||||
#[derive(clap::Parser, Debug, Clone)]
|
||||
pub struct FileCopyArgs {
|
||||
@@ -31,11 +31,13 @@ pub struct FileCopyArgs {
|
||||
|
||||
impl FileCopyArgs {
|
||||
pub fn to_input(&self) -> sd_core::ops::files::copy::input::FileCopyInput {
|
||||
use sd_core::domain::addressing::{SdPath, SdPathBatch};
|
||||
use sd_core::ops::files::copy::input::{CopyMethod, FileCopyInput};
|
||||
let sources = self.sources.iter().map(|p| SdPath::local(p.clone())).collect::<Vec<_>>();
|
||||
let destination = SdPath::local(self.destination.clone());
|
||||
FileCopyInput {
|
||||
library_id: None,
|
||||
sources: self.sources.clone(),
|
||||
destination: self.destination.clone(),
|
||||
sources: SdPathBatch { paths: sources },
|
||||
destination,
|
||||
overwrite: self.overwrite,
|
||||
verify_checksum: self.verify_checksum,
|
||||
preserve_timestamps: self.preserve_timestamps,
|
||||
|
||||
@@ -2,16 +2,38 @@ use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
|
||||
use crate::context::{Context, OutputFormat};
|
||||
use crate::util::output::{print_human_line, print_json};
|
||||
use crate::util::output::print_json;
|
||||
use sd_core::ops::libraries::create;
|
||||
use sd_core::ops::libraries::session::set_current;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum LibraryCmd {
|
||||
/// Create a new library
|
||||
Create { name: String },
|
||||
/// Switch to a different library
|
||||
Switch { id: uuid::Uuid },
|
||||
/// List libraries
|
||||
List,
|
||||
}
|
||||
|
||||
pub async fn run(ctx: &Context, cmd: LibraryCmd) -> Result<()> {
|
||||
match cmd {
|
||||
LibraryCmd::Create { name } => {
|
||||
let input = create::LibraryCreateInput::new(name);
|
||||
let bytes = ctx.core.action(&input).await?;
|
||||
let out: create::LibraryCreateOutput = bincode::deserialize(&bytes)?;
|
||||
println!("Created library {} with ID {} at {}", out.name, out.library_id, out.path.display());
|
||||
}
|
||||
LibraryCmd::Switch { id } => {
|
||||
let input = set_current::SetCurrentLibraryInput { library_id: id };
|
||||
let bytes = ctx.core.action(&input).await?;
|
||||
let out: set_current::SetCurrentLibraryOutput = bincode::deserialize(&bytes)?;
|
||||
if out.success {
|
||||
println!("Switched to library {}", id);
|
||||
} else {
|
||||
println!("Failed to switch to library {}", id);
|
||||
}
|
||||
}
|
||||
LibraryCmd::List => {
|
||||
let libs: Vec<sd_core::ops::libraries::list::output::LibraryInfo> = ctx
|
||||
.core
|
||||
@@ -20,10 +42,10 @@ pub async fn run(ctx: &Context, cmd: LibraryCmd) -> Result<()> {
|
||||
match ctx.format {
|
||||
OutputFormat::Human => {
|
||||
if libs.is_empty() {
|
||||
print_human_line("No libraries found");
|
||||
println!("No libraries found");
|
||||
}
|
||||
for l in libs {
|
||||
print_human_line(&format!("- {} {}", l.id, l.path.display()));
|
||||
println!("- {} {}", l.id, l.path.display());
|
||||
}
|
||||
}
|
||||
OutputFormat::Json => print_json(&libs),
|
||||
|
||||
@@ -16,8 +16,15 @@ pub enum LocationCmd {
|
||||
pub async fn run(ctx: &Context, cmd: LocationCmd) -> Result<()> {
|
||||
match cmd {
|
||||
LocationCmd::Add { path, name, mode } => {
|
||||
let out: sd_core::ops::locations::add::output::LocationAddOutput = ctx.core.action(&sd_core::ops::locations::add::action::LocationAddInput { path, name, mode: sd_core::ops::indexing::job::IndexMode::from(mode) }).await?;
|
||||
println!("Added location {} -> {}", out.id, out.path.display());
|
||||
let libs: Vec<sd_core::ops::libraries::list::output::LibraryInfo> = ctx
|
||||
.core
|
||||
.query(&sd_core::ops::libraries::list::query::ListLibrariesQuery::basic())
|
||||
.await?;
|
||||
let library_id = if libs.len() == 1 { libs[0].id } else { anyhow::bail!("Specify --library to add locations when multiple libraries exist") };
|
||||
|
||||
let result_bytes = ctx.core.action(&sd_core::ops::locations::add::action::LocationAddInput { library_id, path, name, mode: sd_core::ops::indexing::job::IndexMode::from(mode) }).await?;
|
||||
let out: sd_core::ops::locations::add::output::LocationAddOutput = bincode::deserialize(&result_bytes)?;
|
||||
println!("Added location {} -> {}", out.location_id, out.path.display());
|
||||
}
|
||||
LocationCmd::List => {
|
||||
let libs: Vec<sd_core::ops::libraries::list::output::LibraryInfo> = ctx
|
||||
@@ -29,11 +36,19 @@ pub async fn run(ctx: &Context, cmd: LocationCmd) -> Result<()> {
|
||||
for loc in out.locations { println!("- {} {}", loc.id, loc.path.display()); }
|
||||
}
|
||||
LocationCmd::Remove { location_id } => {
|
||||
let _out: sd_core::ops::locations::remove::output::LocationRemoveOutput = ctx.core.action(&sd_core::ops::locations::remove::action::LocationRemoveInput { location_id }).await?;
|
||||
let libs: Vec<sd_core::ops::libraries::list::output::LibraryInfo> = ctx
|
||||
.core
|
||||
.query(&sd_core::ops::libraries::list::query::ListLibrariesQuery::basic())
|
||||
.await?;
|
||||
let library_id = if libs.len() == 1 { libs[0].id } else { anyhow::bail!("Specify --library to remove locations when multiple libraries exist") };
|
||||
|
||||
let result_bytes = ctx.core.action(&sd_core::ops::locations::remove::action::LocationRemoveInput { library_id, location_id }).await?;
|
||||
let _out: sd_core::ops::locations::remove::output::LocationRemoveOutput = bincode::deserialize(&result_bytes)?;
|
||||
println!("Removed location {}", location_id);
|
||||
}
|
||||
LocationCmd::Rescan { location_id, force: _ } => {
|
||||
let _out: sd_core::ops::locations::rescan::output::LocationRescanOutput = ctx.core.action(&sd_core::ops::locations::rescan::action::LocationRescanInput { location_id }).await?;
|
||||
LocationCmd::Rescan { location_id, force } => {
|
||||
let result_bytes = ctx.core.action(&sd_core::ops::locations::rescan::action::LocationRescanInput { location_id, full_rescan: force }).await?;
|
||||
let _out: sd_core::ops::locations::rescan::output::LocationRescanOutput = bincode::deserialize(&result_bytes)?;
|
||||
println!("Rescan requested for {}", location_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,7 @@ pub enum NetworkCmd {
|
||||
Status,
|
||||
/// List devices
|
||||
Devices(NetworkDevicesArgs),
|
||||
/// Start networking
|
||||
Start,
|
||||
/// Stop networking
|
||||
Stop,
|
||||
|
||||
/// Pairing commands
|
||||
#[command(subcommand)]
|
||||
Pair(PairCmd),
|
||||
@@ -86,17 +83,11 @@ pub async fn run(ctx: &Context, cmd: NetworkCmd) -> Result<()> {
|
||||
OutputFormat::Json => print_json(&devices),
|
||||
}
|
||||
}
|
||||
NetworkCmd::Start => {
|
||||
let out: NetworkStartOutput = ctx.core.action(&NetworkStartInput {}).await?;
|
||||
println!("Networking {}", if out.started { "started" } else { "already running" });
|
||||
}
|
||||
NetworkCmd::Stop => {
|
||||
let out: NetworkStopOutput = ctx.core.action(&NetworkStopInput {}).await?;
|
||||
println!("Networking {}", if out.stopped { "stopped" } else { "not running" });
|
||||
}
|
||||
|
||||
NetworkCmd::Pair(pc) => match pc {
|
||||
PairCmd::Generate { auto_accept } => {
|
||||
let out: PairGenerateOutput = ctx.core.action(&PairGenerateInput { auto_accept }).await?;
|
||||
let bytes = ctx.core.action(&PairGenerateInput { auto_accept }).await?;
|
||||
let out: PairGenerateOutput = bincode::deserialize(&bytes)?;
|
||||
match ctx.format {
|
||||
OutputFormat::Human => {
|
||||
println!("Pairing code: {}", out.code);
|
||||
@@ -107,7 +98,8 @@ pub async fn run(ctx: &Context, cmd: NetworkCmd) -> Result<()> {
|
||||
}
|
||||
}
|
||||
PairCmd::Join { code } => {
|
||||
let out: PairJoinOutput = ctx.core.action(&PairJoinInput { code }).await?;
|
||||
let bytes = ctx.core.action(&PairJoinInput { code }).await?;
|
||||
let out: PairJoinOutput = bincode::deserialize(&bytes)?;
|
||||
match ctx.format {
|
||||
OutputFormat::Human => println!("Paired with {} ({})", out.device_name, out.paired_device_id),
|
||||
OutputFormat::Json => print_json(&out),
|
||||
@@ -124,18 +116,21 @@ pub async fn run(ctx: &Context, cmd: NetworkCmd) -> Result<()> {
|
||||
}
|
||||
}
|
||||
PairCmd::Cancel { session_id } => {
|
||||
let out: PairCancelOutput = ctx.core.action(&PairCancelInput { session_id }).await?;
|
||||
let bytes = ctx.core.action(&PairCancelInput { session_id }).await?;
|
||||
let out: PairCancelOutput = bincode::deserialize(&bytes)?;
|
||||
println!("Cancelled: {}", out.cancelled);
|
||||
}
|
||||
},
|
||||
NetworkCmd::Revoke { device_id } => {
|
||||
let out: DeviceRevokeOutput = ctx.core.action(&DeviceRevokeInput { device_id }).await?;
|
||||
let bytes = ctx.core.action(&DeviceRevokeInput { device_id }).await?;
|
||||
let out: DeviceRevokeOutput = bincode::deserialize(&bytes)?;
|
||||
println!("Revoked: {}", out.revoked);
|
||||
}
|
||||
NetworkCmd::Spacedrop(args) => {
|
||||
use sd_core::domain::addressing::SdPath;
|
||||
let paths = args.paths.iter().map(|s| SdPath::from_uri(s).unwrap_or_else(|_| SdPath::local(s))).collect::<Vec<_>>();
|
||||
let out: SpacedropSendOutput = ctx.core.action(&SpacedropSendInput { device_id: args.device_id, paths, sender: args.sender }).await?;
|
||||
let bytes = ctx.core.action(&SpacedropSendInput { device_id: args.device_id, paths, sender: args.sender }).await?;
|
||||
let out: SpacedropSendOutput = bincode::deserialize(&bytes)?;
|
||||
match ctx.format {
|
||||
OutputFormat::Human => {
|
||||
if let Some(j) = out.job_id { println!("Transfer job: {}", j); }
|
||||
|
||||
+33
-5
@@ -39,6 +39,14 @@ struct Cli {
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
/// Start the Spacedrive daemon
|
||||
Start {
|
||||
/// Automatically start networking
|
||||
#[arg(long)]
|
||||
enable_networking: bool,
|
||||
},
|
||||
/// Stop the Spacedrive daemon
|
||||
Stop,
|
||||
/// Core info
|
||||
Status,
|
||||
/// Libraries operations
|
||||
@@ -66,16 +74,36 @@ enum Commands {
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let data_dir = cli.data_dir.unwrap_or(sd_core::config::default_data_dir()?);
|
||||
let socket = if let Some(inst) = cli.instance {
|
||||
let instance = cli.instance;
|
||||
let socket_path = if let Some(inst) = &instance {
|
||||
data_dir.join("daemon").join(format!("daemon-{}.sock", inst))
|
||||
} else {
|
||||
data_dir.join("daemon/daemon.sock")
|
||||
};
|
||||
let core = CoreClient::new(socket);
|
||||
|
||||
let ctx = Context::new(core, cli.format);
|
||||
|
||||
match cli.command {
|
||||
Commands::Start { enable_networking } => {
|
||||
println!("Starting daemon...");
|
||||
sd_core::infra::daemon::bootstrap::start_default_server(socket_path, data_dir, enable_networking).await.map_err(|e| anyhow::anyhow!(e.to_string()))?;
|
||||
}
|
||||
Commands::Stop => {
|
||||
println!("Stopping daemon...");
|
||||
let core = CoreClient::new(socket_path.clone());
|
||||
let _ = core.send_raw_request(&sd_core::infra::daemon::types::DaemonRequest::Shutdown).await.map_err(|e| anyhow::anyhow!(e.to_string()))?;
|
||||
println!("Daemon stopped.");
|
||||
}
|
||||
_ => {
|
||||
run_client_command(cli.command, cli.format, data_dir, socket_path).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_client_command(command: Commands, format: OutputFormat, data_dir: std::path::PathBuf, socket_path: std::path::PathBuf) -> Result<()> {
|
||||
let core = CoreClient::new(socket_path.clone());
|
||||
let ctx = Context::new(core, format, data_dir, socket_path);
|
||||
match command {
|
||||
Commands::Status => {
|
||||
let status: sd_core::ops::core::status::output::CoreStatus = ctx
|
||||
.core
|
||||
@@ -95,7 +123,7 @@ async fn main() -> Result<()> {
|
||||
Commands::Location(cmd) => location::run(&ctx, cmd).await?,
|
||||
Commands::Network(cmd) => network::run(&ctx, cmd).await?,
|
||||
Commands::Job(cmd) => job::run(&ctx, cmd).await?,
|
||||
_ => {} // Start and Stop are handled in main
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod output;
|
||||
@@ -1,10 +1,5 @@
|
||||
use crate::context::OutputFormat;
|
||||
use serde::Serialize;
|
||||
|
||||
pub fn print_human_line(line: &str) { println!("{}", line) }
|
||||
|
||||
pub fn print_json<T: serde::Serialize>(value: &T) {
|
||||
println!("{}", serde_json::to_string_pretty(value).unwrap_or_else(|_| "{}".to_string()))
|
||||
pub fn print_json<T: Serialize>(data: &T) {
|
||||
println!("{}", serde_json::to_string_pretty(data).unwrap());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -67,4 +67,8 @@ impl CoreClient {
|
||||
Err(e) => Err(anyhow::anyhow!(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_raw_request(&self, req: &DaemonRequest) -> Result<DaemonResponse> {
|
||||
self.daemon.send(req).await.map_err(|e| anyhow::anyhow!(e.to_string()))
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -2,10 +2,9 @@
|
||||
|
||||
//! Shared context providing access to core application components.
|
||||
|
||||
use crate::{
|
||||
config::JobLoggingConfig, device::DeviceManager, infra::action::manager::ActionManager,
|
||||
use crate::{config::JobLoggingConfig, device::DeviceManager, infra::action::manager::ActionManager,
|
||||
infra::event::EventBus, crypto::library_key_manager::LibraryKeyManager, library::LibraryManager,
|
||||
service::network::NetworkingService, volume::VolumeManager,
|
||||
service::network::NetworkingService, volume::VolumeManager, infra::daemon::state::SessionStateService,
|
||||
};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
@@ -24,6 +23,7 @@ pub struct CoreContext {
|
||||
// Job logging configuration
|
||||
pub job_logging_config: Option<JobLoggingConfig>,
|
||||
pub job_logs_dir: Option<PathBuf>,
|
||||
pub session_state: Arc<SessionStateService>,
|
||||
}
|
||||
|
||||
impl CoreContext {
|
||||
@@ -34,6 +34,7 @@ impl CoreContext {
|
||||
library_manager: Arc<LibraryManager>,
|
||||
volume_manager: Arc<VolumeManager>,
|
||||
library_key_manager: Arc<LibraryKeyManager>,
|
||||
session_state: Arc<SessionStateService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
events,
|
||||
@@ -45,6 +46,7 @@ impl CoreContext {
|
||||
networking: Arc::new(RwLock::new(None)),
|
||||
job_logging_config: None,
|
||||
job_logs_dir: None,
|
||||
session_state,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ use crate::infra::daemon::{
|
||||
pub async fn start_default_server(
|
||||
socket_path: PathBuf,
|
||||
data_dir: PathBuf,
|
||||
enable_networking: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let instances = Arc::new(CoreInstanceManager::new(data_dir.clone()));
|
||||
let session = Arc::new(SessionStateService::new(data_dir));
|
||||
let session = Arc::new(SessionStateService::new(data_dir.clone()));
|
||||
let instances = Arc::new(CoreInstanceManager::new(data_dir.clone(), enable_networking, session.clone()));
|
||||
let server = RpcServer::new(socket_path, instances, session);
|
||||
server.start().await
|
||||
}
|
||||
@@ -4,16 +4,24 @@ use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::Core;
|
||||
use crate::infra::daemon::state::SessionStateService;
|
||||
|
||||
/// Manages lifecycle of Core instances (by name)
|
||||
pub struct CoreInstanceManager {
|
||||
instances: Arc<RwLock<HashMap<String, Arc<Core>>>>,
|
||||
default_data_dir: PathBuf,
|
||||
enable_networking: bool,
|
||||
session_state: Arc<SessionStateService>,
|
||||
}
|
||||
|
||||
impl CoreInstanceManager {
|
||||
pub fn new(default_data_dir: PathBuf) -> Self {
|
||||
Self { instances: Arc::new(RwLock::new(HashMap::new())), default_data_dir }
|
||||
pub fn new(default_data_dir: PathBuf, enable_networking: bool, session_state: Arc<SessionStateService>) -> Self {
|
||||
Self {
|
||||
instances: Arc::new(RwLock::new(HashMap::new())),
|
||||
default_data_dir,
|
||||
enable_networking,
|
||||
session_state,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get or start the default instance
|
||||
@@ -32,9 +40,18 @@ impl CoreInstanceManager {
|
||||
}
|
||||
|
||||
let data_dir = data_dir.unwrap_or_else(|| self.default_data_dir.clone());
|
||||
let core = Arc::new(Core::new_with_config(data_dir).await?);
|
||||
self.instances.write().await.insert(name, core.clone());
|
||||
Ok(core)
|
||||
let core = Arc::new(Core::new_with_config(data_dir, self.session_state.clone()).await?);
|
||||
if self.enable_networking {
|
||||
let core_with_networking = Core::init_networking_shared(core.clone(), self.session_state.clone()).await?;
|
||||
self.instances
|
||||
.write()
|
||||
.await
|
||||
.insert(name, core_with_networking.clone());
|
||||
Ok(core_with_networking)
|
||||
} else {
|
||||
self.instances.write().await.insert(name, core.clone());
|
||||
Ok(core)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shutdown a named instance
|
||||
|
||||
@@ -47,13 +47,13 @@ impl RpcServer {
|
||||
let req: Result<DaemonRequest, _> = serde_json::from_slice(&buf);
|
||||
let resp = match req {
|
||||
Ok(DaemonRequest::Ping) => DaemonResponse::Pong,
|
||||
Ok(DaemonRequest::Action { method, payload }) => {
|
||||
Ok(DaemonRequest::Action { ref method, ref payload }) => {
|
||||
match instances.get_default().await {
|
||||
Ok(core) => {
|
||||
let session_snapshot = session.get().await;
|
||||
match core
|
||||
.execute_action_by_method(
|
||||
&method,
|
||||
method,
|
||||
payload.clone(),
|
||||
session_snapshot.clone(),
|
||||
)
|
||||
@@ -68,11 +68,11 @@ impl RpcServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(DaemonRequest::Query { method, payload }) => {
|
||||
Ok(DaemonRequest::Query { ref method, ref payload }) => {
|
||||
match instances.get_default().await {
|
||||
Ok(core) => {
|
||||
// Pass-through for queries using opaque method string
|
||||
match core.execute_query_by_method(&method, payload.clone()).await {
|
||||
match core.execute_query_by_method(method, payload.clone()).await {
|
||||
Ok(out) => DaemonResponse::Ok(out),
|
||||
Err(e) => DaemonResponse::Error(e),
|
||||
}
|
||||
@@ -82,11 +82,20 @@ impl RpcServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => DaemonResponse::Error(format!("Invalid request: {}", e)),
|
||||
Ok(DaemonRequest::Shutdown) => {
|
||||
// Handle shutdown request
|
||||
DaemonResponse::Ok(Vec::new()) // Send an OK response
|
||||
}
|
||||
Err(ref e) => DaemonResponse::Error(format!("Invalid request: {}", e)), // Use ref e here
|
||||
};
|
||||
let _ = stream
|
||||
.write_all(serde_json::to_string(&resp).unwrap().as_bytes())
|
||||
.await;
|
||||
if let Ok(ref req_inner) = req { // Use ref req_inner here
|
||||
if let DaemonRequest::Shutdown = req_inner {
|
||||
break Ok(()); // Break the loop to shut down the server
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ pub enum DaemonRequest {
|
||||
Ping,
|
||||
Action { method: String, payload: Vec<u8> },
|
||||
Query { method: String, payload: Vec<u8> },
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
||||
+6
-7
@@ -43,6 +43,7 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tracing::{error, info};
|
||||
use crate::infra::daemon::state::SessionStateService;
|
||||
|
||||
/// Pending pairing request information
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -140,14 +141,10 @@ pub struct Core {
|
||||
}
|
||||
|
||||
impl Core {
|
||||
/// Initialize a new Core instance with default data directory
|
||||
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let data_dir = crate::config::default_data_dir()?;
|
||||
Self::new_with_config(data_dir).await
|
||||
}
|
||||
|
||||
|
||||
/// Initialize a new Core instance with custom data directory
|
||||
pub async fn new_with_config(data_dir: PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
pub async fn new_with_config(data_dir: PathBuf, session_state: Arc<SessionStateService>) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
info!("Initializing Spacedrive Core at {:?}", data_dir);
|
||||
|
||||
// 1. Load or create app config
|
||||
@@ -195,6 +192,7 @@ impl Core {
|
||||
libraries.clone(),
|
||||
volumes.clone(),
|
||||
library_key_manager.clone(),
|
||||
session_state.clone(), // Pass session_state here
|
||||
);
|
||||
|
||||
// Set job logging configuration if enabled
|
||||
@@ -398,12 +396,13 @@ impl Core {
|
||||
/// Initialize networking from Arc<Core> - for daemon use
|
||||
pub async fn init_networking_shared(
|
||||
core: Arc<Core>,
|
||||
session_state: Arc<SessionStateService>,
|
||||
) -> Result<Arc<Core>, Box<dyn std::error::Error>> {
|
||||
info!("Initializing networking for shared core...");
|
||||
|
||||
// Create a new Core with networking enabled
|
||||
let mut new_core =
|
||||
Core::new_with_config(core.config().read().await.data_dir.clone()).await?;
|
||||
Core::new_with_config(core.config().read().await.data_dir.clone(), session_state).await?;
|
||||
|
||||
// Initialize networking on the new core
|
||||
new_core.init_networking().await?;
|
||||
|
||||
@@ -22,8 +22,7 @@ impl crate::infra::action::CoreAction for SetCurrentLibraryAction {
|
||||
self,
|
||||
context: std::sync::Arc<crate::context::CoreContext>,
|
||||
) -> Result<Self::Output, crate::infra::action::error::ActionError> {
|
||||
// Persist in daemon session state if available (requires daemon to wire it),
|
||||
// otherwise noop. For now, this sets nothing globally; placeholder success.
|
||||
context.session_state.set_current_library(Some(self.input.library_id)).await.map_err(|e| crate::infra::action::error::ActionError::Internal(e.to_string()))?;
|
||||
Ok(SetCurrentLibraryOutput { success: true })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::output::{LocationInfo, LocationsListOutput};
|
||||
use crate::{context::CoreContext, cqrs::Query};
|
||||
use anyhow::Result;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
use sea_orm::EntityTrait;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
@@ -17,12 +17,11 @@ impl Query for LocationsListQuery {
|
||||
let library = context.library_manager.get_library(self.library_id).await.ok_or_else(|| anyhow::anyhow!("Library not found"))?;
|
||||
let db = library.db().conn();
|
||||
let rows = crate::infra::db::entities::location::Entity::find()
|
||||
.filter(crate::infra::db::entities::location::Column::LibraryId.eq(self.library_id))
|
||||
.all(db)
|
||||
.await?;
|
||||
let mut out = Vec::new();
|
||||
for r in rows {
|
||||
out.push(LocationInfo { id: r.uuid, path: std::path::PathBuf::from(r.path), name: r.name });
|
||||
out.push(LocationInfo { id: r.uuid, path: std::path::PathBuf::from(r.name.clone().unwrap_or_default()), name: r.name.clone() });
|
||||
}
|
||||
Ok(LocationsListOutput { locations: out })
|
||||
}
|
||||
|
||||
@@ -3,10 +3,51 @@ use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use crate::service::network::PairingState;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum SerializablePairingState {
|
||||
Idle,
|
||||
GeneratingCode,
|
||||
Broadcasting,
|
||||
Scanning,
|
||||
WaitingForConnection,
|
||||
Connecting,
|
||||
Authenticating,
|
||||
ExchangingKeys,
|
||||
AwaitingConfirmation,
|
||||
EstablishingSession,
|
||||
ChallengeReceived,
|
||||
ResponsePending,
|
||||
ResponseSent,
|
||||
Completed,
|
||||
Failed { reason: String },
|
||||
}
|
||||
|
||||
impl From<PairingState> for SerializablePairingState {
|
||||
fn from(state: PairingState) -> Self {
|
||||
match state {
|
||||
PairingState::Idle => Self::Idle,
|
||||
PairingState::GeneratingCode => Self::GeneratingCode,
|
||||
PairingState::Broadcasting => Self::Broadcasting,
|
||||
PairingState::Scanning => Self::Scanning,
|
||||
PairingState::WaitingForConnection => Self::WaitingForConnection,
|
||||
PairingState::Connecting => Self::Connecting,
|
||||
PairingState::Authenticating => Self::Authenticating,
|
||||
PairingState::ExchangingKeys => Self::ExchangingKeys,
|
||||
PairingState::AwaitingConfirmation => Self::AwaitingConfirmation,
|
||||
PairingState::EstablishingSession => Self::EstablishingSession,
|
||||
PairingState::ChallengeReceived { .. } => Self::ChallengeReceived,
|
||||
PairingState::ResponsePending { .. } => Self::ResponsePending,
|
||||
PairingState::ResponseSent => Self::ResponseSent,
|
||||
PairingState::Completed => Self::Completed,
|
||||
PairingState::Failed { reason } => Self::Failed { reason },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PairingSessionSummary {
|
||||
pub id: Uuid,
|
||||
pub state: PairingState,
|
||||
pub state: SerializablePairingState,
|
||||
pub remote_device_id: Option<Uuid>,
|
||||
pub expires_at: Option<DateTime<Utc>>, // optional if available
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ impl Query for PairStatusQuery {
|
||||
for s in sessions.into_iter() {
|
||||
sessions_out.push(PairingSessionSummary {
|
||||
id: s.id,
|
||||
state: s.state,
|
||||
state: s.state.into(),
|
||||
remote_device_id: s.remote_device_id,
|
||||
expires_at: None,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user