mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-04 13:26:00 -04:00
more refactor
This commit is contained in:
BIN
Cargo.lock
generated
BIN
Cargo.lock
generated
Binary file not shown.
@@ -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
158
Cargo.toml.bak
Normal 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
|
||||
@@ -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 }
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user