mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-10-20 08:04:33 -04:00
more refactor
This commit is contained in:
1 parent
8f03cc6d4c
commit
a8a2c732ef
287 files changed
+4500
-5975
No files matched your search
Generated
+1752
-3620
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -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
@@ -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 }
|
||||
|
||||
+9
-5
@@ -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
@@ -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.
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
+195
-191
@@ -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.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
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.
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,
|
||||
File renamed without changes.
@@ -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.
File renamed without changes.
File renamed without changes.
+5
-5
@@ -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 {
|
||||
+4
-4
@@ -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,
|
||||
+5
-5
@@ -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,
|
||||
File renamed without changes.
+19
-19
@@ -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?
|
||||
+8
-8
@@ -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()
|
||||
+2
-2
@@ -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;
|
||||
File renamed without changes.
File renamed without changes.
+3
-3
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-15
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
+7
-7
@@ -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()),
|
||||
+4
-4
@@ -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
|
||||
+12
-12
@@ -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,
|
||||
},
|
||||
+2
-2
@@ -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
-4
@@ -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()),
|
||||
},
|
||||
+3
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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();
|
||||
File renamed without changes.
File renamed without changes.
+2
-2
@@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
};
|
||||
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -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;
|
||||
|
||||
File renamed without changes.
File renamed without changes.
+8
-8
@@ -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.
+7
-7
@@ -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.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
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
Reference in new issue
Block a user