more refactor

This commit is contained in:
Jamie Pine committed 2025-09-07 01:43:57 -04:00
1 parent 8f03cc6d4c
commit a8a2c732ef
287 files changed
+4500 -5975

No files matched your search

Generated
+1752 -3620
View File
File diff suppressed because it is too large. Load diff
+1 -1
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
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
-2
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 }
+9 -5
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"
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4 -6
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);
File renamed without changes.
File renamed without changes.
File renamed without changes.
+13 -13
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
+3 -3
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())
}
+195 -191
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,
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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
File renamed without changes.
@@ -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())
}
File renamed without changes.
@@ -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)
File renamed without changes.
File renamed without changes.
@@ -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,
@@ -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());
}
File renamed without changes.
@@ -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(_) => {
@@ -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,
@@ -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 {
@@ -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,
@@ -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,
@@ -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?
@@ -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()
@@ -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;
@@ -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
)
}
}
}
@@ -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,
},
};
@@ -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()),
@@ -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
@@ -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,
},
@@ -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;
@@ -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()),
},
@@ -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)
}
}
}
@@ -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,
},
@@ -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();
@@ -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(())
}
}
}
@@ -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
};
@@ -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)]
@@ -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;
@@ -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,
}
}
}
File renamed without changes.
@@ -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:"));
@@ -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"));
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 287 files, more files were not shown because too many files have changed in this diff. Show more