more refactor

This commit is contained in:
Jamie Pine
2025-09-07 01:43:57 -04:00
parent 8f03cc6d4c
commit a8a2c732ef
287 changed files with 2748 additions and 2355 deletions

BIN
Cargo.lock generated
View File

Binary file not shown.

View File

@@ -1,6 +1,6 @@
[workspace]
members = [
"apps/cloud",
# "apps/cloud",
"apps/desktop/crates/*",
"apps/desktop/src-tauri",
"apps/mobile/modules/sd-core/android/crate",

158
Cargo.toml.bak Normal file
View File

@@ -0,0 +1,158 @@
[workspace]
members = [
"apps/cloud",
"apps/desktop/crates/*",
"apps/desktop/src-tauri",
"apps/mobile/modules/sd-core/android/crate",
"apps/mobile/modules/sd-core/core",
"apps/mobile/modules/sd-core/ios/crate",
"apps/server",
"core",
"core/crates/*",
"crates/*"
]
resolver = "2"
[workspace.package]
edition = "2021"
license = "AGPL-3.0-only"
repository = "https://github.com/spacedriveapp/spacedrive"
rust-version = "1.81"
[workspace.dependencies]
# First party dependencies
# sd-cloud-schema = { git = "https://github.com/spacedriveapp/cloud-services-schema", rev = "515ba740ea" }
# Third party dependencies used by one or more of our crates
anyhow = "1.0.94"
async-channel = "2.3"
async-stream = "0.3.6"
async-trait = "0.1.83"
axum = "0.7.9"
axum-extra = "0.9.6"
base64 = "0.22.1"
blake3 = "1.5.5"
bytes = "1.9.0"
chrono = "0.4.39"
ed25519-dalek = "2.1"
flume = "0.11.0"
futures = "0.3.31"
futures-concurrency = "7.6.2"
globset = "0.4.15"
http = "1.2.0"
hyper = "1.5.2"
image = "0.25.5"
iroh = "0.29.0"
itertools = "0.13.0"
lending-stream = "1.0"
libc = "0.2.169"
mimalloc = "0.1.43"
normpath = "1.3"
pin-project-lite = "0.2.15"
quic-rpc = "0.17.3"
rand = "0.9.0-alpha.2"
regex = "1.11.1"
reqwest = { version = "0.12.9", default-features = false }
rmp = "0.8.14"
rmp-serde = "1.3"
rmpv = { version = "1.3", features = ["with-serde"] }
serde = "1.0.216"
serde_json = "1.0.133"
specta = "=2.0.0-rc.20"
strum = "0.26"
strum_macros = "0.26"
tempfile = "3.14.0"
thiserror = "2.0.8"
tokio = "1.42.0"
tokio-stream = "0.1.17"
tokio-util = "0.7.13"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
tracing-test = "0.2.5"
uhlc = "0.8.0" # Must follow version used by specta
uuid = "1.10" # Must follow version used by specta
webp = "0.3.0"
zeroize = "1.8"
[workspace.dependencies.rspc]
git = "https://github.com/spacedriveapp/rspc.git"
rev = "6a77167495"
[workspace.dependencies.prisma-client-rust]
default-features = false
features = ["migrations", "specta", "sqlite", "sqlite-create-many"]
git = "https://github.com/spacedriveapp/prisma-client-rust"
rev = "b22ad7dc7d"
[workspace.dependencies.prisma-client-rust-sdk]
default-features = false
features = ["sqlite"]
git = "https://github.com/spacedriveapp/prisma-client-rust"
rev = "b22ad7dc7d"
# Proper IOS Support
[patch.crates-io.if-watch]
git = "https://github.com/spacedriveapp/if-watch.git"
rev = "a92c17d3f8"
# Add `Control::open_stream_with_addrs`
[patch.crates-io.libp2p]
git = "https://github.com/spacedriveapp/rust-libp2p"
rev = "1024411ffa"
[patch.crates-io.libp2p-core]
git = "https://github.com/spacedriveapp/rust-libp2p"
rev = "1024411ffa"
[patch.crates-io.libp2p-identity]
git = "https://github.com/spacedriveapp/rust-libp2p"
rev = "1024411ffa"
[patch.crates-io.libp2p-swarm]
git = "https://github.com/spacedriveapp/rust-libp2p"
rev = "1024411ffa"
[patch.crates-io.libp2p-stream]
git = "https://github.com/spacedriveapp/rust-libp2p"
rev = "1024411ffa"
[profile.dev]
# Make compilation faster on macOS
codegen-units = 256
debug = 0
incremental = true
lto = false
opt-level = 0
split-debuginfo = "unpacked"
strip = "none"
[profile.dev-debug]
inherits = "dev"
# Enables debugger
codegen-units = 256
debug = "full"
incremental = true
lto = "off"
opt-level = 0
split-debuginfo = "none"
strip = "none"
# Set the settings for build scripts and proc-macros.
[profile.dev.build-override]
opt-level = 3
# Set the default for dependencies, except workspace members.
[profile.dev.package."*"]
incremental = false
opt-level = 3
# Set the default for dependencies, except workspace members.
[profile.dev-debug.package."*"]
debug = "full"
incremental = false
inherits = "dev"
opt-level = 3
# Optimize release builds
[profile.release]
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
lto = true # Enables link to optimizations
opt-level = "s" # Optimize for binary size
panic = "unwind" # Sadly we need unwind to avoid unexpected crashes on third party crates
strip = true # Remove debug symbols

View File

@@ -13,7 +13,6 @@ repository.workspace = true
# Spacedrive Sub-crates
sd-core = { path = "../../../core", features = ["ffmpeg", "heif"] }
sd-fda = { path = "../../../crates/fda" }
sd-prisma = { path = "../../../crates/prisma" }
# Workspace dependencies
axum = { workspace = true, features = ["query"] }
@@ -22,7 +21,6 @@ base64 = { workspace = true }
futures = { workspace = true }
http = { workspace = true }
hyper = { workspace = true }
prisma-client-rust = { workspace = true }
rand = { workspace = true }
rspc = { workspace = true, features = ["tauri"] }
serde = { workspace = true }

View File

@@ -7,9 +7,13 @@ version = "0.1.0"
default = []
# FFmpeg support for video thumbnails
ffmpeg = ["dep:sd-ffmpeg"]
# AI models support
ai = []
# HEIF image support
heif = []
# Mobile platform support
mobile = []
[workspace]
members = ["benchmarks", "crates/*"]
[dependencies]
# Async runtime
@@ -65,13 +69,13 @@ globset = { version = "0.4", features = ["serde1"] }
inventory = "0.3" # Automatic job registration
rmp = "0.8" # MessagePack core types
rmp-serde = "1.3" # MessagePack serialization for job state
sd-task-system = { path = "crates/task-system" }
sd-task-system = { path = "../crates/task-system" }
spacedrive-jobs-derive = { path = "crates/spacedrive-jobs-derive" } # Job derive macros
# Media processing dependencies
image = "0.25"
sd-ffmpeg = { path = "crates/ffmpeg", optional = true }
sd-images = { path = "crates/images" }
sd-ffmpeg = { path = "../crates/ffmpeg", optional = true }
sd-images = { path = "../crates/images" }
tokio-rustls = "0.26"
webp = "0.3"

View File

@@ -3,11 +3,9 @@
//! Shared context providing access to core application components.
use crate::{
config::JobLoggingConfig,
device::DeviceManager, infrastructure::events::EventBus,
keys::library_key_manager::LibraryKeyManager, library::LibraryManager,
infrastructure::actions::manager::ActionManager,
services::networking::NetworkingService, volume::VolumeManager,
config::JobLoggingConfig, device::DeviceManager, infra::actions::manager::ActionManager,
infra::events::EventBus, keys::library_key_manager::LibraryKeyManager, library::LibraryManager,
service::networking::NetworkingService, volume::VolumeManager,
};
use std::{path::PathBuf, sync::Arc};
use tokio::sync::RwLock;
@@ -49,7 +47,7 @@ impl CoreContext {
job_logs_dir: None,
}
}
/// Set job logging configuration
pub fn set_job_logging(&mut self, config: JobLoggingConfig, logs_dir: PathBuf) {
self.job_logging_config = Some(config);

View File

@@ -1,6 +1,6 @@
//! Core addressing data structures for the Virtual Distributed File System
//!
//! This module contains the fundamental "nouns" of the addressing system -
//! This module contains the fundamental "nouns" of the addressing system -
//! the data structures that represent paths in Spacedrive's distributed
//! file system.
@@ -56,7 +56,7 @@ impl SdPath {
/// Create an SdPath for a local file on this device
pub fn local(path: impl Into<PathBuf>) -> Self {
Self::Physical {
device_id: crate::shared::utils::get_current_device_id(),
device_id: crate::common::utils::get_current_device_id(),
path: path.into(),
}
}
@@ -64,7 +64,7 @@ impl SdPath {
/// Check if this path is on the current device
pub fn is_local(&self) -> bool {
match self {
Self::Physical { device_id, .. } => *device_id == crate::shared::utils::get_current_device_id(),
Self::Physical { device_id, .. } => *device_id == crate::common::utils::get_current_device_id(),
Self::Content { .. } => false, // Content paths are abstract, not inherently local
}
}
@@ -73,7 +73,7 @@ impl SdPath {
pub fn as_local_path(&self) -> Option<&Path> {
match self {
Self::Physical { device_id, path } => {
if *device_id == crate::shared::utils::get_current_device_id() {
if *device_id == crate::common::utils::get_current_device_id() {
Some(path)
} else {
None
@@ -87,7 +87,7 @@ impl SdPath {
pub fn display(&self) -> String {
match self {
Self::Physical { device_id, path } => {
if *device_id == crate::shared::utils::get_current_device_id() {
if *device_id == crate::common::utils::get_current_device_id() {
path.display().to_string()
} else {
format!("sd://{}/{}", device_id, path.display())
@@ -147,7 +147,7 @@ impl SdPath {
Self::Content { .. } => None, // Content paths don't have volumes until resolved
}
}
/// Check if this path is on the same volume as another path
pub async fn same_volume(&self, other: &SdPath, volume_manager: &crate::volume::VolumeManager) -> bool {
match (self, other) {
@@ -155,7 +155,7 @@ impl SdPath {
if !self.is_local() || !other.is_local() {
return false;
}
if let (Some(self_path), Some(other_path)) = (self.as_local_path(), other.as_local_path()) {
volume_manager.same_volume(self_path, other_path).await
} else {
@@ -174,7 +174,7 @@ impl SdPath {
pub fn from_uri(uri: &str) -> Result<Self, SdPathParseError> {
if uri.starts_with("sd://") {
let uri = &uri[5..]; // Strip "sd://"
if let Some(content_id_str) = uri.strip_prefix("content/") {
// Parse content path
let content_id = Uuid::parse_str(content_id_str)
@@ -186,11 +186,11 @@ impl SdPath {
if parts.len() != 2 {
return Err(SdPathParseError::InvalidFormat);
}
let device_id = Uuid::parse_str(parts[0])
.map_err(|_| SdPathParseError::InvalidDeviceId)?;
let path = PathBuf::from("/").join(parts[1]);
Ok(Self::Physical { device_id, path })
}
} else {
@@ -252,14 +252,14 @@ impl SdPath {
&self,
context: &crate::context::CoreContext
) -> Result<SdPath, PathResolutionError> {
let resolver = crate::operations::addressing::PathResolver;
let resolver = crate::ops::addressing::PathResolver;
resolver.resolve(self, context).await
}
/// Resolve this path using a JobContext
pub async fn resolve_in_job<'a>(
&self,
job_ctx: &crate::infrastructure::jobs::context::JobContext<'a>
job_ctx: &crate::infra::jobs::context::JobContext<'a>
) -> Result<SdPath, PathResolutionError> {
// For now, if it's already physical, just return it
// TODO: Implement proper resolution using job context's library and networking

View File

@@ -133,7 +133,7 @@ impl ContentKind {
}
/// Get content kind from file type
pub fn from_file_type(file_type: &crate::file_type::FileType) -> Self {
pub fn from_file_type(file_type: &crate::filetype::FileType) -> Self {
file_type.category
}
}
@@ -171,10 +171,10 @@ impl ContentHashGenerator {
let mut hasher = Hasher::new();
hasher.update(&size.to_le_bytes());
let content = tokio::fs::read(path).await?;
hasher.update(&content);
Ok(hasher.finalize().to_hex()[..16].to_string())
}

View File

@@ -11,265 +11,269 @@ use uuid::Uuid;
/// A device running Spacedrive
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Device {
/// Unique identifier for this device
pub id: Uuid,
/// Unique identifier for this device
pub id: Uuid,
/// Human-readable name
pub name: String,
/// Human-readable name
pub name: String,
/// Operating system
pub os: OperatingSystem,
/// Operating system
pub os: OperatingSystem,
/// Hardware model (e.g., "MacBook Pro", "iPhone 15")
pub hardware_model: Option<String>,
/// Hardware model (e.g., "MacBook Pro", "iPhone 15")
pub hardware_model: Option<String>,
/// Network addresses for P2P connections
pub network_addresses: Vec<String>,
/// Network addresses for P2P connections
pub network_addresses: Vec<String>,
/// Whether this device is currently online
pub is_online: bool,
/// Whether this device is currently online
pub is_online: bool,
/// Sync leadership status per library
pub sync_leadership: HashMap<Uuid, SyncRole>,
/// Sync leadership status per library
pub sync_leadership: HashMap<Uuid, SyncRole>,
/// Last time this device was seen
pub last_seen_at: DateTime<Utc>,
/// Last time this device was seen
pub last_seen_at: DateTime<Utc>,
/// When this device was first added
pub created_at: DateTime<Utc>,
/// When this device was first added
pub created_at: DateTime<Utc>,
/// When this device info was last updated
pub updated_at: DateTime<Utc>,
/// When this device info was last updated
pub updated_at: DateTime<Utc>,
}
/// Sync role for a device in a specific library
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum SyncRole {
/// This device maintains the sync log for the library
Leader,
/// This device maintains the sync log for the library
Leader,
/// This device syncs from the leader
Follower,
/// This device syncs from the leader
Follower,
/// This device doesn't participate in sync for this library
Inactive,
/// This device doesn't participate in sync for this library
Inactive,
}
/// Operating system types
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum OperatingSystem {
MacOS,
Windows,
Linux,
IOs,
Android,
Other,
MacOS,
Windows,
Linux,
IOs,
Android,
Other,
}
impl Device {
/// Create a new device
pub fn new(name: String) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4(),
name,
os: detect_operating_system(),
hardware_model: detect_hardware_model(),
network_addresses: Vec::new(),
is_online: true,
sync_leadership: HashMap::new(),
last_seen_at: now,
created_at: now,
updated_at: now,
}
}
/// Create a new device
pub fn new(name: String) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4(),
name,
os: detect_operating_system(),
hardware_model: detect_hardware_model(),
network_addresses: Vec::new(),
is_online: true,
sync_leadership: HashMap::new(),
last_seen_at: now,
created_at: now,
updated_at: now,
}
}
/// Create the current device
pub fn current() -> Self {
Self::new(get_device_name())
}
/// Create the current device
pub fn current() -> Self {
Self::new(get_device_name())
}
/// Update network addresses
pub fn update_network_addresses(&mut self, addresses: Vec<String>) {
self.network_addresses = addresses;
self.updated_at = Utc::now();
}
/// Update network addresses
pub fn update_network_addresses(&mut self, addresses: Vec<String>) {
self.network_addresses = addresses;
self.updated_at = Utc::now();
}
/// Mark device as online
pub fn mark_online(&mut self) {
self.is_online = true;
self.last_seen_at = Utc::now();
self.updated_at = Utc::now();
}
/// Mark device as online
pub fn mark_online(&mut self) {
self.is_online = true;
self.last_seen_at = Utc::now();
self.updated_at = Utc::now();
}
/// Mark device as offline
pub fn mark_offline(&mut self) {
self.is_online = false;
self.updated_at = Utc::now();
}
/// Mark device as offline
pub fn mark_offline(&mut self) {
self.is_online = false;
self.updated_at = Utc::now();
}
/// Check if this is the current device
pub fn is_current(&self) -> bool {
self.id == crate::shared::utils::get_current_device_id()
}
/// Check if this is the current device
pub fn is_current(&self) -> bool {
self.id == crate::common::utils::get_current_device_id()
}
/// Set sync role for a library
pub fn set_sync_role(&mut self, library_id: Uuid, role: SyncRole) {
self.sync_leadership.insert(library_id, role);
self.updated_at = Utc::now();
}
/// Set sync role for a library
pub fn set_sync_role(&mut self, library_id: Uuid, role: SyncRole) {
self.sync_leadership.insert(library_id, role);
self.updated_at = Utc::now();
}
/// Get sync role for a library
pub fn sync_role(&self, library_id: &Uuid) -> SyncRole {
self.sync_leadership.get(library_id).copied().unwrap_or(SyncRole::Inactive)
}
/// Get sync role for a library
pub fn sync_role(&self, library_id: &Uuid) -> SyncRole {
self.sync_leadership
.get(library_id)
.copied()
.unwrap_or(SyncRole::Inactive)
}
/// Check if this device is the sync leader for a library
pub fn is_sync_leader(&self, library_id: &Uuid) -> bool {
matches!(self.sync_role(library_id), SyncRole::Leader)
}
/// Check if this device is the sync leader for a library
pub fn is_sync_leader(&self, library_id: &Uuid) -> bool {
matches!(self.sync_role(library_id), SyncRole::Leader)
}
/// Get all libraries where this device is the leader
pub fn leader_libraries(&self) -> Vec<Uuid> {
self.sync_leadership
.iter()
.filter_map(|(lib_id, role)| {
if *role == SyncRole::Leader {
Some(*lib_id)
} else {
None
}
})
.collect()
}
/// Get all libraries where this device is the leader
pub fn leader_libraries(&self) -> Vec<Uuid> {
self.sync_leadership
.iter()
.filter_map(|(lib_id, role)| {
if *role == SyncRole::Leader {
Some(*lib_id)
} else {
None
}
})
.collect()
}
}
/// Get the device name from the system
fn get_device_name() -> String {
#[cfg(target_os = "macos")]
{
return whoami::devicename();
}
#[cfg(target_os = "macos")]
{
return whoami::devicename();
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
if let Ok(name) = hostname::get() {
if let Ok(name_str) = name.into_string() {
return name_str;
}
}
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
if let Ok(name) = hostname::get() {
if let Ok(name_str) = name.into_string() {
return name_str;
}
}
}
"Unknown Device".to_string()
"Unknown Device".to_string()
}
/// Detect the operating system
fn detect_operating_system() -> OperatingSystem {
#[cfg(target_os = "macos")]
return OperatingSystem::MacOS;
#[cfg(target_os = "macos")]
return OperatingSystem::MacOS;
#[cfg(target_os = "windows")]
return OperatingSystem::Windows;
#[cfg(target_os = "windows")]
return OperatingSystem::Windows;
#[cfg(target_os = "linux")]
return OperatingSystem::Linux;
#[cfg(target_os = "linux")]
return OperatingSystem::Linux;
#[cfg(target_os = "ios")]
return OperatingSystem::IOs;
#[cfg(target_os = "ios")]
return OperatingSystem::IOs;
#[cfg(target_os = "android")]
return OperatingSystem::Android;
#[cfg(target_os = "android")]
return OperatingSystem::Android;
#[cfg(not(any(
target_os = "macos",
target_os = "windows",
target_os = "linux",
target_os = "ios",
target_os = "android"
)))]
return OperatingSystem::Other;
#[cfg(not(any(
target_os = "macos",
target_os = "windows",
target_os = "linux",
target_os = "ios",
target_os = "android"
)))]
return OperatingSystem::Other;
}
/// Get hardware model information
fn detect_hardware_model() -> Option<String> {
// This would use platform-specific APIs
// For now, return None
None
// This would use platform-specific APIs
// For now, return None
None
}
impl std::fmt::Display for OperatingSystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OperatingSystem::MacOS => write!(f, "macOS"),
OperatingSystem::Windows => write!(f, "Windows"),
OperatingSystem::Linux => write!(f, "Linux"),
OperatingSystem::IOs => write!(f, "iOS"),
OperatingSystem::Android => write!(f, "Android"),
OperatingSystem::Other => write!(f, "Other"),
}
}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OperatingSystem::MacOS => write!(f, "macOS"),
OperatingSystem::Windows => write!(f, "Windows"),
OperatingSystem::Linux => write!(f, "Linux"),
OperatingSystem::IOs => write!(f, "iOS"),
OperatingSystem::Android => write!(f, "Android"),
OperatingSystem::Other => write!(f, "Other"),
}
}
}
// Conversion implementations for database entities
use crate::infrastructure::database::entities;
use crate::infra::database::entities;
use sea_orm::ActiveValue;
impl From<Device> for entities::device::ActiveModel {
fn from(device: Device) -> Self {
use sea_orm::ActiveValue::*;
fn from(device: Device) -> Self {
use sea_orm::ActiveValue::*;
entities::device::ActiveModel {
id: NotSet, // Auto-increment
uuid: Set(device.id),
name: Set(device.name),
os: Set(device.os.to_string()),
os_version: Set(None), // TODO: Add to domain model if needed
hardware_model: Set(device.hardware_model),
network_addresses: Set(serde_json::json!(device.network_addresses)),
is_online: Set(device.is_online),
last_seen_at: Set(device.last_seen_at),
capabilities: Set(serde_json::json!({
"indexing": true,
"p2p": true,
"volume_detection": true
})),
sync_leadership: Set(serde_json::json!(device.sync_leadership)),
created_at: Set(device.created_at),
updated_at: Set(device.updated_at),
}
}
entities::device::ActiveModel {
id: NotSet, // Auto-increment
uuid: Set(device.id),
name: Set(device.name),
os: Set(device.os.to_string()),
os_version: Set(None), // TODO: Add to domain model if needed
hardware_model: Set(device.hardware_model),
network_addresses: Set(serde_json::json!(device.network_addresses)),
is_online: Set(device.is_online),
last_seen_at: Set(device.last_seen_at),
capabilities: Set(serde_json::json!({
"indexing": true,
"p2p": true,
"volume_detection": true
})),
sync_leadership: Set(serde_json::json!(device.sync_leadership)),
created_at: Set(device.created_at),
updated_at: Set(device.updated_at),
}
}
}
impl TryFrom<entities::device::Model> for Device {
type Error = serde_json::Error;
type Error = serde_json::Error;
fn try_from(model: entities::device::Model) -> Result<Self, Self::Error> {
let network_addresses: Vec<String> = serde_json::from_value(model.network_addresses)?;
let sync_leadership: HashMap<Uuid, SyncRole> = serde_json::from_value(model.sync_leadership)?;
fn try_from(model: entities::device::Model) -> Result<Self, Self::Error> {
let network_addresses: Vec<String> = serde_json::from_value(model.network_addresses)?;
let sync_leadership: HashMap<Uuid, SyncRole> =
serde_json::from_value(model.sync_leadership)?;
Ok(Device {
id: model.uuid,
name: model.name,
os: parse_operating_system(&model.os),
hardware_model: model.hardware_model,
network_addresses,
is_online: model.is_online,
sync_leadership,
last_seen_at: model.last_seen_at,
created_at: model.created_at,
updated_at: model.updated_at,
})
}
Ok(Device {
id: model.uuid,
name: model.name,
os: parse_operating_system(&model.os),
hardware_model: model.hardware_model,
network_addresses,
is_online: model.is_online,
sync_leadership,
last_seen_at: model.last_seen_at,
created_at: model.created_at,
updated_at: model.updated_at,
})
}
}
/// Parse OS string to enum
fn parse_operating_system(os_str: &str) -> OperatingSystem {
match os_str {
"macOS" => OperatingSystem::MacOS,
"Windows" => OperatingSystem::Windows,
"Linux" => OperatingSystem::Linux,
"iOS" => OperatingSystem::IOs,
"Android" => OperatingSystem::Android,
_ => OperatingSystem::Other,
}
}
match os_str {
"macOS" => OperatingSystem::MacOS,
"Windows" => OperatingSystem::Windows,
"Linux" => OperatingSystem::Linux,
"iOS" => OperatingSystem::IOs,
"Android" => OperatingSystem::Android,
_ => OperatingSystem::Other,
}
}

View File

@@ -2,7 +2,7 @@
use super::{FileType, FileTypeError, IdentificationMethod, IdentificationResult, Result};
use crate::domain::ContentKind;
use crate::file_type::magic::MagicBytePattern;
use crate::filetype::magic::MagicBytePattern;
use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
@@ -50,10 +50,10 @@ struct MagicByteDefinition {
pub struct FileTypeRegistry {
/// All registered file types by ID
types: HashMap<String, FileType>,
/// Extension to type IDs mapping
extension_map: HashMap<String, Vec<String>>,
/// MIME type to type ID mapping
mime_map: HashMap<String, String>,
}
@@ -66,18 +66,18 @@ impl FileTypeRegistry {
extension_map: HashMap::new(),
mime_map: HashMap::new(),
};
// Load built-in types
registry.load_builtin_types();
registry
}
/// Load built-in file type definitions
fn load_builtin_types(&mut self) {
// Load all TOML definitions from the builtin module
let toml_definitions = super::builtin::get_builtin_toml_definitions();
for toml_content in toml_definitions {
// Use the loader to parse TOML
if let Err(e) = self.load_from_toml(toml_content) {
@@ -85,12 +85,12 @@ impl FileTypeRegistry {
}
}
}
/// Register a file type
pub fn register(&mut self, file_type: FileType) -> Result<()> {
// Add to main registry
let id = file_type.id.clone();
// Update extension map
for ext in &file_type.extensions {
self.extension_map
@@ -98,26 +98,26 @@ impl FileTypeRegistry {
.or_insert_with(Vec::new)
.push(id.clone());
}
// Update MIME map
for mime in &file_type.mime_types {
self.mime_map.insert(mime.clone(), id.clone());
}
self.types.insert(id, file_type);
Ok(())
}
/// Get a file type by ID
pub fn get(&self, id: &str) -> Option<&FileType> {
self.types.get(id)
}
/// Get file types by extension
pub fn get_by_extension(&self, ext: &str) -> Vec<&FileType> {
let ext = ext.trim_start_matches('.').to_lowercase();
self.extension_map
.get(&ext)
.map(|ids| {
@@ -127,14 +127,14 @@ impl FileTypeRegistry {
})
.unwrap_or_default()
}
/// Get file type by MIME type
pub fn get_by_mime(&self, mime: &str) -> Option<&FileType> {
self.mime_map
.get(mime)
.and_then(|id| self.types.get(id))
}
/// Identify a file type from a path
pub async fn identify(&self, path: &Path) -> Result<IdentificationResult> {
// Get extension
@@ -142,10 +142,10 @@ impl FileTypeRegistry {
.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
// Get possible types by extension
let candidates = self.get_by_extension(extension);
match candidates.len() {
0 => {
// No extension match, try magic bytes on all types
@@ -183,7 +183,7 @@ impl FileTypeRegistry {
}
}
}
/// Identify by magic bytes from a set of candidates
async fn identify_by_magic_bytes(
&self,
@@ -195,10 +195,10 @@ impl FileTypeRegistry {
let mut buffer = vec![0u8; MAX_MAGIC_BYTES];
let bytes_read = file.read(&mut buffer).await?;
buffer.truncate(bytes_read);
// Check each candidate
let mut matches: Vec<(&FileType, u8)> = Vec::new();
for candidate in candidates {
for pattern in &candidate.magic_bytes {
if pattern.matches(&buffer) {
@@ -207,10 +207,10 @@ impl FileTypeRegistry {
}
}
}
// Sort by priority (highest first)
matches.sort_by_key(|(_, priority)| std::cmp::Reverse(*priority));
if let Some((file_type, _)) = matches.first() {
Ok(IdentificationResult {
file_type: (*file_type).clone(),
@@ -228,21 +228,21 @@ impl FileTypeRegistry {
}
}
}
/// Check if a specific file type's magic bytes match
async fn check_magic_bytes(&self, path: &Path, file_type: &FileType) -> Result<bool> {
if file_type.magic_bytes.is_empty() {
return Ok(true);
}
let mut file = File::open(path).await?;
let mut buffer = vec![0u8; MAX_MAGIC_BYTES];
let bytes_read = file.read(&mut buffer).await?;
buffer.truncate(bytes_read);
Ok(file_type.magic_bytes.iter().any(|pattern| pattern.matches(&buffer)))
}
/// Identify by content analysis (for text files)
async fn identify_by_content(
&self,
@@ -254,7 +254,7 @@ impl FileTypeRegistry {
let mut buffer = vec![0u8; MAX_CONTENT_BYTES];
let bytes_read = file.read(&mut buffer).await?;
buffer.truncate(bytes_read);
// Try to convert to string
if let Ok(content) = String::from_utf8(buffer) {
// Simple heuristics for now
@@ -269,7 +269,7 @@ impl FileTypeRegistry {
}
}
}
// Default to first text candidate
if let Some(text_type) = candidates.iter().find(|ft| {
matches!(ft.category, ContentKind::Text | ContentKind::Code)
@@ -283,20 +283,20 @@ impl FileTypeRegistry {
Err(FileTypeError::UnknownType)
}
}
/// Load definitions from a TOML string
pub fn load_from_toml(&mut self, content: &str) -> Result<()> {
let defs: FileTypeDefinitions = toml::from_str(content)
.map_err(|e| FileTypeError::InvalidConfig(format!("TOML parse error: {}", e)))?;
for def in defs.file_types {
let file_type = self.definition_to_file_type(def)?;
self.register(file_type)?;
}
Ok(())
}
/// Convert a definition to a FileType
fn definition_to_file_type(&self, def: FileTypeDefinition) -> Result<FileType> {
// Parse category
@@ -318,7 +318,7 @@ impl FileTypeRegistry {
"key" => ContentKind::Key,
_ => ContentKind::Unknown,
};
// Parse magic bytes
let mut magic_bytes = Vec::new();
for mb_def in def.magic_bytes {
@@ -329,7 +329,7 @@ impl FileTypeRegistry {
).map_err(|e| FileTypeError::InvalidConfig(format!("Invalid magic bytes: {}", e)))?;
magic_bytes.push(pattern);
}
Ok(FileType {
id: def.id,
name: def.name,
@@ -353,21 +353,21 @@ impl Default for FileTypeRegistry {
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_registry_basic() {
let registry = FileTypeRegistry::new();
// Test getting by extension
let jpeg_types = registry.get_by_extension("jpg");
assert_eq!(jpeg_types.len(), 1);
assert_eq!(jpeg_types[0].id, "image/jpeg");
// Test getting by MIME
let png_type = registry.get_by_mime("image/png");
assert!(png_type.is_some());
assert_eq!(png_type.unwrap().id, "image/png");
// Test extension conflict
let ts_types = registry.get_by_extension("ts");
assert_eq!(ts_types.len(), 2); // TypeScript and MPEG-TS

View File

@@ -1,9 +1,9 @@
//! Error types for the Action System
use crate::{
infrastructure::jobs::error::JobError,
infra::jobs::error::JobError,
library::LibraryError,
shared::errors::CoreError,
common::errors::CoreError,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
@@ -18,83 +18,83 @@ pub enum ActionError {
/// Action type not registered in the registry
#[error("Action type '{0}' is not registered")]
ActionNotRegistered(String),
/// Invalid action type for the handler
#[error("Invalid action type for this handler")]
InvalidActionType,
/// Invalid input provided to action
#[error("Invalid input: {0}")]
InvalidInput(String),
/// Permission denied for this action
#[error("Permission denied for action '{action}': {reason}")]
PermissionDenied {
action: String,
reason: String,
},
/// Library not found
#[error("Library {0} not found")]
LibraryNotFound(Uuid),
/// Location not found
#[error("Location {0} not found")]
LocationNotFound(Uuid),
/// Device not found
#[error("Device {0} not found")]
DeviceNotFound(Uuid),
/// File system error
#[error("File system error at '{path}': {error}")]
FileSystem {
path: String,
error: String,
},
/// Network error for cross-device operations
#[error("Network error with device {device_id}: {error}")]
Network {
device_id: Uuid,
error: String,
},
/// Job creation or execution error
#[error("Job error: {0}")]
Job(#[from] JobError),
/// Database operation error
#[error("Database error: {0}")]
Database(String),
/// Validation error
#[error("Validation error for field '{field}': {message}")]
Validation {
field: String,
message: String,
},
/// Action execution timeout
#[error("Action execution timed out")]
Timeout,
/// Action was cancelled
#[error("Action was cancelled")]
Cancelled,
/// Device manager error
#[error("Device manager error: {0}")]
DeviceManager(String),
/// JSON serialization error
#[error("JSON serialization error: {0}")]
JsonSerialization(#[from] serde_json::Error),
/// Sea-ORM database error
#[error("Database operation failed: {0}")]
SeaOrm(#[from] sea_orm::DbErr),
/// IO error
#[error("IO error at '{path}': {source}")]
Io {
@@ -102,7 +102,7 @@ pub enum ActionError {
#[source]
source: std::io::Error,
},
/// Generic internal error
#[error("Internal error: {0}")]
Internal(String),
@@ -140,7 +140,7 @@ impl ActionError {
source: error,
}
}
pub fn device_manager_error(error: impl std::fmt::Display) -> Self {
Self::DeviceManager(error.to_string())
}

View File

@@ -5,8 +5,8 @@ use super::{
};
use crate::{
context::CoreContext,
infrastructure::database::entities::{audit_log, AuditLog, AuditLogActive},
shared::utils::get_current_device_id,
infra::database::entities::{audit_log, AuditLog, AuditLogActive},
common::utils::get_current_device_id,
};
use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, Set};
use std::sync::Arc;
@@ -63,7 +63,7 @@ impl ActionManager {
) -> ActionResult<audit_log::Model> {
let library = self.get_library(library_id).await?;
let db = library.db().conn();
let audit_entry = AuditLogActive {
uuid: Set(Uuid::new_v4().to_string()),
action_type: Set(action.kind().to_string()),
@@ -108,7 +108,7 @@ impl ActionManager {
// Convert to active model and explicitly mark changed fields
let mut active_model: AuditLogActive = entry.into();
// Explicitly mark the fields we want to update as "Set" (changed)
match result {
Ok(output) => {
@@ -124,7 +124,7 @@ impl ActionManager {
active_model.error_message = Set(Some(error.to_string()));
}
}
active_model
.update(db)
.await
@@ -152,13 +152,13 @@ impl ActionManager {
) -> ActionResult<Vec<audit_log::Model>> {
let library = self.get_library(library_id).await?;
let db = library.db().conn();
let mut query = AuditLog::find();
if let Some(limit) = limit {
query = query.limit(limit);
}
if let Some(offset) = offset {
query = query.offset(offset);
}
@@ -177,7 +177,7 @@ impl ActionManager {
) -> ActionResult<Option<audit_log::Model>> {
let library = self.get_library(library_id).await?;
let db = library.db().conn();
AuditLog::find()
.filter(audit_log::Column::Uuid.eq(action_uuid))
.one(db)

View File

@@ -1,6 +1,6 @@
//! Action execution receipts
use crate::infrastructure::jobs::handle::JobHandle;
use crate::infra::jobs::handle::JobHandle;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@@ -9,14 +9,14 @@ use uuid::Uuid;
pub struct ActionReceipt {
/// Unique identifier for the action execution
pub action_id: Uuid,
/// Optional job handle if the action created a background job
#[serde(skip)]
pub job_handle: Option<JobHandle>,
/// Optional result payload (for immediate actions)
pub result_payload: Option<serde_json::Value>,
/// Whether the action completed immediately or is running in background
pub is_immediate: bool,
}
@@ -31,7 +31,7 @@ impl ActionReceipt {
is_immediate: true,
}
}
/// Create a new receipt for a job-based action
pub fn job_based(action_id: Uuid, job_handle: JobHandle) -> Self {
Self {
@@ -41,7 +41,7 @@ impl ActionReceipt {
is_immediate: false,
}
}
/// Create a new receipt for a hybrid action (immediate with optional job)
pub fn hybrid(
action_id: Uuid,

View File

@@ -5,7 +5,7 @@ mod tests {
use super::*;
use crate::{
context::CoreContext,
infrastructure::actions::{Action, registry::REGISTRY},
infra::actions::{Action, registry::REGISTRY},
};
#[test]
@@ -53,7 +53,7 @@ mod tests {
assert!(REGISTRY.has_action("location.add"));
assert!(REGISTRY.has_action("location.remove"));
assert!(REGISTRY.has_action("location.index"));
// Test that unknown actions are not registered
assert!(!REGISTRY.has_action("unknown.action"));
}
@@ -62,7 +62,7 @@ mod tests {
fn test_action_registry_get_handler() {
let handler = REGISTRY.get("library.create");
assert!(handler.is_some());
let handler = REGISTRY.get("unknown.action");
assert!(handler.is_none());
}

View File

@@ -5,11 +5,11 @@
//! - Checking daemon status
//! - Managing multiple daemon instances
use crate::infrastructure::cli::daemon::{
use crate::infra::cli::daemon::{
Daemon, DaemonClient, DaemonCommand, DaemonConfig, DaemonResponse,
};
use crate::infrastructure::cli::output::messages::LibraryInfo as OutputLibraryInfo;
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infra::cli::output::messages::LibraryInfo as OutputLibraryInfo;
use crate::infra::cli::output::{CliOutput, Message};
use clap::Subcommand;
use comfy_table::Table;
use std::path::PathBuf;
@@ -215,11 +215,11 @@ async fn handle_stop_daemon(
output.print(Message::DaemonStopped {
instance: instance_display.to_string(),
})?;
// If reset flag is set, remove the data directory
if reset {
output.warning("Removing all Spacedrive data...")?;
if data_dir.exists() {
match std::fs::remove_dir_all(&data_dir) {
Ok(_) => {

View File

@@ -5,9 +5,9 @@
//! - Indexing operations with enhanced scope options
//! - Legacy scanning operations
use crate::infrastructure::cli::adapters::FileCopyCliArgs;
use crate::infrastructure::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infra::cli::adapters::FileCopyCliArgs;
use crate::infra::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infra::cli::output::{CliOutput, Message};
use clap::{Subcommand, ValueEnum};
use std::path::PathBuf;
@@ -129,7 +129,11 @@ async fn handle_copy_command(
// Send copy command to daemon
match client
.send_command(DaemonCommand::Copy {
sources: input.sources.iter().map(|p| p.display().to_string()).collect(),
sources: input
.sources
.iter()
.map(|p| p.display().to_string())
.collect(),
destination: input.destination.display().to_string(),
overwrite: input.overwrite,
verify: input.verify_checksum,

View File

@@ -5,12 +5,10 @@
//! - Getting detailed job information
//! - Monitoring job progress in real-time
use crate::infrastructure::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infrastructure::cli::output::messages::{
JobInfo as OutputJobInfo, JobStatus as OutputJobStatus,
};
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infrastructure::cli::utils::{format_bytes, progress_styles};
use crate::infra::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infra::cli::output::messages::{JobInfo as OutputJobInfo, JobStatus as OutputJobStatus};
use crate::infra::cli::output::{CliOutput, Message};
use crate::infra::cli::utils::{format_bytes, progress_styles};
use clap::Subcommand;
use comfy_table::Table;
use indicatif::{MultiProgress, ProgressBar};
@@ -102,7 +100,7 @@ pub async fn handle_job_command(
.map(|job| {
let status = job
.status
.parse::<crate::infrastructure::jobs::types::JobStatus>()
.parse::<crate::infra::jobs::types::JobStatus>()
.map(OutputJobStatus::from)
.unwrap_or(OutputJobStatus::Queued);
@@ -119,7 +117,7 @@ pub async fn handle_job_command(
if matches!(
output.format(),
crate::infrastructure::cli::output::OutputFormat::Json
crate::infra::cli::output::OutputFormat::Json
) {
output.print(Message::JobList { jobs: output_jobs })?;
} else {

View File

@@ -6,9 +6,9 @@
//! - Switching between libraries
//! - Getting current library info
use crate::infrastructure::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infrastructure::cli::output::messages::LibraryInfo as OutputLibraryInfo;
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infra::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infra::cli::output::messages::LibraryInfo as OutputLibraryInfo;
use crate::infra::cli::output::{CliOutput, Message};
use clap::Subcommand;
use comfy_table::Table;
use std::path::PathBuf;
@@ -123,7 +123,7 @@ pub async fn handle_library_command(
if detailed
|| matches!(
output.format(),
crate::infrastructure::cli::output::OutputFormat::Json
crate::infra::cli::output::OutputFormat::Json
) {
output.print(Message::LibraryList {
libraries: output_libs,

View File

@@ -6,9 +6,9 @@
//! - Removing locations
//! - Rescanning locations for changes
use crate::infrastructure::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infrastructure::cli::output::messages::LocationInfo as OutputLocationInfo;
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infra::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infra::cli::output::messages::LocationInfo as OutputLocationInfo;
use crate::infra::cli::output::{CliOutput, Message};
use clap::{Subcommand, ValueEnum};
use comfy_table::Table;
use std::path::PathBuf;
@@ -84,7 +84,7 @@ pub async fn handle_location_command(
name,
})
.await;
match response {
Ok(DaemonResponse::LocationAdded {
location_id,
@@ -177,7 +177,7 @@ pub async fn handle_location_command(
if matches!(
output.format(),
crate::infrastructure::cli::output::OutputFormat::Json
crate::infra::cli::output::OutputFormat::Json
) {
output.print(Message::LocationList {
locations: output_locations,

View File

@@ -6,11 +6,11 @@
//! - Pairing operations with other devices
//! - Spacedrop file sharing
use crate::infrastructure::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infrastructure::cli::output::messages::{DeviceInfo as OutputDeviceInfo, DeviceStatus, PairingRequest};
use crate::infrastructure::cli::utils::format_bytes_parts;
use crate::services::networking::DeviceInfo;
use crate::infra::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infra::cli::output::{CliOutput, Message};
use crate::infra::cli::output::messages::{DeviceInfo as OutputDeviceInfo, DeviceStatus, PairingRequest};
use crate::infra::cli::utils::format_bytes_parts;
use crate::service::networking::DeviceInfo;
use clap::Subcommand;
use comfy_table::{presets::UTF8_FULL, Table};
use std::path::PathBuf;
@@ -24,7 +24,7 @@ pub enum NetworkCommands {
/// Start networking services
Start,
/// Stop networking services
/// Stop networking services
Stop,
/// List discovered devices
@@ -184,8 +184,8 @@ pub async fn handle_network_command(
peer_id: None, // TODO: Get from daemon if available
})
.collect();
if matches!(output.format(), crate::infrastructure::cli::output::OutputFormat::Json) {
if matches!(output.format(), crate::infra::cli::output::OutputFormat::Json) {
output.print(Message::DevicesList { devices: output_devices })?;
} else {
// For human output, use a table
@@ -226,7 +226,7 @@ pub async fn handle_network_command(
return Ok(());
}
};
match client
.send_command(DaemonCommand::RevokeDevice { device_id: device_uuid })
.await?
@@ -257,10 +257,10 @@ pub async fn handle_network_command(
return Ok(());
}
};
// Use sender name or default
let sender_name = sender.unwrap_or_else(|| "Anonymous".to_string());
match client
.send_command(DaemonCommand::SendSpacedrop {
device_id: device_uuid,
@@ -313,7 +313,7 @@ async fn handle_pairing_command(
expires_in_seconds,
} => {
output.print(Message::PairingCodeGenerated { code: code.clone() })?;
output.section()
.empty_line()
.text(&format!("This code expires in {} seconds", expires_in_seconds))
@@ -345,8 +345,8 @@ async fn handle_pairing_command(
.await?
{
DaemonResponse::PairingInProgress => {
output.print(Message::PairingInProgress {
device_name: "remote device".to_string()
output.print(Message::PairingInProgress {
device_name: "remote device".to_string()
})?;
output.info("Pairing process started - this may take a few moments...")?;
@@ -465,9 +465,9 @@ async fn handle_pairing_command(
let section = output.section()
.title("Current Pairing Status")
.item("Status", &status);
let section = if let Some(device) = remote_device {
section.item("Connected Device", &format!("{} ({})",
section.item("Connected Device", &format!("{} ({})",
device.device_name,
&device.device_id.to_string()[..8]
))
@@ -506,7 +506,7 @@ async fn handle_pairing_command(
timestamp: 0, // TODO: Parse from received_at string
})
.collect();
output.print(Message::PairingStatus {
status: "active".to_string(),
pending_requests,
@@ -577,7 +577,7 @@ async fn handle_pairing_command(
return Ok(());
}
};
match client
.send_command(DaemonCommand::AcceptPairing { request_id: request_uuid })
.await?
@@ -602,7 +602,7 @@ async fn handle_pairing_command(
return Ok(());
}
};
match client
.send_command(DaemonCommand::RejectPairing { request_id: request_uuid })
.await?

View File

@@ -5,8 +5,8 @@
//! - Log viewing and following
//! - Real-time monitoring
use crate::infrastructure::cli::daemon::{DaemonClient, DaemonCommand, DaemonConfig, DaemonResponse};
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infra::cli::daemon::{DaemonClient, DaemonCommand, DaemonConfig, DaemonResponse};
use crate::infra::cli::output::{CliOutput, Message};
use clap::Subcommand;
use std::fs::File;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
@@ -30,7 +30,7 @@ pub enum SystemCommands {
/// Monitor all system activity in real-time
Monitor,
/// Launch Terminal User Interface for real-time monitoring
Tui,
}
@@ -61,20 +61,20 @@ async fn handle_status_command(
output: &mut CliOutput,
) -> Result<(), Box<dyn std::error::Error>> {
let mut client = DaemonClient::new_with_instance(instance_name.clone());
match client.send_command(DaemonCommand::GetStatus).await {
Ok(DaemonResponse::Status(status)) => {
let section = output.section()
.title("System Status")
.item("Version", &status.version)
.item("Uptime", &format!("{} seconds", status.uptime_secs));
let section = if let Some(library_id) = status.current_library {
section.item("Current Library", &library_id.to_string())
} else {
section.item("Current Library", "None")
};
section.item("Active Jobs", &status.active_jobs.to_string())
.item("Total Locations", &status.total_locations.to_string())
.empty_line()
@@ -92,7 +92,7 @@ async fn handle_status_command(
output.error(Message::Error("Unexpected response from daemon".to_string()))?;
}
}
Ok(())
}
@@ -209,7 +209,7 @@ fn format_log_line(line: &str, output: &CliOutput) -> String {
if !output.use_color() {
return line.to_string();
}
use owo_colors::OwoColorize;
if line.contains("ERROR") {
line.red().to_string()

View File

@@ -1,7 +1,7 @@
//! Volume CLI commands
use crate::infrastructure::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infra::cli::daemon::{DaemonClient, DaemonCommand, DaemonResponse};
use crate::infra::cli::output::{CliOutput, Message};
use crate::volume::types::VolumeFingerprint;
use clap::Subcommand;
use comfy_table::Table;

View File

@@ -5,9 +5,9 @@ use std::sync::Arc;
use tokio::sync::oneshot;
use tracing::error;
use crate::infra::cli::daemon::services::StateService;
use crate::infra::cli::daemon::types::{DaemonCommand, DaemonResponse, DaemonStatus};
use crate::Core;
use crate::infrastructure::cli::daemon::services::StateService;
use crate::infrastructure::cli::daemon::types::{DaemonCommand, DaemonResponse, DaemonStatus};
use super::CommandHandler;
@@ -78,4 +78,4 @@ impl CommandHandler for CoreHandler {
DaemonCommand::Ping | DaemonCommand::Shutdown | DaemonCommand::GetStatus
)
}
}
}

View File

@@ -5,9 +5,9 @@ use std::path::PathBuf;
use std::sync::Arc;
use uuid::Uuid;
use crate::infrastructure::actions::builder::{ActionBuildError, ActionBuilder};
use crate::infrastructure::cli::daemon::services::StateService;
use crate::infrastructure::cli::daemon::types::{DaemonCommand, DaemonResponse};
use crate::infra::actions::builder::{ActionBuildError, ActionBuilder};
use crate::infra::cli::daemon::services::StateService;
use crate::infra::cli::daemon::types::{DaemonCommand, DaemonResponse};
use crate::Core;
use super::CommandHandler;
@@ -37,21 +37,21 @@ impl CommandHandler for FileHandler {
let library_id = library.id();
// Create copy options from parameters
let options = crate::operations::files::copy::CopyOptions {
let options = crate::ops::files::copy::CopyOptions {
overwrite,
verify_checksum: verify,
preserve_timestamps,
delete_after_copy: move_files,
move_mode: if move_files {
Some(crate::operations::files::copy::MoveMode::Move)
move_mode: if move_files {
Some(crate::ops::files::copy::MoveMode::Move)
} else {
None
},
copy_method: crate::operations::files::copy::input::CopyMethod::Auto,
copy_method: crate::ops::files::copy::input::CopyMethod::Auto,
};
// Create action directly from URIs
let action = match crate::operations::files::copy::action::FileCopyActionBuilder::from_uris(
let action = match crate::ops::files::copy::action::FileCopyActionBuilder::from_uris(
sources,
destination,
options,
@@ -70,7 +70,7 @@ impl CommandHandler for FileHandler {
Some(action_manager) => {
// Create the full Action enum
let full_action = crate::infrastructure::actions::Action::FileCopy {
let full_action = crate::infra::actions::Action::FileCopy {
library_id,
action,
};
@@ -152,7 +152,7 @@ impl CommandHandler for FileHandler {
};
browse_entries.push(
crate::infrastructure::cli::daemon::types::common::BrowseEntry {
crate::infra::cli::daemon::types::common::BrowseEntry {
name: file_name,
path: file_path,
is_dir,
@@ -208,9 +208,9 @@ impl CommandHandler for FileHandler {
// Dispatch LocationIndexAction for each location
for location in locations {
let action = crate::infrastructure::actions::Action::LocationIndex {
let action = crate::infra::actions::Action::LocationIndex {
library_id,
action: crate::operations::locations::index::action::LocationIndexAction {
action: crate::ops::locations::index::action::LocationIndexAction {
location_id: location.id,
mode: location.index_mode.into(),
},
@@ -267,11 +267,11 @@ impl CommandHandler for FileHandler {
match core.context.get_action_manager().await {
Some(action_manager) => {
// Create LocationIndexAction
let action = crate::infrastructure::actions::Action::LocationIndex {
let action = crate::infra::actions::Action::LocationIndex {
library_id,
action: crate::operations::locations::index::action::LocationIndexAction {
action: crate::ops::locations::index::action::LocationIndexAction {
location_id,
mode: crate::operations::indexing::IndexMode::Content,
mode: crate::ops::indexing::IndexMode::Content,
},
};

View File

@@ -4,8 +4,8 @@ use async_trait::async_trait;
use std::sync::Arc;
use uuid::Uuid;
use crate::infrastructure::cli::daemon::services::StateService;
use crate::infrastructure::cli::daemon::types::{DaemonCommand, DaemonResponse, JobInfo};
use crate::infra::cli::daemon::services::StateService;
use crate::infra::cli::daemon::types::{DaemonCommand, DaemonResponse, JobInfo};
use crate::Core;
use super::CommandHandler;
@@ -47,7 +47,7 @@ impl CommandHandler for JobHandler {
// For other statuses, query the database
let status_filter = status.and_then(|s| {
s.parse::<crate::infrastructure::jobs::types::JobStatus>()
s.parse::<crate::infra::jobs::types::JobStatus>()
.ok()
});
@@ -95,8 +95,8 @@ impl CommandHandler for JobHandler {
// Get current library from CLI state
if let Some(library) = state_service.get_current_library(core).await {
let job_manager = library.jobs();
let job_id = crate::infrastructure::jobs::types::JobId(id);
let job_id = crate::infra::jobs::types::JobId(id);
match job_manager.pause_job(job_id).await {
Ok(_) => DaemonResponse::Ok,
Err(e) => DaemonResponse::Error(e.to_string()),
@@ -110,8 +110,8 @@ impl CommandHandler for JobHandler {
// Get current library from CLI state
if let Some(library) = state_service.get_current_library(core).await {
let job_manager = library.jobs();
let job_id = crate::infrastructure::jobs::types::JobId(id);
let job_id = crate::infra::jobs::types::JobId(id);
match job_manager.resume_job(job_id).await {
Ok(_) => DaemonResponse::Ok,
Err(e) => DaemonResponse::Error(e.to_string()),

View File

@@ -6,8 +6,8 @@ use tracing::warn;
use uuid::Uuid;
use crate::Core;
use crate::infrastructure::cli::daemon::services::StateService;
use crate::infrastructure::cli::daemon::types::{
use crate::infra::cli::daemon::services::StateService;
use crate::infra::cli::daemon::types::{
DaemonCommand, DaemonResponse, LibraryInfo,
};
@@ -35,7 +35,7 @@ impl CommandHandler for LibraryHandler {
// Auto-select the newly created library
let library_id = library.id();
let library_path = library.path().to_path_buf();
// Try to set the new library as current
if let Err(e) = state_service
.switch_library(library_id, library_path.clone())
@@ -43,7 +43,7 @@ impl CommandHandler for LibraryHandler {
{
warn!("Failed to auto-select new library: {}", e);
}
DaemonResponse::LibraryCreated {
id: library_id,
name: name.clone(), // Use the name passed in instead of reading from library

View File

@@ -5,9 +5,9 @@ use std::path::PathBuf;
use std::sync::Arc;
use uuid::Uuid;
use crate::infrastructure::cli::daemon::services::StateService;
use crate::infrastructure::cli::daemon::types::{DaemonCommand, DaemonResponse, LocationInfo};
use crate::infrastructure::actions::output::ActionOutput;
use crate::infra::cli::daemon::services::StateService;
use crate::infra::cli::daemon::types::{DaemonCommand, DaemonResponse, LocationInfo};
use crate::infra::actions::output::ActionOutput;
use crate::Core;
use super::CommandHandler;
@@ -33,13 +33,13 @@ impl CommandHandler for LocationHandler {
match core.context.get_action_manager().await {
Some(action_manager) => {
// Create the location add action
let action = crate::infrastructure::actions::Action::LocationAdd {
let action = crate::infra::actions::Action::LocationAdd {
library_id,
action:
crate::operations::locations::add::action::LocationAddAction {
crate::ops::locations::add::action::LocationAddAction {
path: path.clone(),
name,
mode: crate::operations::indexing::IndexMode::Content,
mode: crate::ops::indexing::IndexMode::Content,
},
};
@@ -77,8 +77,8 @@ impl CommandHandler for LocationHandler {
// Get current library from CLI state
if let Some(library) = state_service.get_current_library(core).await {
// For listing, we can directly query the database since it's a read operation
use crate::infrastructure::database::entities;
use crate::operations::indexing::PathResolver;
use crate::infra::database::entities;
use crate::ops::indexing::PathResolver;
use sea_orm::EntityTrait;
match entities::location::Entity::find()
@@ -123,9 +123,9 @@ impl CommandHandler for LocationHandler {
match core.context.get_action_manager().await {
Some(action_manager) => {
// Create the location remove action
let action = crate::infrastructure::actions::Action::LocationRemove {
let action = crate::infra::actions::Action::LocationRemove {
library_id,
action: crate::operations::locations::remove::action::LocationRemoveAction {
action: crate::ops::locations::remove::action::LocationRemoveAction {
location_id: id,
},
};
@@ -155,9 +155,9 @@ impl CommandHandler for LocationHandler {
match core.context.get_action_manager().await {
Some(action_manager) => {
// Create LocationRescanAction
let action = crate::infrastructure::actions::Action::LocationRescan {
let action = crate::infra::actions::Action::LocationRescan {
library_id,
action: crate::operations::locations::rescan::action::LocationRescanAction {
action: crate::ops::locations::rescan::action::LocationRescanAction {
location_id: id,
full_rescan: false,
},

View File

@@ -4,8 +4,8 @@ use async_trait::async_trait;
use std::sync::Arc;
use crate::Core;
use crate::infrastructure::cli::daemon::services::StateService;
use crate::infrastructure::cli::daemon::types::{DaemonCommand, DaemonResponse};
use crate::infra::cli::daemon::services::StateService;
use crate::infra::cli::daemon::types::{DaemonCommand, DaemonResponse};
pub mod core;
pub mod file;

View File

@@ -4,8 +4,8 @@ use async_trait::async_trait;
use std::sync::Arc;
use uuid::Uuid;
use crate::infrastructure::cli::daemon::services::StateService;
use crate::infrastructure::cli::daemon::types::{
use crate::infra::cli::daemon::services::StateService;
use crate::infra::cli::daemon::types::{
ConnectedDeviceInfo, DaemonCommand, DaemonResponse, PairingRequestInfo,
};
use crate::Core;
@@ -95,10 +95,10 @@ impl CommandHandler for NetworkHandler {
match core.context.get_action_manager().await {
Some(action_manager) => {
// Create DeviceRevokeAction
let action = crate::infrastructure::actions::Action::DeviceRevoke {
let action = crate::infra::actions::Action::DeviceRevoke {
library_id,
action:
crate::operations::devices::revoke::action::DeviceRevokeAction {
crate::ops::devices::revoke::action::DeviceRevokeAction {
device_id,
reason: Some("Revoked via CLI".to_string()),
},

View File

@@ -4,9 +4,9 @@ use async_trait::async_trait;
use std::path::PathBuf;
use std::sync::Arc;
use crate::infra::cli::daemon::services::StateService;
use crate::infra::cli::daemon::types::{DaemonCommand, DaemonResponse};
use crate::Core;
use crate::infrastructure::cli::daemon::services::StateService;
use crate::infrastructure::cli::daemon::types::{DaemonCommand, DaemonResponse};
use super::CommandHandler;
@@ -42,4 +42,4 @@ impl CommandHandler for SystemHandler {
fn can_handle(&self, cmd: &DaemonCommand) -> bool {
matches!(cmd, DaemonCommand::SubscribeEvents)
}
}
}

View File

@@ -5,7 +5,7 @@ use std::sync::Arc;
use tracing::debug;
use crate::{
infrastructure::{
infra::{
actions::{manager::ActionManager, Action},
cli::{
commands::VolumeCommands,
@@ -15,7 +15,7 @@ use crate::{
},
},
},
operations::volumes::{
ops::volumes::{
speed_test::action::VolumeSpeedTestAction, track::action::VolumeTrackAction,
untrack::action::VolumeUntrackAction,
},

View File

@@ -3,7 +3,7 @@
//! The daemon runs in the background and handles all core operations.
//! The CLI communicates with it via Unix domain socket (on Unix) or named pipe (on Windows).
use crate::{infrastructure::cli::state::CliState, Core};
use crate::{infra::cli::state::CliState, Core};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
@@ -158,7 +158,7 @@ impl Daemon {
// Emit CoreStarted event to signal daemon is ready
self.core
.events
.emit(crate::infrastructure::events::Event::CoreStarted);
.emit(crate::infra::events::Event::CoreStarted);
// Set up shutdown channel
let (shutdown_tx, mut shutdown_rx) = oneshot::channel();

View File

@@ -1,6 +1,6 @@
//! State management service for the daemon
use crate::infrastructure::cli::state::CliState;
use crate::infra::cli::state::CliState;
use crate::library::Library;
use crate::Core;
use std::path::PathBuf;
@@ -72,4 +72,4 @@ impl StateService {
Ok(())
}
}
}

View File

@@ -3,8 +3,8 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use uuid::Uuid;
use crate::infrastructure::cli::commands::{
LibraryCommands, LocationCommands, JobCommands, FileCommands,
use crate::infra::cli::commands::{
LibraryCommands, LocationCommands, JobCommands, FileCommands,
NetworkCommands, SystemCommands, VolumeCommands
};

View File

@@ -8,7 +8,7 @@ use super::common::{
BrowseEntry, ConnectedDeviceInfo, JobInfo, LibraryInfo, LocationInfo, PairingRequestInfo,
VolumeListItem,
};
use crate::{infrastructure::actions::output::ActionOutput, volume::Volume};
use crate::{infra::actions::output::ActionOutput, volume::Volume};
/// Responses from the daemon
#[derive(Debug, Serialize, Deserialize)]

View File

@@ -6,7 +6,7 @@ pub mod pairing_ui;
pub mod state;
pub mod utils;
use crate::infrastructure::cli::commands::{
use crate::infra::cli::commands::{
daemon::{handle_daemon_command, DaemonCommands},
file::{handle_file_command, FileCommands},
job::{handle_job_command, JobCommands},
@@ -16,7 +16,7 @@ use crate::infrastructure::cli::commands::{
system::{handle_system_command, SystemCommands},
volume::{handle_volume_command, VolumeCommands},
};
use crate::infrastructure::cli::output::{CliOutput, Message};
use crate::infra::cli::output::{CliOutput, Message};
use clap::{Parser, Subcommand};
use std::path::PathBuf;

View File

@@ -272,15 +272,15 @@ pub enum JobStatus {
Cancelled,
}
impl From<crate::infrastructure::jobs::types::JobStatus> for JobStatus {
fn from(status: crate::infrastructure::jobs::types::JobStatus) -> Self {
impl From<crate::infra::jobs::types::JobStatus> for JobStatus {
fn from(status: crate::infra::jobs::types::JobStatus) -> Self {
match status {
crate::infrastructure::jobs::types::JobStatus::Queued => Self::Queued,
crate::infrastructure::jobs::types::JobStatus::Running => Self::Running,
crate::infrastructure::jobs::types::JobStatus::Completed => Self::Completed,
crate::infrastructure::jobs::types::JobStatus::Failed => Self::Failed,
crate::infrastructure::jobs::types::JobStatus::Paused => Self::Paused,
crate::infrastructure::jobs::types::JobStatus::Cancelled => Self::Cancelled,
crate::infra::jobs::types::JobStatus::Queued => Self::Queued,
crate::infra::jobs::types::JobStatus::Running => Self::Running,
crate::infra::jobs::types::JobStatus::Completed => Self::Completed,
crate::infra::jobs::types::JobStatus::Failed => Self::Failed,
crate::infra::jobs::types::JobStatus::Paused => Self::Paused,
crate::infra::jobs::types::JobStatus::Cancelled => Self::Cancelled,
}
}
}

View File

@@ -152,13 +152,13 @@ impl<'a> HelpSection<'a> {
pub fn end(mut self) -> OutputSection<'a> {
if !self.items.is_empty() {
self.parent.lines.push(Line::Empty);
if self.parent.context.use_emoji() {
self.parent.lines.push(Line::Text("💡 Tips:".to_string()));
} else {
self.parent.lines.push(Line::Text("Tips:".to_string()));
}
for item in self.items {
self.parent.lines.push(Line::Text(format!("{}", item)));
}
@@ -175,12 +175,12 @@ impl<'a> HelpSection<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::infrastructure::cli::output::{CliOutput, OutputFormat};
use crate::infra::cli::output::{CliOutput, OutputFormat};
#[test]
fn test_section_builder() {
let (mut output, buffer) = CliOutput::test();
output.section()
.title("Test Section")
.status("Status", "Active")
@@ -189,7 +189,7 @@ mod tests {
.item("Item 2", "Value 2")
.render()
.unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Test Section"));
assert!(result.contains("Status: Active"));
@@ -199,7 +199,7 @@ mod tests {
#[test]
fn test_help_section() {
let (mut output, buffer) = CliOutput::test();
output.section()
.title("Commands")
.help()
@@ -207,7 +207,7 @@ mod tests {
.item("Use 'delete' to remove items")
.render()
.unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Commands"));
assert!(result.contains("Tips:"));

View File

@@ -2,22 +2,22 @@
#[cfg(test)]
mod tests {
use crate::infrastructure::cli::output::*;
use crate::infrastructure::cli::output::messages::*;
use crate::infra::cli::output::*;
use crate::infra::cli::output::messages::*;
use std::path::PathBuf;
use uuid::Uuid;
#[test]
fn test_human_format_basic_messages() {
let (mut output, buffer) = CliOutput::test();
output.success("Operation successful").unwrap();
output.error(Message::Error("Something went wrong".to_string())).unwrap();
output.warning("This is a warning").unwrap();
output.info("Some information").unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Operation successful"));
assert!(result.contains("Something went wrong"));
assert!(result.contains("This is a warning"));
@@ -28,24 +28,24 @@ mod tests {
fn test_json_format() {
use std::sync::{Arc, Mutex};
use super::super::TestWriter;
let buffer = Arc::new(Mutex::new(Vec::new()));
let writer = TestWriter { buffer: buffer.clone() };
let mut output = CliOutput {
context: OutputContext::test(Box::new(writer)),
};
output.context.format = OutputFormat::Json;
output.context.formatter = Box::new(crate::infrastructure::cli::output::formatters::JsonFormatter);
output.context.formatter = Box::new(crate::infra::cli::output::formatters::JsonFormatter);
output.print(Message::LibraryCreated {
name: "Test Library".to_string(),
id: Uuid::new_v4(),
path: PathBuf::from("/test/path"),
}).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
let json: serde_json::Value = serde_json::from_str(&result.trim()).unwrap();
assert_eq!(json["type"], "library_created");
assert_eq!(json["success"], true);
assert_eq!(json["data"]["name"], "Test Library");
@@ -55,23 +55,23 @@ mod tests {
fn test_quiet_format() {
use std::sync::{Arc, Mutex};
use super::super::TestWriter;
let buffer = Arc::new(Mutex::new(Vec::new()));
let writer = TestWriter { buffer: buffer.clone() };
let mut output = CliOutput {
context: OutputContext::test(Box::new(writer)),
};
output.context.format = OutputFormat::Quiet;
// Normal messages should not appear in quiet mode
output.info("This should not appear").unwrap();
output.success("This also should not appear").unwrap();
// Errors should always appear
output.error(Message::Error("This error should appear".to_string())).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(!result.contains("This should not appear"));
assert!(!result.contains("This also should not appear"));
assert!(result.contains("This error should appear"));
@@ -81,7 +81,7 @@ mod tests {
fn test_verbosity_levels() {
use std::sync::{Arc, Mutex};
use super::super::TestWriter;
let buffer = Arc::new(Mutex::new(Vec::new()));
let writer = TestWriter { buffer: buffer.clone() };
let mut output = CliOutput::with_options(
@@ -90,22 +90,22 @@ mod tests {
ColorMode::Never
);
output.context = OutputContext::test(Box::new(writer));
// Normal messages should appear
output.info("Normal info").unwrap();
// Debug messages should not appear at normal verbosity
output.print(Message::Debug("Debug info".to_string())).unwrap();
// Progress messages (verbose level) should not appear
output.print(Message::IndexingProgress {
current: 10,
total: 100,
location: "test".to_string(),
}).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Normal info"));
assert!(!result.contains("Debug info"));
assert!(!result.contains("Indexing"));
@@ -114,16 +114,16 @@ mod tests {
#[test]
fn test_library_messages() {
let (mut output, buffer) = CliOutput::test();
let lib_id = Uuid::new_v4();
output.print(Message::LibraryCreated {
name: "My Library".to_string(),
id: lib_id,
path: PathBuf::from("/home/user/library"),
}).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Library 'My Library' created successfully"));
assert!(result.contains(&lib_id.to_string()));
assert!(result.contains("/home/user/library"));
@@ -132,7 +132,7 @@ mod tests {
#[test]
fn test_device_list() {
let (mut output, buffer) = CliOutput::test();
let devices = vec![
DeviceInfo {
id: "device1".to_string(),
@@ -147,11 +147,11 @@ mod tests {
peer_id: Some("peer123".to_string()),
},
];
output.print(Message::DevicesList { devices }).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Discovered devices"));
assert!(result.contains("My Computer"));
assert!(result.contains("My Phone"));
@@ -162,7 +162,7 @@ mod tests {
#[test]
fn test_section_builder_basic() {
let (mut output, buffer) = CliOutput::test();
output.section()
.title("Test Section")
.status("Version", "1.0.0")
@@ -171,9 +171,9 @@ mod tests {
.text("Some additional text")
.render()
.unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Test Section"));
assert!(result.contains("Version: 1.0.0"));
assert!(result.contains("Status: Running"));
@@ -183,20 +183,20 @@ mod tests {
#[test]
fn test_section_builder_with_table() {
let (mut output, buffer) = CliOutput::test();
let mut table = comfy_table::Table::new();
table.set_header(vec!["ID", "Name", "Status"]);
table.add_row(vec!["1", "Item 1", "Active"]);
table.add_row(vec!["2", "Item 2", "Inactive"]);
output.section()
.title("Items")
.table(table)
.render()
.unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Items"));
assert!(result.contains("ID"));
assert!(result.contains("Name"));
@@ -208,7 +208,7 @@ mod tests {
#[test]
fn test_help_section() {
let (mut output, buffer) = CliOutput::test();
output.section()
.title("Available Commands")
.help()
@@ -217,9 +217,9 @@ mod tests {
.item("delete - Delete an item")
.render()
.unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Available Commands"));
assert!(result.contains("Tips:"));
assert!(result.contains("• create - Create a new item"));
@@ -231,7 +231,7 @@ mod tests {
fn test_progress_messages() {
use std::sync::{Arc, Mutex};
use super::super::TestWriter;
let buffer = Arc::new(Mutex::new(Vec::new()));
let writer = TestWriter { buffer: buffer.clone() };
let mut output = CliOutput::with_options(
@@ -241,21 +241,21 @@ mod tests {
);
output.context = OutputContext::test(Box::new(writer));
output.context.verbosity = VerbosityLevel::Verbose;
output.print(Message::IndexingProgress {
current: 150,
total: 1000,
location: "/home/user/documents".to_string(),
}).unwrap();
output.print(Message::CopyProgress {
current: 5,
total: 10,
current_file: Some("file.txt".to_string()),
}).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Indexing /home/user/documents: 150/1000 files"));
assert!(result.contains("Copying file.txt: 5/10 files"));
}
@@ -263,18 +263,18 @@ mod tests {
#[test]
fn test_pairing_messages() {
let (mut output, buffer) = CliOutput::test();
output.print(Message::PairingCodeGenerated {
code: "ABC123".to_string(),
}).unwrap();
output.print(Message::PairingSuccess {
device_name: "John's Phone".to_string(),
device_id: "device123".to_string(),
}).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Pairing code generated"));
assert!(result.contains("ABC123"));
assert!(result.contains("Successfully paired with John's Phone"));
@@ -283,28 +283,28 @@ mod tests {
#[test]
fn test_job_messages() {
let (mut output, buffer) = CliOutput::test();
let job_id = Uuid::new_v4();
output.print(Message::JobStarted {
id: job_id,
name: "File Copy".to_string(),
}).unwrap();
output.print(Message::JobCompleted {
id: job_id,
name: "File Copy".to_string(),
duration: 42,
}).unwrap();
output.print(Message::JobFailed {
id: job_id,
name: "File Validation".to_string(),
error: "Checksum mismatch".to_string(),
}).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Job started: File Copy"));
assert!(result.contains("Job completed: File Copy (42s)"));
assert!(result.contains("Job failed: File Validation"));
@@ -314,7 +314,7 @@ mod tests {
#[test]
fn test_empty_line_deduplication() {
let (mut output, buffer) = CliOutput::test();
output.section()
.title("Test")
.empty_line()
@@ -323,17 +323,17 @@ mod tests {
.text("Content")
.render()
.unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
let lines: Vec<&str> = result.lines().collect();
// Count empty lines between "Test" and "Content"
let empty_count = lines.iter()
.skip_while(|&&line| !line.contains("Test"))
.take_while(|&&line| !line.contains("Content"))
.filter(|&&line| line.trim().is_empty())
.count();
assert_eq!(empty_count, 1, "Should have exactly one empty line");
}
@@ -347,7 +347,7 @@ mod tests {
);
// In test environment, should detect no color support
assert!(!ctx.use_color());
// Test always mode
let ctx = OutputContext::with_options(
OutputFormat::Human,
@@ -355,7 +355,7 @@ mod tests {
ColorMode::Always
);
assert!(ctx.use_color());
// Test never mode
let ctx = OutputContext::with_options(
OutputFormat::Human,
@@ -368,7 +368,7 @@ mod tests {
#[test]
fn test_daemon_status_message() {
let (mut output, buffer) = CliOutput::test();
output.print(Message::DaemonStatus {
version: "2.0.0".to_string(),
uptime: 3600,
@@ -382,9 +382,9 @@ mod tests {
}
],
}).unwrap();
let result = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(result.contains("Spacedrive Daemon Status"));
assert!(result.contains("Version: 2.0.0"));
assert!(result.contains("Instance: default"));

Some files were not shown because too many files have changed in this diff Show More