adding comments, moving technical documentation, improve Dockerfile, delete unuseful files
This commit is contained in:
@@ -1,55 +0,0 @@
|
|||||||
# OxiCloud Development Guide
|
|
||||||
|
|
||||||
## Build Commands
|
|
||||||
```bash
|
|
||||||
# Core development workflow
|
|
||||||
cargo build # Build the project
|
|
||||||
cargo run # Run the project locally (server at http://127.0.0.1:8085)
|
|
||||||
cargo check # Quick check for compilation errors without building
|
|
||||||
|
|
||||||
# Testing commands
|
|
||||||
cargo test # Run all tests
|
|
||||||
cargo test -- --nocapture # Run tests with output displayed
|
|
||||||
cargo test <test_name> # Run a specific test (e.g., cargo test file_service)
|
|
||||||
cargo test domain::entities::file::tests::test_create_file # Run a specific test function
|
|
||||||
RUST_LOG=debug cargo test # Run tests with debug-level logging
|
|
||||||
RUST_LOG=trace cargo test # Run tests with trace-level logging
|
|
||||||
|
|
||||||
# Code quality tools
|
|
||||||
cargo clippy # Run linter to catch common mistakes
|
|
||||||
cargo clippy --fix # Fix auto-fixable linting issues
|
|
||||||
cargo fmt --check # Check code formatting without changing files
|
|
||||||
cargo fmt # Format code according to Rust conventions
|
|
||||||
|
|
||||||
# Debugging
|
|
||||||
RUST_LOG=debug cargo run # Run with detailed logging for debugging
|
|
||||||
RUST_BACKTRACE=1 cargo run # Run with full backtrace for better error diagnostics
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code Style Guidelines
|
|
||||||
- **Architecture**: Follow Clean Architecture with clear layer separation (domain → application → infrastructure → interfaces)
|
|
||||||
- **Naming**: Use `snake_case` for files, modules, functions, variables; `PascalCase` for types/structs/enums; getters without `get_` prefix
|
|
||||||
- **Modules**: Use mod.rs files for explicit exports with visibility modifiers (pub, pub(crate))
|
|
||||||
- **Error Handling**: Use Result<T, E> with thiserror for custom error types; propagate errors with ? operator; include context in error messages
|
|
||||||
- **Documentation**: Document public APIs with /// doc comments, explain "why" not "what"; both English and Spanish comments are acceptable
|
|
||||||
- **Imports**: Group imports: 1) std, 2) external crates, 3) internal modules (with blank lines between)
|
|
||||||
- **Async**: Use async-trait for repository interfaces; handle futures with .await and tokio runtime; implement timeouts for I/O operations
|
|
||||||
- **Testing**: Write unit tests in the same file as implementation (bottom of file, in a tests module with #[cfg(test)])
|
|
||||||
- **Dependencies**: Use axum for web API, tower-http for middleware, serde for serialization; share dependencies with Arc
|
|
||||||
- **Logging**: Use tracing with appropriate levels (debug, info, warn, error) and structured contexts for detailed diagnostics
|
|
||||||
- **Repository Pattern**: Define interfaces in domain layer, implement in infrastructure layer; use traits with dynamic dispatch (Box<dyn Trait>)
|
|
||||||
- **I18n**: Store translations in JSON files under static/locales/, use i18n service for text lookups
|
|
||||||
- **Type Safety**: Prefer strong typing with domain-specific types over primitive types; validate at construction time
|
|
||||||
- **Error Messages**: Provide clear, actionable error messages that help diagnose the issue
|
|
||||||
- **Immutability**: Prefer immutable data structures; use with_* methods to return modified copies rather than mutating in place
|
|
||||||
- **Performance**: Implement caching with proper invalidation; use parallel processing for large file operations; optimize based on file sizes
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
OxiCloud is a NextCloud-like file storage system built in Rust with a focus on performance and security. It provides a clean REST API and web interface for file management using a layered architecture approach:
|
|
||||||
|
|
||||||
- **Domain Layer**: Core business logic and entities (src/domain/)
|
|
||||||
- **Application Layer**: Use cases and application services (src/application/)
|
|
||||||
- **Infrastructure Layer**: External systems and implementations (src/infrastructure/)
|
|
||||||
- **Interfaces Layer**: API and web controllers (src/interfaces/)
|
|
||||||
|
|
||||||
The roadmap in TODO-LIST.md outlines planned features including enhanced folder support, file previews, user authentication, sharing, and a sync client.
|
|
||||||
Generated
+9
-9
@@ -153,9 +153,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.8.1"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
|
checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum-core",
|
"axum-core",
|
||||||
"axum-macros",
|
"axum-macros",
|
||||||
@@ -189,12 +189,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.5.0"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
|
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-core",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
@@ -1466,9 +1466,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.2"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
@@ -2197,9 +2197,9 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.8"
|
version = "0.5.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ name = "oxicloud"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "file_operations"
|
|
||||||
harness = true
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.1", features = ["multipart", "http1", "tokio", "macros"] }
|
axum = { version = "0.8.1", features = ["multipart", "http1", "tokio", "macros"] }
|
||||||
|
|||||||
+53
-11
@@ -1,23 +1,65 @@
|
|||||||
FROM alpine:3.21.3 AS builder
|
# Stage 1: Builder - compile the application
|
||||||
|
FROM rust:1.82-alpine AS builder
|
||||||
|
|
||||||
COPY . /Oxicloud
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache musl-dev pkgconfig openssl-dev
|
||||||
|
|
||||||
WORKDIR /Oxicloud
|
# Create a non-root user for better security
|
||||||
|
RUN adduser -D -u 10001 oxicloud
|
||||||
|
|
||||||
RUN apk update && \
|
# Create a new empty project and copy only dependency files first
|
||||||
apk add cargo pkgconfig openssl-dev
|
WORKDIR /app
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
|
||||||
|
# Create empty source files to trick cargo into caching dependencies
|
||||||
|
RUN mkdir -p src && \
|
||||||
|
echo "fn main() {}" > src/main.rs && \
|
||||||
|
touch src/lib.rs
|
||||||
|
|
||||||
|
# Build dependencies only (this will be cached if dependencies don't change)
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|
||||||
# /Oxicloud/target/release/oxicloud
|
# Remove the fake source files
|
||||||
|
RUN rm -rf src
|
||||||
|
|
||||||
|
# Copy the actual source code
|
||||||
|
COPY src ./src
|
||||||
|
COPY db ./db
|
||||||
|
|
||||||
|
# Build the actual application
|
||||||
|
RUN cargo build --release && \
|
||||||
|
strip target/release/oxicloud
|
||||||
|
|
||||||
|
# Stage 2: Runtime - only include what's necessary for running
|
||||||
FROM alpine:3.21.3
|
FROM alpine:3.21.3
|
||||||
|
|
||||||
COPY . /Oxicloud
|
# Install runtime dependencies only
|
||||||
|
RUN apk add --no-cache libgcc openssl ca-certificates tzdata && \
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
COPY --from=builder /Oxicloud/target/release/ /Oxicloud
|
# Create a non-root user
|
||||||
COPY --from=builder /usr/lib/libgcc_s.so.1 /usr/lib/libgcc_s.so.1
|
RUN adduser -D -u 10001 oxicloud
|
||||||
|
|
||||||
WORKDIR /Oxicloud
|
# Create app directories with proper permissions
|
||||||
|
WORKDIR /app
|
||||||
|
RUN mkdir -p /app/static /app/storage && \
|
||||||
|
chown -R oxicloud:oxicloud /app
|
||||||
|
|
||||||
CMD ["./oxicloud","--release"]
|
# Copy only the compiled binary from the builder stage
|
||||||
|
COPY --from=builder /app/target/release/oxicloud /app/oxicloud
|
||||||
|
|
||||||
|
# Copy static files and necessary runtime config
|
||||||
|
COPY static ./static
|
||||||
|
COPY db ./db
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
RUN chown -R oxicloud:oxicloud /app
|
||||||
|
|
||||||
|
# Set the user to run the application
|
||||||
|
USER oxicloud
|
||||||
|
|
||||||
|
# Expose the port the application runs on
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Run the binary
|
||||||
|
CMD ["./oxicloud", "--release"]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="static/oxicloud-logo.svg" alt="OxiCloud" width="400" />
|
<img src="static/oxicloud-logo.svg" alt="OxiCloud" width="375" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|||||||
+3
-3
@@ -38,9 +38,9 @@ This document contains the task list for the development of OxiCloud, a minimali
|
|||||||
## Phase 2: Authentication and Multi-User
|
## Phase 2: Authentication and Multi-User
|
||||||
|
|
||||||
### User System
|
### User System
|
||||||
- [ ] Design data model for users
|
- [x] Design data model for users
|
||||||
- [ ] Implement user registration
|
- [ ] Implement user registration
|
||||||
- [ ] Create login system
|
- [x] Create login system
|
||||||
- [ ] Add user profile page
|
- [ ] Add user profile page
|
||||||
- [ ] Implement password recovery
|
- [ ] Implement password recovery
|
||||||
- [ ] Separate storage by user
|
- [ ] Separate storage by user
|
||||||
@@ -63,7 +63,7 @@ This document contains the task list for the development of OxiCloud, a minimali
|
|||||||
## Phase 3: Collaboration Features
|
## Phase 3: Collaboration Features
|
||||||
|
|
||||||
### File Sharing
|
### File Sharing
|
||||||
- [ ] Implement shared link generation
|
- [x] Implement shared link generation
|
||||||
- [ ] Add permission configuration for links
|
- [ ] Add permission configuration for links
|
||||||
- [ ] Implement password protection for links
|
- [ ] Implement password protection for links
|
||||||
- [ ] Add expiration dates for shared links
|
- [ ] Add expiration dates for shared links
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Definir variables de conexión por defecto
|
|
||||||
DB_HOST=${PGHOST:-"localhost"}
|
|
||||||
DB_PORT=${PGPORT:-"5432"}
|
|
||||||
DB_USER=${PGUSER:-"postgres"}
|
|
||||||
DB_PASS=${PGPASSWORD:-"postgres"}
|
|
||||||
DB_NAME=${PGDATABASE:-"postgres"}
|
|
||||||
|
|
||||||
# Intentar usar variables de entorno de OxiCloud si están definidas
|
|
||||||
if [ -n "$OXICLOUD_DB_CONNECTION" ]; then
|
|
||||||
# Parse postgres:// connection string
|
|
||||||
if [[ $OXICLOUD_DB_CONNECTION =~ postgres://([^:]+):([^@]+)@([^:]+):([0-9]+)/([^?]+) ]]; then
|
|
||||||
DB_USER="${BASH_REMATCH[1]}"
|
|
||||||
DB_PASS="${BASH_REMATCH[2]}"
|
|
||||||
DB_HOST="${BASH_REMATCH[3]}"
|
|
||||||
DB_PORT="${BASH_REMATCH[4]}"
|
|
||||||
DB_NAME="${BASH_REMATCH[5]}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Applying database migrations..."
|
|
||||||
echo "Using database: postgres://$DB_USER:***@$DB_HOST:$DB_PORT/$DB_NAME"
|
|
||||||
|
|
||||||
# Exportar variable PGPASSWORD para psql
|
|
||||||
export PGPASSWORD="$DB_PASS"
|
|
||||||
|
|
||||||
# Ejecutar el script SQL de migración
|
|
||||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f fix-userrole.sql
|
|
||||||
|
|
||||||
# Comprobar si fue exitoso
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "Migration applied successfully!"
|
|
||||||
else
|
|
||||||
echo "Error applying migration."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Database is now ready for use."
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# This script is used to apply all migrations in order
|
|
||||||
# It can be run manually or as part of container initialization
|
|
||||||
|
|
||||||
# Determine run mode
|
|
||||||
if [ -z "$POSTGRES_DB" ]; then
|
|
||||||
# Manual mode - script was run from command line
|
|
||||||
|
|
||||||
# Verify we are in the right environment
|
|
||||||
if [ ! -f "Cargo.toml" ]; then
|
|
||||||
echo "Error: This script must be run from the OxiCloud project root directory"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if postgres container is running
|
|
||||||
POSTGRES_ID=$(docker-compose ps -q postgres 2>/dev/null)
|
|
||||||
if [ -z "$POSTGRES_ID" ]; then
|
|
||||||
echo "Error: PostgreSQL container is not running. Start it with 'docker-compose up -d postgres'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set parameters for manual mode
|
|
||||||
DB_NAME="oxicloud"
|
|
||||||
MIGRATIONS_DIR="./migrations"
|
|
||||||
MIGRATIONS_CMD="docker-compose exec -T postgres psql -U postgres"
|
|
||||||
else
|
|
||||||
# Auto mode - script is running inside postgres container during initialization
|
|
||||||
echo "Running in PostgreSQL container initialization mode"
|
|
||||||
|
|
||||||
# Set parameters for auto mode
|
|
||||||
DB_NAME="$POSTGRES_DB"
|
|
||||||
MIGRATIONS_DIR="/docker-entrypoint-initdb.d/migrations"
|
|
||||||
MIGRATIONS_CMD="psql -U $POSTGRES_USER"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Applying migrations from $MIGRATIONS_DIR to database $DB_NAME"
|
|
||||||
|
|
||||||
# Ensure schema is created (fallback)
|
|
||||||
$MIGRATIONS_CMD -d "$DB_NAME" -c "CREATE SCHEMA IF NOT EXISTS auth;" 2>/dev/null
|
|
||||||
|
|
||||||
# Get each SQL file from migrations directory in alphabetical order
|
|
||||||
for sql_file in $(find $MIGRATIONS_DIR -name "*.sql" | sort); do
|
|
||||||
echo "Applying migration: $(basename $sql_file)"
|
|
||||||
|
|
||||||
# Run the migration
|
|
||||||
if [ -z "$POSTGRES_DB" ]; then
|
|
||||||
# Manual mode - run through docker-compose
|
|
||||||
docker-compose exec -T postgres psql -U postgres -d "$DB_NAME" -f "/docker-entrypoint-initdb.d/migrations/$(basename $sql_file)"
|
|
||||||
else
|
|
||||||
# Auto mode - run directly
|
|
||||||
psql -U "$POSTGRES_USER" -d "$DB_NAME" -f "$sql_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if migration was successful
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to apply migration $sql_file"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "All migrations applied successfully"
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
//! Benchmarks for file operations in OxiCloud
|
|
||||||
#![feature(test)]
|
|
||||||
|
|
||||||
extern crate test;
|
|
||||||
use test::{black_box, Bencher};
|
|
||||||
|
|
||||||
use oxicloud::application::services::file_service::{FileCreationOptions, FileService};
|
|
||||||
use oxicloud::domain::entities::file::File;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
/// Benchmark for creating files
|
|
||||||
#[bench]
|
|
||||||
fn bench_create_file(b: &mut Bencher) {
|
|
||||||
let rt = Runtime::new().unwrap();
|
|
||||||
|
|
||||||
// Initialize services - this would need adaptation based on actual application structure
|
|
||||||
let file_service = Arc::new(get_file_service());
|
|
||||||
|
|
||||||
let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap();
|
|
||||||
let filename = "benchmark_test.txt";
|
|
||||||
let content = "This is a test file for benchmarking".as_bytes().to_vec();
|
|
||||||
let folder_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
|
|
||||||
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(rt.block_on(async {
|
|
||||||
let options = FileCreationOptions {
|
|
||||||
overwrite: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a file with the service
|
|
||||||
file_service.create_file(
|
|
||||||
user_id,
|
|
||||||
folder_id,
|
|
||||||
filename.to_string(),
|
|
||||||
content.clone(),
|
|
||||||
options,
|
|
||||||
).await
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mock implementation for getting a file service instance for benchmarking
|
|
||||||
fn get_file_service() -> FileService {
|
|
||||||
// This is a simplified mock implementation
|
|
||||||
// In a real benchmark, you would use actual dependencies
|
|
||||||
FileService::new(
|
|
||||||
// Add required repositories/services as needed
|
|
||||||
// For illustration only - will need adaptation for actual implementation
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
POSTGRES_DB: postgres
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
- ./fix-userrole.sql:/docker-entrypoint-initdb.d/fix-userrole.sql
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
-883
@@ -1,883 +0,0 @@
|
|||||||
warning: unused import: `UseCaseFactory`
|
|
||||||
--> src/common/di.rs:20:70
|
|
||||||
|
|
|
||||||
20 | use crate::application::ports::inbound::{FileUseCase, FolderUseCase, UseCaseFactory};
|
|
||||||
| ^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `#[warn(unused_imports)]` on by default
|
|
||||||
|
|
||||||
warning: unused import: `FilePathResolutionPort`
|
|
||||||
--> src/common/di.rs:23:77
|
|
||||||
|
|
|
||||||
23 | use crate::application::ports::storage_ports::{FileReadPort, FileWritePort, FilePathResolutionPort};
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `crate::domain::repositories::folder_repository::FolderRepository`
|
|
||||||
--> src/common/di.rs:29:5
|
|
||||||
|
|
|
||||||
29 | use crate::domain::repositories::folder_repository::FolderRepository;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unnecessary trailing semicolon
|
|
||||||
--> src/common/di.rs:677:46
|
|
||||||
|
|
|
||||||
677 | struct DummyI18nApplicationService {};
|
|
||||||
| ^ help: remove this semicolon
|
|
||||||
|
|
|
||||||
= note: `#[warn(redundant_semicolons)]` on by default
|
|
||||||
|
|
||||||
warning: unused import: `DateTime`
|
|
||||||
--> src/domain/services/auth_service.rs:4:19
|
|
||||||
|
|
|
||||||
4 | use chrono::{Utc, DateTime};
|
|
||||||
| ^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `UserRole`
|
|
||||||
--> src/domain/services/auth_service.rs:6:43
|
|
||||||
|
|
|
||||||
6 | use crate::domain::entities::user::{User, UserRole};
|
|
||||||
| ^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `UserRole`
|
|
||||||
--> src/application/dtos/user_dto.rs:3:43
|
|
||||||
|
|
|
||||||
3 | use crate::domain::entities::user::{User, UserRole};
|
|
||||||
| ^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused imports: `Path` and `middleware`
|
|
||||||
--> src/interfaces/api/handlers/auth_handler.rs:5:28
|
|
||||||
|
|
|
||||||
5 | extract::{State, Json, Path, Extension},
|
|
||||||
| ^^^^
|
|
||||||
...
|
|
||||||
8 | middleware,
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused imports: `AuthResponseDto` and `UserDto`
|
|
||||||
--> src/interfaces/api/handlers/auth_handler.rs:13:28
|
|
||||||
|
|
|
||||||
13 | LoginDto, RegisterDto, UserDto, ChangePasswordDto, RefreshTokenDto, AuthResponseDto
|
|
||||||
| ^^^^^^^ ^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `middleware`
|
|
||||||
--> src/interfaces/api/routes.rs:6:5
|
|
||||||
|
|
|
||||||
6 | middleware,
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `crate::interfaces::middleware::auth::auth_middleware`
|
|
||||||
--> src/interfaces/api/routes.rs:13:5
|
|
||||||
|
|
|
||||||
13 | use crate::interfaces::middleware::auth::auth_middleware;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `std::path::PathBuf`
|
|
||||||
--> src/interfaces/web/mod.rs:7:5
|
|
||||||
|
|
|
||||||
7 | use std::path::PathBuf;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused imports: `FromRequestParts`, `RequestPartsExt`, `body::Body`, and `request::Parts`
|
|
||||||
--> src/interfaces/middleware/auth.rs:3:31
|
|
||||||
|
|
|
||||||
3 | extract::{State, Request, FromRequestParts},
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
4 | http::{StatusCode, request::Parts, HeaderMap, header},
|
|
||||||
| ^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
7 | body::Body,
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
8 | RequestPartsExt,
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `async_trait::async_trait`
|
|
||||||
--> src/interfaces/middleware/auth.rs:10:5
|
|
||||||
|
|
|
||||||
10 | use async_trait::async_trait;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `futures::future::BoxFuture`
|
|
||||||
--> src/interfaces/middleware/auth.rs:11:5
|
|
||||||
|
|
|
||||||
11 | use futures::future::BoxFuture;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `crate::common::errors::AppError`
|
|
||||||
--> src/interfaces/middleware/auth.rs:14:5
|
|
||||||
|
|
|
||||||
14 | use crate::common::errors::AppError;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `crate::domain::entities::user::UserRole`
|
|
||||||
--> src/interfaces/middleware/auth.rs:15:5
|
|
||||||
|
|
|
||||||
15 | use crate::domain::entities::user::UserRole;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused variable: `state`
|
|
||||||
--> src/interfaces/middleware/auth.rs:65:11
|
|
||||||
|
|
|
||||||
65 | State(state): State<Arc<AppState>>,
|
|
||||||
| ^^^^^ help: if this is intentional, prefix it with an underscore: `_state`
|
|
||||||
|
|
|
||||||
= note: `#[warn(unused_variables)]` on by default
|
|
||||||
|
|
||||||
warning: unused variable: `token_str`
|
|
||||||
--> src/interfaces/middleware/auth.rs:71:17
|
|
||||||
|
|
|
||||||
71 | if let Some(token_str) = headers
|
|
||||||
| ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_token_str`
|
|
||||||
|
|
||||||
warning: unused import: `crate::application::ports::outbound::IdMappingPort`
|
|
||||||
--> src/infrastructure/repositories/file_fs_repository.rs:19:5
|
|
||||||
|
|
|
||||||
19 | use crate::application::ports::outbound::IdMappingPort;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `crate::application::ports::outbound::IdMappingPort`
|
|
||||||
--> src/infrastructure/repositories/folder_fs_repository.rs:13:5
|
|
||||||
|
|
|
||||||
13 | use crate::application::ports::outbound::IdMappingPort;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused import: `crate::application::ports::outbound::IdMappingPort`
|
|
||||||
--> src/infrastructure/repositories/file_path_resolver.rs:7:5
|
|
||||||
|
|
|
||||||
7 | use crate::application::ports::outbound::IdMappingPort;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused variable: `folder_repository`
|
|
||||||
--> src/application/services/storage_mediator.rs:118:9
|
|
||||||
|
|
|
||||||
118 | folder_repository: Arc<RwLock<Option<Arc<dyn FolderRepository>>>>,
|
|
||||||
| ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_folder_repository`
|
|
||||||
|
|
||||||
warning: unused variable: `folder_id`
|
|
||||||
--> src/infrastructure/repositories/file_fs_read_repository.rs:166:32
|
|
||||||
|
|
|
||||||
166 | async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<File>, DomainError> {
|
|
||||||
| ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_folder_id`
|
|
||||||
|
|
||||||
warning: unused variable: `abs_path`
|
|
||||||
--> src/infrastructure/repositories/file_fs_read_repository.rs:183:13
|
|
||||||
|
|
|
||||||
183 | let abs_path = self.path_resolver.resolve_storage_path(file.storage_path());
|
|
||||||
| ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_abs_path`
|
|
||||||
|
|
||||||
warning: unused variable: `id`
|
|
||||||
--> src/infrastructure/repositories/file_fs_read_repository.rs:190:37
|
|
||||||
|
|
|
||||||
190 | async fn get_file_stream(&self, id: &str) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Sen...
|
|
||||||
| ^^ help: if this is intentional, prefix it with an underscore: `_id`
|
|
||||||
|
|
||||||
warning: unused variable: `result`
|
|
||||||
--> src/infrastructure/repositories/pg/user_pg_repository.rs:49:13
|
|
||||||
|
|
|
||||||
49 | let result = sqlx::query(
|
|
||||||
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_result`
|
|
||||||
|
|
||||||
warning: unused variable: `config`
|
|
||||||
--> src/interfaces/api/routes.rs:144:9
|
|
||||||
|
|
|
||||||
144 | let config = AppConfig::from_env();
|
|
||||||
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_config`
|
|
||||||
|
|
||||||
warning: struct `DummyFilePathResolutionPort` is never constructed
|
|
||||||
--> src/common/di.rs:497:16
|
|
||||||
|
|
|
||||||
497 | struct DummyFilePathResolutionPort;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `#[warn(dead_code)]` on by default
|
|
||||||
|
|
||||||
warning: fields `root_path`, `config`, and `parallel_processor` are never read
|
|
||||||
--> src/infrastructure/repositories/file_fs_read_repository.rs:19:5
|
|
||||||
|
|
|
||||||
18 | pub struct FileFsReadRepository {
|
|
||||||
| -------------------- fields in this struct
|
|
||||||
19 | root_path: PathBuf,
|
|
||||||
| ^^^^^^^^^
|
|
||||||
...
|
|
||||||
22 | config: AppConfig,
|
|
||||||
| ^^^^^^
|
|
||||||
23 | parallel_processor: Option<Arc<ParallelFileProcessor>>,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: fields `root_path`, `storage_mediator`, and `parallel_processor` are never read
|
|
||||||
--> src/infrastructure/repositories/file_fs_write_repository.rs:18:5
|
|
||||||
|
|
|
||||||
17 | pub struct FileFsWriteRepository {
|
|
||||||
| --------------------- fields in this struct
|
|
||||||
18 | root_path: PathBuf,
|
|
||||||
| ^^^^^^^^^
|
|
||||||
...
|
|
||||||
21 | storage_mediator: Arc<dyn StorageMediator>,
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
22 | config: AppConfig,
|
|
||||||
23 | parallel_processor: Option<Arc<ParallelFileProcessor>>,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: method `delete_file_non_blocking` is never used
|
|
||||||
--> src/infrastructure/repositories/file_fs_write_repository.rs:112:14
|
|
||||||
|
|
|
||||||
26 | impl FileFsWriteRepository {
|
|
||||||
| -------------------------- method in this implementation
|
|
||||||
...
|
|
||||||
112 | async fn delete_file_non_blocking(&self, _abs_path: PathBuf) -> FileRepositoryResult<()> {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: `oxicloud` (lib) generated 32 warnings (run `cargo fix --lib -p oxicloud` to apply 16 suggestions)
|
|
||||||
warning: unused import: `ports::inbound::FolderUseCase`
|
|
||||||
--> src/application/mod.rs:7:9
|
|
||||||
|
|
|
||||||
7 | pub use ports::inbound::FolderUseCase;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused imports: `FileManagementUseCase`, `FileRetrievalUseCase`, `FileUploadUseCase`, and `FileUseCaseFactory`
|
|
||||||
--> src/application/mod.rs:8:29
|
|
||||||
|
|
|
||||||
8 | pub use ports::file_ports::{FileUploadUseCase, FileRetrievalUseCase, FileManagementUseCase, FileUseCaseFactory};
|
|
||||||
| ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused imports: `FolderStoragePort` and `IdMappingPort`
|
|
||||||
--> src/application/mod.rs:9:27
|
|
||||||
|
|
|
||||||
9 | pub use ports::outbound::{FolderStoragePort, IdMappingPort};
|
|
||||||
| ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: unused imports: `DirectoryManagementPort`, `FilePathResolutionPort`, `FileReadPort`, `FileWritePort`, and `StorageVerificationPort`
|
|
||||||
--> src/application/mod.rs:10:32
|
|
||||||
|
|
|
||||||
10 | ...ts::{FileReadPort, FileWritePort, FilePathResolutionPort, StorageVerificationPort, DirectoryManagementPort};
|
|
||||||
| ^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: value assigned to `app` is never read
|
|
||||||
--> src/main.rs:285:9
|
|
||||||
|
|
|
||||||
285 | app = app.nest("/api/auth", auth_router);
|
|
||||||
| ^^^
|
|
||||||
|
|
|
||||||
= help: maybe it is overwritten before being read?
|
|
||||||
= note: `#[warn(unused_assignments)]` on by default
|
|
||||||
|
|
||||||
warning: unused variable: `path`
|
|
||||||
--> src/main.rs:580:45
|
|
||||||
|
|
|
||||||
580 | ... let path = entry.path();
|
|
||||||
| ^^^^ help: if this is intentional, prefix it with an underscore: `_path`
|
|
||||||
|
|
||||||
warning: variant `NotImplemented` is never constructed
|
|
||||||
--> src/common/errors.rs:21:5
|
|
||||||
|
|
|
||||||
7 | pub enum ErrorKind {
|
|
||||||
| --------- variant in this enum
|
|
||||||
...
|
|
||||||
21 | NotImplemented,
|
|
||||||
| ^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `ErrorKind` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
|
||||||
= note: `#[warn(dead_code)]` on by default
|
|
||||||
|
|
||||||
warning: associated function `not_implemented` is never used
|
|
||||||
--> src/common/errors.rs:140:12
|
|
||||||
|
|
|
||||||
55 | impl DomainError {
|
|
||||||
| ---------------- associated function in this implementation
|
|
||||||
...
|
|
||||||
140 | pub fn not_implemented<S: Into<String>>(entity_type: &'static str, message: S) -> Self {
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: associated functions `bad_request`, `forbidden`, and `not_found` are never used
|
|
||||||
--> src/common/errors.rs:252:12
|
|
||||||
|
|
|
||||||
243 | impl AppError {
|
|
||||||
| ------------- associated functions in this implementation
|
|
||||||
...
|
|
||||||
252 | pub fn bad_request(message: impl Into<String>) -> Self {
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
260 | pub fn forbidden(message: impl Into<String>) -> Self {
|
|
||||||
| ^^^^^^^^^
|
|
||||||
...
|
|
||||||
264 | pub fn not_found(message: impl Into<String>) -> Self {
|
|
||||||
| ^^^^^^^^^
|
|
||||||
|
|
||||||
warning: fields `file_ttl_ms`, `directory_ttl_ms`, and `max_entries` are never read
|
|
||||||
--> src/common/config.rs:9:9
|
|
||||||
|
|
|
||||||
7 | pub struct CacheConfig {
|
|
||||||
| ----------- fields in this struct
|
|
||||||
8 | /// TTL para entradas de archivos en caché (ms)
|
|
||||||
9 | pub file_ttl_ms: u64,
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
10 | /// TTL para entradas de directorios en caché (ms)
|
|
||||||
11 | pub directory_ttl_ms: u64,
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
12 | /// Máximo número de entradas en caché
|
|
||||||
13 | pub max_entries: usize,
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `CacheConfig` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: methods `file_read_timeout` and `file_delete_timeout` are never used
|
|
||||||
--> src/common/config.rs:63:12
|
|
||||||
|
|
|
||||||
51 | impl TimeoutConfig {
|
|
||||||
| ------------------ methods in this implementation
|
|
||||||
...
|
|
||||||
63 | pub fn file_read_timeout(&self) -> Duration {
|
|
||||||
| ^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
68 | pub fn file_delete_timeout(&self) -> Duration {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: fields `hash_memory_cost` and `hash_time_cost` are never read
|
|
||||||
--> src/common/config.rs:228:9
|
|
||||||
|
|
|
||||||
224 | pub struct AuthConfig {
|
|
||||||
| ---------- fields in this struct
|
|
||||||
...
|
|
||||||
228 | pub hash_memory_cost: u32,
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
229 | pub hash_time_cost: u32,
|
|
||||||
| ^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `AuthConfig` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: field `enable_file_sharing` is never read
|
|
||||||
--> src/common/config.rs:249:9
|
|
||||||
|
|
|
||||||
246 | pub struct FeaturesConfig {
|
|
||||||
| -------------- field in this struct
|
|
||||||
...
|
|
||||||
249 | pub enable_file_sharing: bool,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `FeaturesConfig` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: field `cache` is never read
|
|
||||||
--> src/common/config.rs:274:9
|
|
||||||
|
|
|
||||||
264 | pub struct AppConfig {
|
|
||||||
| --------- field in this struct
|
|
||||||
...
|
|
||||||
274 | pub cache: CacheConfig,
|
|
||||||
| ^^^^^
|
|
||||||
|
|
|
||||||
= note: `AppConfig` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: methods `with_features`, `db_enabled`, and `auth_enabled` are never used
|
|
||||||
--> src/common/config.rs:386:12
|
|
||||||
|
|
|
||||||
307 | impl AppConfig {
|
|
||||||
| -------------- methods in this implementation
|
|
||||||
...
|
|
||||||
386 | pub fn with_features(mut self, features: FeaturesConfig) -> Self {
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
391 | pub fn db_enabled(&self) -> bool {
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
...
|
|
||||||
395 | pub fn auth_enabled(&self) -> bool {
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: fields `core`, `repositories`, and `applications` are never read
|
|
||||||
--> src/common/di.rs:269:9
|
|
||||||
|
|
|
||||||
268 | pub struct AppState {
|
|
||||||
| -------- fields in this struct
|
|
||||||
269 | pub core: CoreServices,
|
|
||||||
| ^^^^
|
|
||||||
270 | pub repositories: RepositoryServices,
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
271 | pub applications: ApplicationServices,
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: struct `DummyFilePathResolutionPort` is never constructed
|
|
||||||
--> src/common/di.rs:497:16
|
|
||||||
|
|
|
||||||
497 | struct DummyFilePathResolutionPort;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `update_storage_used`, `deactivate`, and `activate` are never used
|
|
||||||
--> src/domain/entities/user.rs:209:12
|
|
||||||
|
|
|
||||||
57 | impl User {
|
|
||||||
| --------- methods in this implementation
|
|
||||||
...
|
|
||||||
209 | pub fn update_storage_used(&mut self, storage_used_bytes: i64) {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
222 | pub fn deactivate(&mut self) {
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
...
|
|
||||||
228 | pub fn activate(&mut self) {
|
|
||||||
| ^^^^^^^^
|
|
||||||
|
|
||||||
warning: method `revoke` is never used
|
|
||||||
--> src/domain/entities/session.rs:67:12
|
|
||||||
|
|
|
||||||
17 | impl Session {
|
|
||||||
| ------------ method in this implementation
|
|
||||||
...
|
|
||||||
67 | pub fn revoke(&mut self) {
|
|
||||||
| ^^^^^^
|
|
||||||
|
|
||||||
warning: variants `ValidationError`, `Timeout`, and `OperationNotAllowed` are never constructed
|
|
||||||
--> src/domain/repositories/user_repository.rs:17:5
|
|
||||||
|
|
|
||||||
6 | pub enum UserRepositoryError {
|
|
||||||
| ------------------- variants in this enum
|
|
||||||
...
|
|
||||||
17 | ValidationError(String),
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
20 | Timeout(String),
|
|
||||||
| ^^^^^^^
|
|
||||||
...
|
|
||||||
23 | OperationNotAllowed(String),
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `UserRepositoryError` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: methods `update_last_login`, `set_user_active_status`, `change_role`, and `delete_user` are never used
|
|
||||||
--> src/domain/repositories/user_repository.rs:75:14
|
|
||||||
|
|
|
||||||
55 | pub trait UserRepository: Send + Sync + 'static {
|
|
||||||
| -------------- methods in this trait
|
|
||||||
...
|
|
||||||
75 | async fn update_last_login(&self, user_id: &str) -> UserRepositoryResult<()>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
81 | async fn set_user_active_status(&self, user_id: &str, active: bool) -> UserRepositoryResult<()>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
87 | async fn change_role(&self, user_id: &str, role: UserRole) -> UserRepositoryResult<()>;
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
90 | async fn delete_user(&self, user_id: &str) -> UserRepositoryResult<()>;
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: variant `Timeout` is never constructed
|
|
||||||
--> src/domain/repositories/session_repository.rs:14:5
|
|
||||||
|
|
|
||||||
6 | pub enum SessionRepositoryError {
|
|
||||||
| ---------------------- variant in this enum
|
|
||||||
...
|
|
||||||
14 | Timeout(String),
|
|
||||||
| ^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `SessionRepositoryError` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: methods `get_session_by_id`, `get_sessions_by_user_id`, and `delete_expired_sessions` are never used
|
|
||||||
--> src/domain/repositories/session_repository.rs:42:14
|
|
||||||
|
|
|
||||||
37 | pub trait SessionRepository: Send + Sync + 'static {
|
|
||||||
| ----------------- methods in this trait
|
|
||||||
...
|
|
||||||
42 | async fn get_session_by_id(&self, id: &str) -> SessionRepositoryResult<Session>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
48 | async fn get_sessions_by_user_id(&self, user_id: &str) -> SessionRepositoryResult<Vec<Session>>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
57 | async fn delete_expired_sessions(&self) -> SessionRepositoryResult<u64>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: variants `InvalidCredentials`, `TokenExpired`, `InvalidToken`, `AccessDenied`, and `OperationNotAllowed` are never constructed
|
|
||||||
--> src/domain/services/auth_service.rs:24:5
|
|
||||||
|
|
|
||||||
22 | pub enum AuthError {
|
|
||||||
| --------- variants in this enum
|
|
||||||
23 | #[error("Credenciales inválidas")]
|
|
||||||
24 | InvalidCredentials,
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
27 | TokenExpired,
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
30 | InvalidToken(String),
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
33 | AccessDenied(String),
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
36 | OperationNotAllowed(String),
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `AuthError` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: method `validate_token` is never used
|
|
||||||
--> src/domain/services/auth_service.rs:107:12
|
|
||||||
|
|
|
||||||
73 | impl AuthService {
|
|
||||||
| ---------------- method in this implementation
|
|
||||||
...
|
|
||||||
107 | pub fn validate_token(&self, token: &str) -> Result<TokenClaims, AuthError> {
|
|
||||||
| ^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: multiple methods are never used
|
|
||||||
--> src/application/ports/inbound.rs:14:14
|
|
||||||
|
|
|
||||||
12 | pub trait FileUseCase: Send + Sync + 'static {
|
|
||||||
| ----------- methods in this trait
|
|
||||||
13 | /// Sube un nuevo archivo desde bytes
|
|
||||||
14 | async fn upload_file(
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
23 | async fn get_file(&self, id: &str) -> Result<FileDto, DomainError>;
|
|
||||||
| ^^^^^^^^
|
|
||||||
...
|
|
||||||
26 | async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<FileDto>, DomainError>;
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
...
|
|
||||||
29 | async fn delete_file(&self, id: &str) -> Result<(), DomainError>;
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
32 | async fn get_file_content(&self, id: &str) -> Result<Vec<u8>, DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
35 | async fn get_file_stream(&self, id: &str) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send...
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
38 | async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> Result<FileDto, DomainError>;
|
|
||||||
| ^^^^^^^^^
|
|
||||||
|
|
||||||
warning: method `get_folder_by_path` is never used
|
|
||||||
--> src/application/ports/inbound.rs:51:14
|
|
||||||
|
|
|
||||||
43 | pub trait FolderUseCase: Send + Sync + 'static {
|
|
||||||
| ------------- method in this trait
|
|
||||||
...
|
|
||||||
51 | async fn get_folder_by_path(&self, path: &str) -> Result<FolderDto, DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: trait `UseCaseFactory` is never used
|
|
||||||
--> src/application/ports/inbound.rs:74:11
|
|
||||||
|
|
|
||||||
74 | pub trait UseCaseFactory {
|
|
||||||
| ^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `resolve_path`, `ensure_directory`, `file_exists`, and `directory_exists` are never used
|
|
||||||
--> src/application/ports/outbound.rs:15:8
|
|
||||||
|
|
|
||||||
13 | pub trait StoragePort: Send + Sync + 'static {
|
|
||||||
| ----------- methods in this trait
|
|
||||||
14 | /// Resuelve una ruta de dominio a una ruta física
|
|
||||||
15 | fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf;
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
18 | async fn ensure_directory(&self, storage_path: &StoragePath) -> Result<(), DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
21 | async fn file_exists(&self, storage_path: &StoragePath) -> Result<bool, DomainError>;
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
24 | async fn directory_exists(&self, storage_path: &StoragePath) -> Result<bool, DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: method `get_file_path` is never used
|
|
||||||
--> src/application/ports/outbound.rs:58:14
|
|
||||||
|
|
|
||||||
29 | pub trait FileStoragePort: Send + Sync + 'static {
|
|
||||||
| --------------- method in this trait
|
|
||||||
...
|
|
||||||
58 | async fn get_file_path(&self, id: &str) -> Result<StoragePath, DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `folder_exists` and `get_folder_path` are never used
|
|
||||||
--> src/application/ports/outbound.rs:95:14
|
|
||||||
|
|
|
||||||
63 | pub trait FolderStoragePort: Send + Sync + 'static {
|
|
||||||
| ----------------- methods in this trait
|
|
||||||
...
|
|
||||||
95 | async fn folder_exists(&self, storage_path: &StoragePath) -> Result<bool, DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
98 | async fn get_folder_path(&self, id: &str) -> Result<StoragePath, DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: method `upload_file` is never used
|
|
||||||
--> src/application/ports/file_ports.rs:13:14
|
|
||||||
|
|
|
||||||
11 | pub trait FileUploadUseCase: Send + Sync + 'static {
|
|
||||||
| ----------------- method in this trait
|
|
||||||
12 | /// Sube un nuevo archivo desde bytes
|
|
||||||
13 | async fn upload_file(
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `get_file`, `list_files`, `get_file_content`, and `get_file_stream` are never used
|
|
||||||
--> src/application/ports/file_ports.rs:26:14
|
|
||||||
|
|
|
||||||
24 | pub trait FileRetrievalUseCase: Send + Sync + 'static {
|
|
||||||
| -------------------- methods in this trait
|
|
||||||
25 | /// Obtiene un archivo por su ID
|
|
||||||
26 | async fn get_file(&self, id: &str) -> Result<FileDto, DomainError>;
|
|
||||||
| ^^^^^^^^
|
|
||||||
...
|
|
||||||
29 | async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<FileDto>, DomainError>;
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
...
|
|
||||||
32 | async fn get_file_content(&self, id: &str) -> Result<Vec<u8>, DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
35 | async fn get_file_stream(&self, id: &str) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send...
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `move_file` and `delete_file` are never used
|
|
||||||
--> src/application/ports/file_ports.rs:42:14
|
|
||||||
|
|
|
||||||
40 | pub trait FileManagementUseCase: Send + Sync + 'static {
|
|
||||||
| --------------------- methods in this trait
|
|
||||||
41 | /// Mueve un archivo a otra carpeta
|
|
||||||
42 | async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> Result<FileDto, DomainError>;
|
|
||||||
| ^^^^^^^^^
|
|
||||||
...
|
|
||||||
45 | async fn delete_file(&self, id: &str) -> Result<(), DomainError>;
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `create_file_upload_use_case`, `create_file_retrieval_use_case`, and `create_file_management_use_case` are never used
|
|
||||||
--> src/application/ports/file_ports.rs:50:8
|
|
||||||
|
|
|
||||||
49 | pub trait FileUseCaseFactory: Send + Sync + 'static {
|
|
||||||
| ------------------ methods in this trait
|
|
||||||
50 | fn create_file_upload_use_case(&self) -> Arc<dyn FileUploadUseCase>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
51 | fn create_file_retrieval_use_case(&self) -> Arc<dyn FileRetrievalUseCase>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
52 | fn create_file_management_use_case(&self) -> Arc<dyn FileManagementUseCase>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `get_file_path` and `resolve_path` are never used
|
|
||||||
--> src/application/ports/storage_ports.rs:49:14
|
|
||||||
|
|
|
||||||
47 | pub trait FilePathResolutionPort: Send + Sync + 'static {
|
|
||||||
| ---------------------- methods in this trait
|
|
||||||
48 | /// Obtiene la ruta de almacenamiento de un archivo
|
|
||||||
49 | async fn get_file_path(&self, id: &str) -> Result<StoragePath, DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
52 | fn resolve_path(&self, storage_path: &StoragePath) -> PathBuf;
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: trait `StorageVerificationPort` is never used
|
|
||||||
--> src/application/ports/storage_ports.rs:57:11
|
|
||||||
|
|
|
||||||
57 | pub trait StorageVerificationPort: Send + Sync + 'static {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: trait `DirectoryManagementPort` is never used
|
|
||||||
--> src/application/ports/storage_ports.rs:67:11
|
|
||||||
|
|
|
||||||
67 | pub trait DirectoryManagementPort: Send + Sync + 'static {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `update_storage_usage`, `list_users`, and `change_password` are never used
|
|
||||||
--> src/application/ports/auth_ports.rs:24:14
|
|
||||||
|
|
|
||||||
7 | pub trait UserStoragePort: Send + Sync + 'static {
|
|
||||||
| --------------- methods in this trait
|
|
||||||
...
|
|
||||||
24 | async fn update_storage_usage(&self, user_id: &str, usage_bytes: i64) -> Result<(), DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
27 | async fn list_users(&self, limit: i64, offset: i64) -> Result<Vec<User>, DomainError>;
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
...
|
|
||||||
30 | async fn change_password(&self, user_id: &str, password_hash: &str) -> Result<(), DomainError>;
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: associated function `new_stub` is never used
|
|
||||||
--> src/application/services/file_service.rs:83:12
|
|
||||||
|
|
|
||||||
76 | impl FileService {
|
|
||||||
| ---------------- associated function in this implementation
|
|
||||||
...
|
|
||||||
83 | pub fn new_stub() -> impl FileUseCase {
|
|
||||||
| ^^^^^^^^
|
|
||||||
|
|
||||||
warning: associated function `new_stub` is never used
|
|
||||||
--> src/application/services/folder_service.rs:22:12
|
|
||||||
|
|
|
||||||
15 | impl FolderService {
|
|
||||||
| ------------------ associated function in this implementation
|
|
||||||
...
|
|
||||||
22 | pub fn new_stub() -> impl FolderUseCase {
|
|
||||||
| ^^^^^^^^
|
|
||||||
|
|
||||||
warning: multiple methods are never used
|
|
||||||
--> src/application/services/storage_mediator.rs:69:14
|
|
||||||
|
|
|
||||||
64 | pub trait StorageMediator: Send + Sync + 'static {
|
|
||||||
| --------------- methods in this trait
|
|
||||||
...
|
|
||||||
69 | async fn get_folder_storage_path(&self, folder_id: &str) -> StorageMediatorResult<StoragePath>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
72 | async fn get_folder(&self, folder_id: &str) -> StorageMediatorResult<Folder>;
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
...
|
|
||||||
75 | async fn file_exists_at_path(&self, path: &Path) -> StorageMediatorResult<bool>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
78 | async fn file_exists_at_storage_path(&self, storage_path: &StoragePath) -> StorageMediatorResult<bool>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
81 | async fn folder_exists_at_path(&self, path: &Path) -> StorageMediatorResult<bool>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
84 | async fn folder_exists_at_storage_path(&self, storage_path: &StoragePath) -> StorageMediatorResult<bool>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
93 | async fn ensure_directory(&self, path: &Path) -> StorageMediatorResult<()>;
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
96 | async fn ensure_storage_directory(&self, storage_path: &StoragePath) -> StorageMediatorResult<()>;
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `logout_all` and `list_users` are never used
|
|
||||||
--> src/application/services/auth_application_service.rs:263:18
|
|
||||||
|
|
|
||||||
18 | impl AuthApplicationService {
|
|
||||||
| --------------------------- methods in this implementation
|
|
||||||
...
|
|
||||||
263 | pub async fn logout_all(&self, user_id: &str) -> Result<u64, DomainError> {
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
...
|
|
||||||
317 | pub async fn list_users(&self, limit: i64, offset: i64) -> Result<Vec<UserDto>, DomainError> {
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: variant `Unavailable` is never constructed
|
|
||||||
--> src/infrastructure/repositories/file_metadata_manager.rs:26:5
|
|
||||||
|
|
|
||||||
18 | pub enum MetadataError {
|
|
||||||
| ------------- variant in this enum
|
|
||||||
...
|
|
||||||
26 | Unavailable(String),
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `MetadataError` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: methods `invalidate` and `invalidate_directory` are never used
|
|
||||||
--> src/infrastructure/repositories/file_metadata_manager.rs:158:18
|
|
||||||
|
|
|
||||||
39 | impl FileMetadataManager {
|
|
||||||
| ------------------------ methods in this implementation
|
|
||||||
...
|
|
||||||
158 | pub async fn invalidate(&self, abs_path: &PathBuf) {
|
|
||||||
| ^^^^^^^^^^
|
|
||||||
...
|
|
||||||
163 | pub async fn invalidate_directory(&self, dir_path: &PathBuf) {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: field `storage_mediator` is never read
|
|
||||||
--> src/infrastructure/repositories/file_path_resolver.rs:15:5
|
|
||||||
|
|
|
||||||
13 | pub struct FilePathResolver {
|
|
||||||
| ---------------- field in this struct
|
|
||||||
14 | path_service: Arc<PathService>,
|
|
||||||
15 | storage_mediator: Arc<dyn StorageMediator>,
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: methods `resolve_legacy_path`, `update_path`, `get_or_create_id`, `remove_id`, and `save_changes` are never used
|
|
||||||
--> src/infrastructure/repositories/file_path_resolver.rs:80:12
|
|
||||||
|
|
|
||||||
19 | impl FilePathResolver {
|
|
||||||
| --------------------- methods in this implementation
|
|
||||||
...
|
|
||||||
80 | pub fn resolve_legacy_path(&self, relative_path: &std::path::Path) -> PathBuf {
|
|
||||||
| ^^^^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
91 | pub async fn update_path(&self, id: &str, storage_path: &StoragePath) -> Result<(), FileRepositoryError> {
|
|
||||||
| ^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
97 | pub async fn get_or_create_id(&self, storage_path: &StoragePath) -> Result<String, FileRepositoryError> {
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
103 | pub async fn remove_id(&self, id: &str) -> Result<(), FileRepositoryError> {
|
|
||||||
| ^^^^^^^^^
|
|
||||||
...
|
|
||||||
109 | pub async fn save_changes(&self) -> Result<(), FileRepositoryError> {
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: associated function `new_in_memory` is never used
|
|
||||||
--> src/infrastructure/services/id_mapping_service.rs:100:12
|
|
||||||
|
|
|
||||||
84 | impl IdMappingService {
|
|
||||||
| --------------------- associated function in this implementation
|
|
||||||
...
|
|
||||||
100 | pub fn new_in_memory() -> Self {
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: fields `username`, `email`, and `role` are never read
|
|
||||||
--> src/interfaces/middleware/auth.rs:21:9
|
|
||||||
|
|
|
||||||
19 | pub struct CurrentUser {
|
|
||||||
| ----------- fields in this struct
|
|
||||||
20 | pub id: String,
|
|
||||||
21 | pub username: String,
|
|
||||||
| ^^^^^^^^
|
|
||||||
22 | pub email: String,
|
|
||||||
| ^^^^^
|
|
||||||
23 | pub role: String,
|
|
||||||
| ^^^^
|
|
||||||
|
|
|
||||||
= note: `CurrentUser` has derived impls for the traits `Debug` and `Clone`, but these are intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: variants `TokenNotProvided`, `InvalidToken`, `TokenExpired`, `UserNotFound`, and `AccessDenied` are never constructed
|
|
||||||
--> src/interfaces/middleware/auth.rs:30:5
|
|
||||||
|
|
|
||||||
28 | pub enum AuthError {
|
|
||||||
| --------- variants in this enum
|
|
||||||
29 | #[error("Token no proporcionado")]
|
|
||||||
30 | TokenNotProvided,
|
|
||||||
| ^^^^^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
33 | InvalidToken(String),
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
36 | TokenExpired,
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
39 | UserNotFound,
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
...
|
|
||||||
42 | AccessDenied(String),
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
|
|
|
||||||
= note: `AuthError` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
|
|
||||||
|
|
||||||
warning: function `auth_middleware` is never used
|
|
||||||
--> src/interfaces/middleware/auth.rs:64:14
|
|
||||||
|
|
|
||||||
64 | pub async fn auth_middleware(
|
|
||||||
| ^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: function `require_admin` is never used
|
|
||||||
--> src/interfaces/middleware/auth.rs:94:14
|
|
||||||
|
|
|
||||||
94 | pub async fn require_admin(
|
|
||||||
| ^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
warning: `oxicloud` (bin "oxicloud") generated 83 warnings (31 duplicates) (run `cargo fix --bin "oxicloud"` to apply 4 suggestions)
|
|
||||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
|
|
||||||
Running `target/debug/oxicloud`
|
|
||||||
[2m2025-03-23T17:05:10.052229Z[0m [32m INFO[0m [2moxicloud::infrastructure::services::id_mapping_service[0m[2m:[0m Loaded ID map with 0 entries (version: 0)
|
|
||||||
[2m2025-03-23T17:05:10.052304Z[0m [32m INFO[0m [2moxicloud[0m[2m:[0m ID mapping optimizer initialized with batch processing and caching
|
|
||||||
[2m2025-03-23T17:05:10.052341Z[0m [32m INFO[0m [2moxicloud[0m[2m:[0m Buffer pool initialized with 50 buffers of 256KB each
|
|
||||||
[2m2025-03-23T17:05:10.052362Z[0m [32m INFO[0m [2moxicloud::infrastructure::services::file_system_i18n_service[0m[2m:[0m Loading translations for locale en from "./static/locales/en.json"
|
|
||||||
[2m2025-03-23T17:05:10.052497Z[0m [32m INFO[0m [2moxicloud::infrastructure::services::file_system_i18n_service[0m[2m:[0m Translations loaded for locale en
|
|
||||||
[2m2025-03-23T17:05:10.052506Z[0m [32m INFO[0m [2moxicloud::infrastructure::services::file_system_i18n_service[0m[2m:[0m Loading translations for locale es from "./static/locales/es.json"
|
|
||||||
[2m2025-03-23T17:05:10.052595Z[0m [32m INFO[0m [2moxicloud::infrastructure::services::file_system_i18n_service[0m[2m:[0m Translations loaded for locale es
|
|
||||||
[2m2025-03-23T17:05:10.052602Z[0m [32m INFO[0m [2moxicloud[0m[2m:[0m Compression service initialized with buffer pool support
|
|
||||||
[2m2025-03-23T17:05:10.053417Z[0m [32m INFO[0m [2moxicloud[0m[2m:[0m Preloading common directories to warm up cache...
|
|
||||||
[2m2025-03-23T17:05:10.054362Z[0m [32m INFO[0m [2moxicloud[0m[2m:[0m Preloaded 4 directory entries into cache
|
|
||||||
[2m2025-03-23T17:05:10.054377Z[0m [32m INFO[0m [2moxicloud[0m[2m:[0m Starting OxiCloud server on http://127.0.0.1:8085
|
|
||||||
[2m2025-03-23T17:05:10.054382Z[0m [32m INFO[0m [2moxicloud[0m[2m:[0m Authentication system initialized successfully
|
|
||||||
[2m2025-03-23T17:05:10.054387Z[0m [32m INFO[0m [2moxicloud[0m[2m:[0m Server binding to http://127.0.0.1:8085
|
|
||||||
[2m2025-03-23T17:05:10.054405Z[0m [34mDEBUG[0m [2moxicloud::interfaces::middleware::cache[0m[2m:[0m HttpCache cleanup: removed 0 expired entries
|
|
||||||
|
|
||||||
thread 'main' panicked at src/main.rs:308:47:
|
|
||||||
Failed to bind to address: Os { code: 98, kind: AddrInUse, message: "Address already in use" }
|
|
||||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
||||||
[2m2025-03-23T17:05:10.054424Z[0m [32m INFO[0m [2moxicloud::interfaces::middleware::cache[0m[2m:[0m HTTP Cache cleanup: removed 0, current: 0/0
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::domain::entities::share::{Share, ShareItemType, SharePermissions};
|
use crate::domain::entities::share::{Share, SharePermissions};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ShareDto {
|
pub struct ShareDto {
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ use crate::application::ports::file_ports::FileManagementUseCase;
|
|||||||
use crate::application::ports::storage_ports::FileWritePort;
|
use crate::application::ports::storage_ports::FileWritePort;
|
||||||
use crate::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
|
|
||||||
/// Servicio para operaciones de gestión de archivos
|
/// Service for file management operations
|
||||||
pub struct FileManagementService {
|
pub struct FileManagementService {
|
||||||
file_repository: Arc<dyn FileWritePort>,
|
file_repository: Arc<dyn FileWritePort>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileManagementService {
|
impl FileManagementService {
|
||||||
/// Crea un nuevo servicio de gestión de archivos
|
/// Creates a new file management service
|
||||||
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self {
|
pub fn new(file_repository: Arc<dyn FileWritePort>) -> Self {
|
||||||
Self { file_repository }
|
Self { file_repository }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea un stub para pruebas
|
/// Creates a stub for testing
|
||||||
pub fn default_stub() -> Self {
|
pub fn default_stub() -> Self {
|
||||||
Self {
|
Self {
|
||||||
file_repository: Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub())
|
file_repository: Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub())
|
||||||
@@ -28,15 +28,15 @@ impl FileManagementService {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl FileManagementUseCase for FileManagementService {
|
impl FileManagementUseCase for FileManagementService {
|
||||||
async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> Result<FileDto, DomainError> {
|
async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> Result<FileDto, DomainError> {
|
||||||
tracing::info!("Moviendo archivo con ID: {} a carpeta: {:?}", file_id, folder_id);
|
tracing::info!("Moving file with ID: {} to folder: {:?}", file_id, folder_id);
|
||||||
|
|
||||||
let moved_file = self.file_repository.move_file(file_id, folder_id).await
|
let moved_file = self.file_repository.move_file(file_id, folder_id).await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Error al mover archivo (ID: {}): {}", file_id, e);
|
tracing::error!("Error moving file (ID: {}): {}", file_id, e);
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
tracing::info!("Archivo movido exitosamente: {} (ID: {}) a carpeta: {:?}",
|
tracing::info!("File moved successfully: {} (ID: {}) to folder: {:?}",
|
||||||
moved_file.name(), moved_file.id(), moved_file.folder_id());
|
moved_file.name(), moved_file.id(), moved_file.folder_id());
|
||||||
|
|
||||||
Ok(FileDto::from(moved_file))
|
Ok(FileDto::from(moved_file))
|
||||||
|
|||||||
@@ -19,23 +19,23 @@ use bytes::Bytes;
|
|||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum FileServiceError {
|
pub enum FileServiceError {
|
||||||
/// Returned when a requested file cannot be found
|
/// Returned when a requested file cannot be found
|
||||||
#[error("Archivo no encontrado: {0}")]
|
#[error("File not found: {0}")]
|
||||||
NotFound(String),
|
NotFound(String),
|
||||||
|
|
||||||
/// Returned when a file operation conflicts with existing files
|
/// Returned when a file operation conflicts with existing files
|
||||||
#[error("Archivo ya existe: {0}")]
|
#[error("File already exists: {0}")]
|
||||||
Conflict(String),
|
Conflict(String),
|
||||||
|
|
||||||
/// Returned when file access fails due to permissions or I/O issues
|
/// Returned when file access fails due to permissions or I/O issues
|
||||||
#[error("Error de acceso al archivo: {0}")]
|
#[error("File access error: {0}")]
|
||||||
AccessError(String),
|
AccessError(String),
|
||||||
|
|
||||||
/// Returned when a file path is invalid
|
/// Returned when a file path is invalid
|
||||||
#[error("Ruta de archivo inválida: {0}")]
|
#[error("Invalid file path: {0}")]
|
||||||
InvalidPath(String),
|
InvalidPath(String),
|
||||||
|
|
||||||
/// Generic internal error for unexpected failures
|
/// Generic internal error for unexpected failures
|
||||||
#[error("Error interno: {0}")]
|
#[error("Internal error: {0}")]
|
||||||
InternalError(String),
|
InternalError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ impl From<FileRepositoryError> for FileServiceError {
|
|||||||
FileRepositoryError::AlreadyExists(path) => FileServiceError::Conflict(path),
|
FileRepositoryError::AlreadyExists(path) => FileServiceError::Conflict(path),
|
||||||
FileRepositoryError::InvalidPath(path) => FileServiceError::InvalidPath(path),
|
FileRepositoryError::InvalidPath(path) => FileServiceError::InvalidPath(path),
|
||||||
FileRepositoryError::IoError(e) => FileServiceError::AccessError(e.to_string()),
|
FileRepositoryError::IoError(e) => FileServiceError::AccessError(e.to_string()),
|
||||||
FileRepositoryError::Timeout(msg) => FileServiceError::AccessError(format!("Operación expiró: {}", msg)),
|
FileRepositoryError::Timeout(msg) => FileServiceError::AccessError(format!("Operation timed out: {}", msg)),
|
||||||
_ => FileServiceError::InternalError(err.to_string()),
|
_ => FileServiceError::InternalError(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,16 +213,16 @@ impl FileService {
|
|||||||
|
|
||||||
/// Moves a file to a new folder using filesystem operations directly
|
/// Moves a file to a new folder using filesystem operations directly
|
||||||
pub async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> FileServiceResult<FileDto> {
|
pub async fn move_file(&self, file_id: &str, folder_id: Option<String>) -> FileServiceResult<FileDto> {
|
||||||
tracing::info!("Moviendo archivo con ID: {} a carpeta: {:?}", file_id, folder_id);
|
tracing::info!("Moving file with ID: {} to folder: {:?}", file_id, folder_id);
|
||||||
|
|
||||||
// Usar la implementación eficiente del repositorio que utiliza rename
|
// Use the efficient repository implementation that uses rename
|
||||||
let moved_file = self.file_repository.move_file(file_id, folder_id).await
|
let moved_file = self.file_repository.move_file(file_id, folder_id).await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Error al mover archivo (ID: {}): {}", file_id, e);
|
tracing::error!("Error moving file (ID: {}): {}", file_id, e);
|
||||||
FileServiceError::from(e)
|
FileServiceError::from(e)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
tracing::info!("Archivo movido exitosamente: {} (ID: {}) a carpeta: {:?}",
|
tracing::info!("File moved successfully: {} (ID: {}) to folder: {:?}",
|
||||||
moved_file.name(), moved_file.id(), moved_file.folder_id());
|
moved_file.name(), moved_file.id(), moved_file.folder_id());
|
||||||
|
|
||||||
Ok(FileDto::from(moved_file))
|
Ok(FileDto::from(moved_file))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::{
|
|||||||
application::{
|
application::{
|
||||||
dtos::{
|
dtos::{
|
||||||
pagination::PaginatedResponseDto,
|
pagination::PaginatedResponseDto,
|
||||||
share_dto::{CreateShareDto, ShareDto, SharePermissionsDto, UpdateShareDto},
|
share_dto::{CreateShareDto, ShareDto, UpdateShareDto},
|
||||||
},
|
},
|
||||||
ports::{
|
ports::{
|
||||||
outbound::{FileStoragePort, FolderStoragePort},
|
outbound::{FileStoragePort, FolderStoragePort},
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ impl TrashService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convierte una entidad TrashedItem a un DTO
|
/// Converts a TrashedItem entity to a DTO
|
||||||
fn to_dto(&self, item: TrashedItem) -> TrashedItemDto {
|
fn to_dto(&self, item: TrashedItem) -> TrashedItemDto {
|
||||||
// Calcular days_until_deletion antes de mover item.original_path
|
// Calculate days_until_deletion before moving item.original_path
|
||||||
let days_until_deletion = item.days_until_deletion();
|
let days_until_deletion = item.days_until_deletion();
|
||||||
|
|
||||||
TrashedItemDto {
|
TrashedItemDto {
|
||||||
@@ -72,12 +72,12 @@ impl TrashService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Valida los permisos del usuario sobre un elemento
|
/// Validates user permissions over an item
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn validate_user_ownership(&self, _item_id: &str, _user_id: &str) -> Result<()> {
|
async fn validate_user_ownership(&self, _item_id: &str, _user_id: &str) -> Result<()> {
|
||||||
// Aquí implementaríamos la validación de permisos
|
// Here we would implement permission validation
|
||||||
// Por ahora, simplemente devolvemos Ok ya que no tenemos una implementación completa
|
// For now, we simply return Ok since we don't have a complete
|
||||||
// de permisos por usuario
|
// implementation of user permissions
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ impl TrashService {
|
|||||||
impl TrashUseCase for TrashService {
|
impl TrashUseCase for TrashService {
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn get_trash_items(&self, user_id: &str) -> Result<Vec<TrashedItemDto>> {
|
async fn get_trash_items(&self, user_id: &str) -> Result<Vec<TrashedItemDto>> {
|
||||||
debug!("Obteniendo elementos en papelera para usuario: {}", user_id);
|
debug!("Getting trash items for user: {}", user_id);
|
||||||
|
|
||||||
let user_uuid = Uuid::parse_str(user_id)
|
let user_uuid = Uuid::parse_str(user_id)
|
||||||
.map_err(|e| DomainError::validation_error("User", format!("Invalid user ID: {}", e)))?;
|
.map_err(|e| DomainError::validation_error("User", format!("Invalid user ID: {}", e)))?;
|
||||||
@@ -102,52 +102,52 @@ impl TrashUseCase for TrashService {
|
|||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn move_to_trash(&self, item_id: &str, item_type: &str, user_id: &str) -> Result<()> {
|
async fn move_to_trash(&self, item_id: &str, item_type: &str, user_id: &str) -> Result<()> {
|
||||||
info!("Moviendo a papelera: tipo={}, id={}, usuario={}", item_type, item_id, user_id);
|
info!("Moving to trash: type={}, id={}, user={}", item_type, item_id, user_id);
|
||||||
debug!("User UUID validation: {}", user_id);
|
debug!("User UUID validation: {}", user_id);
|
||||||
|
|
||||||
// Validate user ownership
|
// Validate user ownership
|
||||||
debug!("Validando permisos de usuario");
|
debug!("Validating user permissions");
|
||||||
self.validate_user_ownership(item_id, user_id).await?;
|
self.validate_user_ownership(item_id, user_id).await?;
|
||||||
debug!("Permisos de usuario validados");
|
debug!("User permissions validated");
|
||||||
|
|
||||||
// Parse UUIDs with detailed error handling
|
// Parse UUIDs with detailed error handling
|
||||||
debug!("Validando UUID del item: {}", item_id);
|
debug!("Validating item UUID: {}", item_id);
|
||||||
let item_uuid = match Uuid::parse_str(item_id) {
|
let item_uuid = match Uuid::parse_str(item_id) {
|
||||||
Ok(uuid) => {
|
Ok(uuid) => {
|
||||||
debug!("UUID del item válido: {}", uuid);
|
debug!("Valid item UUID: {}", uuid);
|
||||||
uuid
|
uuid
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("UUID del item inválido: {} - Error: {}", item_id, e);
|
error!("Invalid item UUID: {} - Error: {}", item_id, e);
|
||||||
return Err(DomainError::validation_error("Item", format!("Invalid item ID: {}", e)));
|
return Err(DomainError::validation_error("Item", format!("Invalid item ID: {}", e)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Validando UUID del usuario: {}", user_id);
|
debug!("Validating user UUID: {}", user_id);
|
||||||
let user_uuid = match Uuid::parse_str(user_id) {
|
let user_uuid = match Uuid::parse_str(user_id) {
|
||||||
Ok(uuid) => {
|
Ok(uuid) => {
|
||||||
debug!("UUID del usuario válido: {}", uuid);
|
debug!("Valid user UUID: {}", uuid);
|
||||||
uuid
|
uuid
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("UUID del usuario inválido: {} - Error: {}", user_id, e);
|
error!("Invalid user UUID: {} - Error: {}", user_id, e);
|
||||||
return Err(DomainError::validation_error("User", format!("Invalid user ID: {}", e)));
|
return Err(DomainError::validation_error("User", format!("Invalid user ID: {}", e)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match item_type {
|
match item_type {
|
||||||
"file" => {
|
"file" => {
|
||||||
info!("Procesando archivo para mover a papelera: {}", item_id);
|
info!("Processing file to move to trash: {}", item_id);
|
||||||
|
|
||||||
// Obtener el archivo para verificar que existe y capturar sus datos
|
// Get the file to verify it exists and capture its data
|
||||||
debug!("Obteniendo datos del archivo: {}", item_id);
|
debug!("Getting file data: {}", item_id);
|
||||||
let file = match self.file_repository.get_file_by_id(item_id).await {
|
let file = match self.file_repository.get_file_by_id(item_id).await {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
debug!("Archivo encontrado: {} ({})", file.name(), item_id);
|
debug!("File found: {} ({})", file.name(), item_id);
|
||||||
file
|
file
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error al obtener archivo: {} - {}", item_id, e);
|
error!("Error getting file: {} - {}", item_id, e);
|
||||||
return Err(DomainError::new(
|
return Err(DomainError::new(
|
||||||
ErrorKind::NotFound,
|
ErrorKind::NotFound,
|
||||||
"File",
|
"File",
|
||||||
@@ -157,10 +157,10 @@ impl TrashUseCase for TrashService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let original_path = file.storage_path().to_string();
|
let original_path = file.storage_path().to_string();
|
||||||
debug!("Ruta original del archivo: {}", original_path);
|
debug!("Original file path: {}", original_path);
|
||||||
|
|
||||||
// Crear el elemento de papelera
|
// Create the trash item
|
||||||
debug!("Creando objeto TrashedItem para el archivo");
|
debug!("Creating TrashedItem object for the file");
|
||||||
let trashed_item = TrashedItem::new(
|
let trashed_item = TrashedItem::new(
|
||||||
item_uuid,
|
item_uuid,
|
||||||
user_uuid,
|
user_uuid,
|
||||||
@@ -169,28 +169,28 @@ impl TrashUseCase for TrashService {
|
|||||||
original_path,
|
original_path,
|
||||||
self.retention_days,
|
self.retention_days,
|
||||||
);
|
);
|
||||||
debug!("TrashedItem creado con éxito: {} -> {}", file.name(), trashed_item.id);
|
debug!("TrashedItem created successfully: {} -> {}", file.name(), trashed_item.id);
|
||||||
|
|
||||||
// Primero añadimos a la papelera para registrar el elemento
|
// First add to trash index to register the item
|
||||||
info!("Añadiendo archivo {} a índice de papelera", item_id);
|
info!("Adding file {} to trash index", item_id);
|
||||||
match self.trash_repository.add_to_trash(&trashed_item).await {
|
match self.trash_repository.add_to_trash(&trashed_item).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Archivo añadido al índice de papelera con éxito");
|
debug!("File added to trash index successfully");
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error al añadir archivo al índice de papelera: {}", e);
|
error!("Error adding file to trash index: {}", e);
|
||||||
return Err(DomainError::internal_error("TrashRepository", format!("Failed to add file to trash: {}", e)));
|
return Err(DomainError::internal_error("TrashRepository", format!("Failed to add file to trash: {}", e)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Luego movemos el archivo físicamente a la papelera
|
// Then physically move the file to trash
|
||||||
info!("Moviendo archivo físicamente a la papelera: {}", item_id);
|
info!("Physically moving file to trash: {}", item_id);
|
||||||
match self.file_repository.move_to_trash(item_id).await {
|
match self.file_repository.move_to_trash(item_id).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Archivo movido físicamente a papelera con éxito: {}", item_id);
|
debug!("File physically moved to trash successfully: {}", item_id);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error al mover archivo físicamente a papelera: {} - {}", item_id, e);
|
error!("Error physically moving file to trash: {} - {}", item_id, e);
|
||||||
return Err(DomainError::new(
|
return Err(DomainError::new(
|
||||||
ErrorKind::InternalError,
|
ErrorKind::InternalError,
|
||||||
"File",
|
"File",
|
||||||
@@ -199,11 +199,11 @@ impl TrashUseCase for TrashService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Archivo movido a papelera completamente: {}", item_id);
|
info!("File completely moved to trash: {}", item_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
"folder" => {
|
"folder" => {
|
||||||
// Obtener la carpeta para verificar que existe y capturar sus datos
|
// Get the folder to verify it exists and capture its data
|
||||||
let folder = self.folder_repository.get_folder_by_id(item_id).await
|
let folder = self.folder_repository.get_folder_by_id(item_id).await
|
||||||
.map_err(|e| DomainError::new(
|
.map_err(|e| DomainError::new(
|
||||||
ErrorKind::NotFound,
|
ErrorKind::NotFound,
|
||||||
@@ -213,7 +213,7 @@ impl TrashUseCase for TrashService {
|
|||||||
|
|
||||||
let original_path = folder.storage_path().to_string();
|
let original_path = folder.storage_path().to_string();
|
||||||
|
|
||||||
// Crear el elemento de papelera
|
// Create the trash item
|
||||||
let trashed_item = TrashedItem::new(
|
let trashed_item = TrashedItem::new(
|
||||||
item_uuid,
|
item_uuid,
|
||||||
user_uuid,
|
user_uuid,
|
||||||
@@ -223,7 +223,7 @@ impl TrashUseCase for TrashService {
|
|||||||
self.retention_days,
|
self.retention_days,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Primero añadimos a la papelera para registrar el elemento
|
// First add to trash index to register the item
|
||||||
debug!("Adding folder {} to trash repository", item_id);
|
debug!("Adding folder {} to trash repository", item_id);
|
||||||
match self.trash_repository.add_to_trash(&trashed_item).await {
|
match self.trash_repository.add_to_trash(&trashed_item).await {
|
||||||
Ok(_) => debug!("Successfully added folder to trash repository"),
|
Ok(_) => debug!("Successfully added folder to trash repository"),
|
||||||
@@ -233,7 +233,7 @@ impl TrashUseCase for TrashService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Luego movemos la carpeta físicamente a la papelera
|
// Then physically move the folder to trash
|
||||||
self.folder_repository.move_to_trash(item_id).await
|
self.folder_repository.move_to_trash(item_id).await
|
||||||
.map_err(|e| DomainError::new(
|
.map_err(|e| DomainError::new(
|
||||||
ErrorKind::InternalError,
|
ErrorKind::InternalError,
|
||||||
@@ -241,7 +241,7 @@ impl TrashUseCase for TrashService {
|
|||||||
format!("Error moving folder {} to trash: {}", item_id, e)
|
format!("Error moving folder {} to trash: {}", item_id, e)
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
debug!("Carpeta movida a papelera: {}", item_id);
|
debug!("Folder moved to trash: {}", item_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
_ => Err(DomainError::validation_error("Item", format!("Invalid item type: {}", item_type))),
|
_ => Err(DomainError::validation_error("Item", format!("Invalid item type: {}", item_type))),
|
||||||
@@ -250,7 +250,7 @@ impl TrashUseCase for TrashService {
|
|||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn restore_item(&self, trash_id: &str, user_id: &str) -> Result<()> {
|
async fn restore_item(&self, trash_id: &str, user_id: &str) -> Result<()> {
|
||||||
info!("Restaurando elemento {} para usuario {}", trash_id, user_id);
|
info!("Restoring item {} for user {}", trash_id, user_id);
|
||||||
|
|
||||||
let trash_uuid = match Uuid::parse_str(trash_id) {
|
let trash_uuid = match Uuid::parse_str(trash_id) {
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
@@ -283,10 +283,10 @@ impl TrashUseCase for TrashService {
|
|||||||
info!("Found item in trash: ID={}, Type={:?}, OriginalID={}",
|
info!("Found item in trash: ID={}, Type={:?}, OriginalID={}",
|
||||||
trash_id, item.item_type, item.original_id);
|
trash_id, item.item_type, item.original_id);
|
||||||
|
|
||||||
// Restaurar según tipo
|
// Restore based on type
|
||||||
match item.item_type {
|
match item.item_type {
|
||||||
TrashedItemType::File => {
|
TrashedItemType::File => {
|
||||||
// Restaurar el archivo a su ubicación original
|
// Restore the file to its original location
|
||||||
let file_id = item.original_id.to_string();
|
let file_id = item.original_id.to_string();
|
||||||
let original_path = item.original_path.clone();
|
let original_path = item.original_path.clone();
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@ impl TrashUseCase for TrashService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
TrashedItemType::Folder => {
|
TrashedItemType::Folder => {
|
||||||
// Restaurar la carpeta a su ubicación original
|
// Restore the folder to its original location
|
||||||
let folder_id = item.original_id.to_string();
|
let folder_id = item.original_id.to_string();
|
||||||
let original_path = item.original_path.clone();
|
let original_path = item.original_path.clone();
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@ impl TrashUseCase for TrashService {
|
|||||||
info!("Found item in trash: ID={}, Type={:?}, OriginalID={}",
|
info!("Found item in trash: ID={}, Type={:?}, OriginalID={}",
|
||||||
trash_id, item.item_type, item.original_id);
|
trash_id, item.item_type, item.original_id);
|
||||||
|
|
||||||
// Eliminar permanentemente según tipo
|
// Permanently delete based on type
|
||||||
match item.item_type {
|
match item.item_type {
|
||||||
TrashedItemType::File => {
|
TrashedItemType::File => {
|
||||||
// Eliminar el archivo permanentemente
|
// Eliminar el archivo permanentemente
|
||||||
@@ -463,7 +463,7 @@ impl TrashUseCase for TrashService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eliminar el item de la papelera siempre, para mantener consistencia
|
// Always remove the item from trash index to maintain consistency
|
||||||
info!("Removing entry from trash index: {}", trash_id);
|
info!("Removing entry from trash index: {}", trash_id);
|
||||||
match self.trash_repository.delete_permanently(&trash_uuid, &user_uuid).await {
|
match self.trash_repository.delete_permanently(&trash_uuid, &user_uuid).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -497,38 +497,38 @@ impl TrashUseCase for TrashService {
|
|||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn empty_trash(&self, user_id: &str) -> Result<()> {
|
async fn empty_trash(&self, user_id: &str) -> Result<()> {
|
||||||
info!("Vaciando papelera para usuario {}", user_id);
|
info!("Emptying trash for user {}", user_id);
|
||||||
|
|
||||||
let user_uuid = Uuid::parse_str(user_id)
|
let user_uuid = Uuid::parse_str(user_id)
|
||||||
.map_err(|e| DomainError::validation_error("User", format!("Invalid user ID: {}", e)))?;
|
.map_err(|e| DomainError::validation_error("User", format!("Invalid user ID: {}", e)))?;
|
||||||
|
|
||||||
// Obtener todos los elementos en la papelera del usuario
|
// Get all items in the user's trash
|
||||||
let items = self.trash_repository.get_trash_items(&user_uuid).await?;
|
let items = self.trash_repository.get_trash_items(&user_uuid).await?;
|
||||||
|
|
||||||
// Eliminar permanentemente cada elemento
|
// Permanently delete each item
|
||||||
for item in items {
|
for item in items {
|
||||||
match item.item_type {
|
match item.item_type {
|
||||||
TrashedItemType::File => {
|
TrashedItemType::File => {
|
||||||
// Eliminar el archivo permanentemente
|
// Permanently delete the file
|
||||||
let file_id = item.original_id.to_string();
|
let file_id = item.original_id.to_string();
|
||||||
if let Err(e) = self.file_repository.delete_file_permanently(&file_id).await {
|
if let Err(e) = self.file_repository.delete_file_permanently(&file_id).await {
|
||||||
error!("Error al eliminar archivo {} permanentemente: {}", file_id, e);
|
error!("Error permanently deleting file {}: {}", file_id, e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TrashedItemType::Folder => {
|
TrashedItemType::Folder => {
|
||||||
// Eliminar la carpeta permanentemente
|
// Permanently delete the folder
|
||||||
let folder_id = item.original_id.to_string();
|
let folder_id = item.original_id.to_string();
|
||||||
if let Err(e) = self.folder_repository.delete_folder_permanently(&folder_id).await {
|
if let Err(e) = self.folder_repository.delete_folder_permanently(&folder_id).await {
|
||||||
error!("Error al eliminar carpeta {} permanentemente: {}", folder_id, e);
|
error!("Error permanently deleting folder {}: {}", folder_id, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limpiar todos los registros de la papelera para este usuario
|
// Clear all trash records for this user
|
||||||
self.trash_repository.clear_trash(&user_uuid).await?;
|
self.trash_repository.clear_trash(&user_uuid).await?;
|
||||||
|
|
||||||
info!("Papelera vaciada completamente para usuario {}", user_id);
|
info!("Trash completely emptied for user {}", user_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+24
-24
@@ -10,11 +10,11 @@ use crate::domain::services::path_service::StoragePath;
|
|||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum FileError {
|
pub enum FileError {
|
||||||
/// Occurs when a file name contains invalid characters or is empty.
|
/// Occurs when a file name contains invalid characters or is empty.
|
||||||
#[error("Nombre de archivo inválido: {0}")]
|
#[error("Invalid file name: {0}")]
|
||||||
InvalidFileName(String),
|
InvalidFileName(String),
|
||||||
|
|
||||||
/// Occurs when validation fails for any file entity attribute.
|
/// Occurs when validation fails for any file entity attribute.
|
||||||
#[error("Error en la validación: {0}")]
|
#[error("Validation error: {0}")]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
ValidationError(String),
|
ValidationError(String),
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ pub struct File {
|
|||||||
modified_at: u64,
|
modified_at: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ya no necesitamos este módulo, ahora usamos un String directamente
|
// We no longer need this module, now we use a String directly
|
||||||
|
|
||||||
impl Default for File {
|
impl Default for File {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@@ -87,7 +87,7 @@ impl Default for File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
/// Crea un nuevo archivo con validación
|
/// Creates a new file with validation
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -96,7 +96,7 @@ impl File {
|
|||||||
mime_type: String,
|
mime_type: String,
|
||||||
folder_id: Option<String>,
|
folder_id: Option<String>,
|
||||||
) -> FileResult<Self> {
|
) -> FileResult<Self> {
|
||||||
// Validar nombre de archivo
|
// Validate file name
|
||||||
if name.is_empty() || name.contains('/') || name.contains('\\') {
|
if name.is_empty() || name.contains('/') || name.contains('\\') {
|
||||||
return Err(FileError::InvalidFileName(name));
|
return Err(FileError::InvalidFileName(name));
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ impl File {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.as_secs();
|
.as_secs();
|
||||||
|
|
||||||
// Almacenamos el string de la ruta para compatibilidad con serialización
|
// Store the path string for serialization compatibility
|
||||||
let path_string = storage_path.to_string();
|
let path_string = storage_path.to_string();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -122,7 +122,7 @@ impl File {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea un archivo con timestamps específicos (para reconstrucción)
|
/// Creates a file with specific timestamps (for reconstruction)
|
||||||
pub fn with_timestamps(
|
pub fn with_timestamps(
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -133,12 +133,12 @@ impl File {
|
|||||||
created_at: u64,
|
created_at: u64,
|
||||||
modified_at: u64,
|
modified_at: u64,
|
||||||
) -> FileResult<Self> {
|
) -> FileResult<Self> {
|
||||||
// Validar nombre de archivo
|
// Validate file name
|
||||||
if name.is_empty() || name.contains('/') || name.contains('\\') {
|
if name.is_empty() || name.contains('/') || name.contains('\\') {
|
||||||
return Err(FileError::InvalidFileName(name));
|
return Err(FileError::InvalidFileName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Almacenamos el string de la ruta para compatibilidad con serialización
|
// Store the path string for serialization compatibility
|
||||||
let path_string = storage_path.to_string();
|
let path_string = storage_path.to_string();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -191,8 +191,8 @@ impl File {
|
|||||||
self.modified_at
|
self.modified_at
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea una nueva instancia de File desde un DTO
|
/// Creates a new File instance from a DTO
|
||||||
/// Esta función es principalmente para conversiones en los batch handlers
|
/// This function is primarily for conversions in batch handlers
|
||||||
pub fn from_dto(
|
pub fn from_dto(
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -203,10 +203,10 @@ impl File {
|
|||||||
created_at: u64,
|
created_at: u64,
|
||||||
modified_at: u64,
|
modified_at: u64,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Crear storage_path desde el string
|
// Create storage_path from string
|
||||||
let storage_path = StoragePath::from_string(&path);
|
let storage_path = StoragePath::from_string(&path);
|
||||||
|
|
||||||
// Crear directamente sin validación para evitar errores en conversiones DTO
|
// Create directly without validation to avoid errors in DTO conversions
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
@@ -220,24 +220,24 @@ impl File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Métodos para crear nuevas versiones del archivo (inmutable)
|
// Methods to create new versions of the file (immutable)
|
||||||
|
|
||||||
/// Crea una nueva versión del archivo con nombre actualizado
|
/// Creates a new version of the file with updated name
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn with_name(&self, new_name: String) -> FileResult<Self> {
|
pub fn with_name(&self, new_name: String) -> FileResult<Self> {
|
||||||
// Validar nombre de archivo
|
// Validate file name
|
||||||
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
|
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
|
||||||
return Err(FileError::InvalidFileName(new_name));
|
return Err(FileError::InvalidFileName(new_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar ruta basada en el nombre
|
// Update path based on name
|
||||||
let parent_path = self.storage_path.parent();
|
let parent_path = self.storage_path.parent();
|
||||||
let new_storage_path = match parent_path {
|
let new_storage_path = match parent_path {
|
||||||
Some(parent) => parent.join(&new_name),
|
Some(parent) => parent.join(&new_name),
|
||||||
None => StoragePath::from_string(&new_name),
|
None => StoragePath::from_string(&new_name),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actualizar representación en string
|
// Update string representation
|
||||||
let new_path_string = new_storage_path.to_string();
|
let new_path_string = new_storage_path.to_string();
|
||||||
|
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
@@ -258,15 +258,15 @@ impl File {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea una nueva versión del archivo con carpeta actualizada
|
/// Creates a new version of the file with updated folder
|
||||||
pub fn with_folder(&self, folder_id: Option<String>, folder_path: Option<StoragePath>) -> FileResult<Self> {
|
pub fn with_folder(&self, folder_id: Option<String>, folder_path: Option<StoragePath>) -> FileResult<Self> {
|
||||||
// Necesitamos una ruta de carpeta para actualizar la ruta del archivo
|
// We need a folder path to update the file path
|
||||||
let new_storage_path = match folder_path {
|
let new_storage_path = match folder_path {
|
||||||
Some(path) => path.join(&self.name),
|
Some(path) => path.join(&self.name),
|
||||||
None => StoragePath::from_string(&self.name), // Raíz
|
None => StoragePath::from_string(&self.name), // Root
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actualizar representación en string
|
// Update string representation
|
||||||
let new_path_string = new_storage_path.to_string();
|
let new_path_string = new_storage_path.to_string();
|
||||||
|
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
@@ -287,7 +287,7 @@ impl File {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea una nueva versión del archivo con tamaño actualizado
|
/// Creates a new version of the file with updated size
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn with_size(&self, new_size: u64) -> Self {
|
pub fn with_size(&self, new_size: u64) -> Self {
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
@@ -333,7 +333,7 @@ mod tests {
|
|||||||
let storage_path = StoragePath::from_string("/test/invalid/file.txt");
|
let storage_path = StoragePath::from_string("/test/invalid/file.txt");
|
||||||
let file = File::new(
|
let file = File::new(
|
||||||
"123".to_string(),
|
"123".to_string(),
|
||||||
"file/with/slash.txt".to_string(), // Nombre inválido
|
"file/with/slash.txt".to_string(), // Invalid name
|
||||||
storage_path,
|
storage_path,
|
||||||
100,
|
100,
|
||||||
"text/plain".to_string(),
|
"text/plain".to_string(),
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::domain::services::path_service::StoragePath;
|
use crate::domain::services::path_service::StoragePath;
|
||||||
|
|
||||||
/// Error en la creación o manipulación de entidades de carpeta
|
/// Error in the creation or manipulation of folder entities
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum FolderError {
|
pub enum FolderError {
|
||||||
#[error("Nombre de carpeta inválido: {0}")]
|
#[error("Invalid folder name: {0}")]
|
||||||
InvalidFolderName(String),
|
InvalidFolderName(String),
|
||||||
|
|
||||||
#[error("Error en la validación: {0}")]
|
#[error("Validation error: {0}")]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
ValidationError(String),
|
ValidationError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tipo de resultado para operaciones con entidades de carpeta
|
/// Result type for folder entity operations
|
||||||
pub type FolderResult<T> = Result<T, FolderError>;
|
pub type FolderResult<T> = Result<T, FolderError>;
|
||||||
|
|
||||||
/// Represents a folder entity in the domain
|
/// Represents a folder entity in the domain
|
||||||
@@ -42,7 +42,7 @@ pub struct Folder {
|
|||||||
modified_at: u64,
|
modified_at: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ya no necesitamos este módulo, ahora usamos un String directamente
|
// We no longer need this module, now we use a String directly
|
||||||
|
|
||||||
impl Default for Folder {
|
impl Default for Folder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@@ -66,7 +66,7 @@ impl Folder {
|
|||||||
storage_path: StoragePath,
|
storage_path: StoragePath,
|
||||||
parent_id: Option<String>,
|
parent_id: Option<String>,
|
||||||
) -> FolderResult<Self> {
|
) -> FolderResult<Self> {
|
||||||
// Validar nombre de carpeta
|
// Validate folder name
|
||||||
if name.is_empty() || name.contains('/') || name.contains('\\') {
|
if name.is_empty() || name.contains('/') || name.contains('\\') {
|
||||||
return Err(FolderError::InvalidFolderName(name));
|
return Err(FolderError::InvalidFolderName(name));
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ impl Folder {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.as_secs();
|
.as_secs();
|
||||||
|
|
||||||
// Almacenamos el string de la ruta para compatibilidad con serialización
|
// Store the path string for serialization compatibility
|
||||||
let path_string = storage_path.to_string();
|
let path_string = storage_path.to_string();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -99,12 +99,12 @@ impl Folder {
|
|||||||
created_at: u64,
|
created_at: u64,
|
||||||
modified_at: u64,
|
modified_at: u64,
|
||||||
) -> FolderResult<Self> {
|
) -> FolderResult<Self> {
|
||||||
// Validar nombre de carpeta
|
// Validate folder name
|
||||||
if name.is_empty() || name.contains('/') || name.contains('\\') {
|
if name.is_empty() || name.contains('/') || name.contains('\\') {
|
||||||
return Err(FolderError::InvalidFolderName(name));
|
return Err(FolderError::InvalidFolderName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Almacenamos el string de la ruta para compatibilidad con serialización
|
// Store the path string for serialization compatibility
|
||||||
let path_string = storage_path.to_string();
|
let path_string = storage_path.to_string();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -147,8 +147,8 @@ impl Folder {
|
|||||||
self.modified_at
|
self.modified_at
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea una nueva instancia de Folder desde un DTO
|
/// Creates a new Folder instance from a DTO
|
||||||
/// Esta función es principalmente para conversiones en los batch handlers
|
/// This function is primarily for conversions in batch handlers
|
||||||
pub fn from_dto(
|
pub fn from_dto(
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -157,10 +157,10 @@ impl Folder {
|
|||||||
created_at: u64,
|
created_at: u64,
|
||||||
modified_at: u64,
|
modified_at: u64,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Crear storage_path desde el string
|
// Create storage_path from the string
|
||||||
let storage_path = StoragePath::from_string(&path);
|
let storage_path = StoragePath::from_string(&path);
|
||||||
|
|
||||||
// Crear directamente sin validación para evitar errores en conversiones DTO
|
// Create directly without validation to avoid errors in DTO conversions
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
@@ -172,23 +172,23 @@ impl Folder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Métodos para crear nuevas versiones de la carpeta (inmutable)
|
// Methods to create new versions of the folder (immutable)
|
||||||
|
|
||||||
/// Creates a new version of the folder with updated name
|
/// Creates a new version of the folder with updated name
|
||||||
pub fn with_name(&self, new_name: String) -> FolderResult<Self> {
|
pub fn with_name(&self, new_name: String) -> FolderResult<Self> {
|
||||||
// Validar nombre de carpeta
|
// Validate folder name
|
||||||
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
|
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
|
||||||
return Err(FolderError::InvalidFolderName(new_name));
|
return Err(FolderError::InvalidFolderName(new_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar ruta basada en el nombre
|
// Update path based on the name
|
||||||
let parent_path = self.storage_path.parent();
|
let parent_path = self.storage_path.parent();
|
||||||
let new_storage_path = match parent_path {
|
let new_storage_path = match parent_path {
|
||||||
Some(parent) => parent.join(&new_name),
|
Some(parent) => parent.join(&new_name),
|
||||||
None => StoragePath::from_string(&new_name),
|
None => StoragePath::from_string(&new_name),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actualizar representación en string
|
// Update string representation
|
||||||
let new_path_string = new_storage_path.to_string();
|
let new_path_string = new_storage_path.to_string();
|
||||||
|
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
@@ -209,13 +209,13 @@ impl Folder {
|
|||||||
|
|
||||||
/// Creates a new version of the folder with updated parent
|
/// Creates a new version of the folder with updated parent
|
||||||
pub fn with_parent(&self, parent_id: Option<String>, parent_path: Option<StoragePath>) -> FolderResult<Self> {
|
pub fn with_parent(&self, parent_id: Option<String>, parent_path: Option<StoragePath>) -> FolderResult<Self> {
|
||||||
// Necesitamos una ruta de carpeta para actualizar la ruta
|
// We need a folder path to update the path
|
||||||
let new_storage_path = match parent_path {
|
let new_storage_path = match parent_path {
|
||||||
Some(path) => path.join(&self.name),
|
Some(path) => path.join(&self.name),
|
||||||
None => StoragePath::from_string(&self.name), // Raíz
|
None => StoragePath::from_string(&self.name), // Root
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actualizar representación en string
|
// Update string representation
|
||||||
let new_path_string = new_storage_path.to_string();
|
let new_path_string = new_storage_path.to_string();
|
||||||
|
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
@@ -276,7 +276,7 @@ mod tests {
|
|||||||
let storage_path = StoragePath::from_string("/test/invalid/folder");
|
let storage_path = StoragePath::from_string("/test/invalid/folder");
|
||||||
let folder = Folder::new(
|
let folder = Folder::new(
|
||||||
"123".to_string(),
|
"123".to_string(),
|
||||||
"folder/with/slash".to_string(), // Nombre inválido
|
"folder/with/slash".to_string(), // Invalid name
|
||||||
storage_path,
|
storage_path,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
@@ -302,6 +302,6 @@ mod tests {
|
|||||||
assert!(renamed.is_ok());
|
assert!(renamed.is_ok());
|
||||||
let renamed = renamed.unwrap();
|
let renamed = renamed.unwrap();
|
||||||
assert_eq!(renamed.name(), "new_name");
|
assert_eq!(renamed.name(), "new_name");
|
||||||
assert_eq!(renamed.id(), "123"); // El ID no cambia
|
assert_eq!(renamed.id(), "123"); // The ID doesn't change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ use crate::infrastructure::repositories::parallel_file_processor::ParallelFilePr
|
|||||||
* filesystem-specific details.
|
* filesystem-specific details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Usar constantes de la configuración centralizada en lugar de valores fijos
|
// Use constants from centralized configuration instead of fixed values
|
||||||
// Esto se reemplaza con self.config.concurrency.max_concurrent_files más adelante
|
// This is replaced with self.config.concurrency.max_concurrent_files later
|
||||||
|
|
||||||
/// Filesystem implementation of the FileRepository interface
|
/// Filesystem implementation of the FileRepository interface
|
||||||
pub struct FileFsRepository {
|
pub struct FileFsRepository {
|
||||||
@@ -130,16 +130,16 @@ impl FileFsRepository {
|
|||||||
async fn file_exists_at_storage_path(&self, storage_path: &StoragePath) -> FileRepositoryResult<bool> {
|
async fn file_exists_at_storage_path(&self, storage_path: &StoragePath) -> FileRepositoryResult<bool> {
|
||||||
let abs_path = self.resolve_storage_path(storage_path);
|
let abs_path = self.resolve_storage_path(storage_path);
|
||||||
|
|
||||||
// Intentar obtener del caché avanzado primero
|
// Try to get from advanced cache first
|
||||||
if let Some(is_file) = self.metadata_cache.is_file(&abs_path).await {
|
if let Some(is_file) = self.metadata_cache.is_file(&abs_path).await {
|
||||||
tracing::debug!("Metadata cache hit for existence check: {} - path: {}", is_file, abs_path.display());
|
tracing::debug!("Metadata cache hit for existence check: {} - path: {}", is_file, abs_path.display());
|
||||||
return Ok(is_file);
|
return Ok(is_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si no está en caché, verificar directamente y actualizar caché
|
// If not in cache, verify directly and update cache
|
||||||
tracing::debug!("Metadata cache miss for existence check: {}", abs_path.display());
|
tracing::debug!("Metadata cache miss for existence check: {}", abs_path.display());
|
||||||
|
|
||||||
// Utilizar timeout para evitar bloqueo
|
// Use timeout to avoid blocking
|
||||||
match time::timeout(
|
match time::timeout(
|
||||||
self.config.timeouts.file_timeout(),
|
self.config.timeouts.file_timeout(),
|
||||||
fs::metadata(&abs_path)
|
fs::metadata(&abs_path)
|
||||||
@@ -147,7 +147,7 @@ impl FileFsRepository {
|
|||||||
Ok(Ok(metadata)) => {
|
Ok(Ok(metadata)) => {
|
||||||
let is_file = metadata.is_file();
|
let is_file = metadata.is_file();
|
||||||
|
|
||||||
// Actualizar la caché con información fresca
|
// Update cache with fresh information
|
||||||
if let Err(e) = self.metadata_cache.refresh_metadata(&abs_path).await {
|
if let Err(e) = self.metadata_cache.refresh_metadata(&abs_path).await {
|
||||||
tracing::warn!("Failed to update cache for {}: {}", abs_path.display(), e);
|
tracing::warn!("Failed to update cache for {}: {}", abs_path.display(), e);
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ impl FileFsRepository {
|
|||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
tracing::warn!("File check failed: {} - {}", abs_path.display(), e);
|
tracing::warn!("File check failed: {} - {}", abs_path.display(), e);
|
||||||
|
|
||||||
// Añadir a caché como no existente
|
// Add to cache as non-existent
|
||||||
let entry_type = CacheEntryType::Unknown;
|
let entry_type = CacheEntryType::Unknown;
|
||||||
let file_metadata = crate::infrastructure::services::file_metadata_cache::FileMetadata::new(
|
let file_metadata = crate::infrastructure::services::file_metadata_cache::FileMetadata::new(
|
||||||
abs_path.clone(),
|
abs_path.clone(),
|
||||||
@@ -191,13 +191,13 @@ impl FileFsRepository {
|
|||||||
pub async fn file_exists(&self, path: &std::path::Path) -> FileRepositoryResult<bool> {
|
pub async fn file_exists(&self, path: &std::path::Path) -> FileRepositoryResult<bool> {
|
||||||
let abs_path = self.resolve_legacy_path(path);
|
let abs_path = self.resolve_legacy_path(path);
|
||||||
|
|
||||||
// Intentar obtener del caché avanzado primero
|
// Try to get from advanced cache first
|
||||||
if let Some(is_file) = self.metadata_cache.is_file(&abs_path).await {
|
if let Some(is_file) = self.metadata_cache.is_file(&abs_path).await {
|
||||||
tracing::debug!("Metadata cache hit for legacy existence check: {} - path: {}", is_file, abs_path.display());
|
tracing::debug!("Metadata cache hit for legacy existence check: {} - path: {}", is_file, abs_path.display());
|
||||||
return Ok(is_file);
|
return Ok(is_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si no está en caché, verificar directamente
|
// If not in cache, verify directly
|
||||||
tracing::info!("Checking if file exists: {} - path: {}", abs_path.exists(), abs_path.display());
|
tracing::info!("Checking if file exists: {} - path: {}", abs_path.exists(), abs_path.display());
|
||||||
|
|
||||||
match time::timeout(
|
match time::timeout(
|
||||||
@@ -207,7 +207,7 @@ impl FileFsRepository {
|
|||||||
Ok(Ok(metadata)) => {
|
Ok(Ok(metadata)) => {
|
||||||
let is_file = metadata.is_file();
|
let is_file = metadata.is_file();
|
||||||
|
|
||||||
// Actualizar la caché con información fresca
|
// Update cache with fresh information
|
||||||
if let Err(e) = self.metadata_cache.refresh_metadata(&abs_path).await {
|
if let Err(e) = self.metadata_cache.refresh_metadata(&abs_path).await {
|
||||||
tracing::warn!("Failed to update cache for {}: {}", abs_path.display(), e);
|
tracing::warn!("Failed to update cache for {}: {}", abs_path.display(), e);
|
||||||
}
|
}
|
||||||
@@ -271,7 +271,7 @@ impl FileFsRepository {
|
|||||||
|
|
||||||
/// Extracts file metadata from a physical path with timeout and cache
|
/// Extracts file metadata from a physical path with timeout and cache
|
||||||
async fn get_file_metadata(&self, abs_path: &PathBuf) -> FileRepositoryResult<(u64, u64, u64)> {
|
async fn get_file_metadata(&self, abs_path: &PathBuf) -> FileRepositoryResult<(u64, u64, u64)> {
|
||||||
// Intentar obtener de caché primero
|
// Try to get from cache first
|
||||||
if let Some(cached_metadata) = self.metadata_cache.get_metadata(abs_path).await {
|
if let Some(cached_metadata) = self.metadata_cache.get_metadata(abs_path).await {
|
||||||
if let (Some(size), Some(created_at), Some(modified_at)) =
|
if let (Some(size), Some(created_at), Some(modified_at)) =
|
||||||
(cached_metadata.size, cached_metadata.created_at, cached_metadata.modified_at) {
|
(cached_metadata.size, cached_metadata.created_at, cached_metadata.modified_at) {
|
||||||
@@ -280,7 +280,7 @@ impl FileFsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si no está en caché o metadatos incompletos, cargar desde sistema de archivos
|
// If not in cache or incomplete metadata, load from filesystem
|
||||||
let metadata = match time::timeout(
|
let metadata = match time::timeout(
|
||||||
self.config.timeouts.file_timeout(),
|
self.config.timeouts.file_timeout(),
|
||||||
fs::metadata(&abs_path)
|
fs::metadata(&abs_path)
|
||||||
@@ -304,7 +304,7 @@ impl FileFsRepository {
|
|||||||
.map(|time| time.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs())
|
.map(|time| time.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs())
|
||||||
.unwrap_or_else(|_| 0);
|
.unwrap_or_else(|_| 0);
|
||||||
|
|
||||||
// Actualizar caché si es posible
|
// Update cache if possible
|
||||||
if let Err(e) = self.metadata_cache.refresh_metadata(abs_path).await {
|
if let Err(e) = self.metadata_cache.refresh_metadata(abs_path).await {
|
||||||
tracing::warn!("Failed to update metadata cache for {}: {}", abs_path.display(), e);
|
tracing::warn!("Failed to update metadata cache for {}: {}", abs_path.display(), e);
|
||||||
}
|
}
|
||||||
@@ -340,7 +340,7 @@ impl FileFsRepository {
|
|||||||
.map_err(|_| FileRepositoryError::Timeout(format!("Timeout checking file size: {}", abs_path.display())))?
|
.map_err(|_| FileRepositoryError::Timeout(format!("Timeout checking file size: {}", abs_path.display())))?
|
||||||
.map_err(FileRepositoryError::IoError)?;
|
.map_err(FileRepositoryError::IoError)?;
|
||||||
|
|
||||||
// Utiliza el método del ResourceConfig para determinar si es un archivo grande
|
// Use the ResourceConfig method to determine if it's a large file
|
||||||
Ok(self.config.resources.is_large_file(metadata.len()))
|
Ok(self.config.resources.is_large_file(metadata.len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@ impl FileRepositoryError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Los errores ya están definidos por la interfaz FileRepositoryError
|
// Errors are already defined by the FileRepositoryError interface
|
||||||
|
|
||||||
// Enable cloning for concurrent operations
|
// Enable cloning for concurrent operations
|
||||||
impl Clone for FileFsRepository {
|
impl Clone for FileFsRepository {
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ use tracing::{debug, error, instrument};
|
|||||||
use crate::domain::repositories::file_repository::FileRepositoryResult;
|
use crate::domain::repositories::file_repository::FileRepositoryResult;
|
||||||
use crate::infrastructure::repositories::file_fs_repository::FileFsRepository;
|
use crate::infrastructure::repositories::file_fs_repository::FileFsRepository;
|
||||||
|
|
||||||
// Este archivo contiene la implementación de los métodos relacionados con la papelera
|
// This file contains the implementation of trash-related methods
|
||||||
// para el repositorio de archivos FileFsRepository
|
// for the FileFsRepository file repository
|
||||||
|
|
||||||
// Implementación de métodos de papelera para el repositorio de archivos
|
// Implementation of trash methods for the file repository
|
||||||
impl FileFsRepository {
|
impl FileFsRepository {
|
||||||
// Obtiene la ruta completa a la papelera
|
// Gets the complete path to the trash directory
|
||||||
fn get_trash_dir(&self) -> PathBuf {
|
fn get_trash_dir(&self) -> PathBuf {
|
||||||
let trash_dir = self.get_root_path().join(".trash").join("files");
|
let trash_dir = self.get_root_path().join(".trash").join("files");
|
||||||
debug!("Base trash directory: {}", trash_dir.display());
|
debug!("Base trash directory: {}", trash_dir.display());
|
||||||
trash_dir
|
trash_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtiene la ruta de la papelera para un usuario específico (si se proporciona)
|
// Gets the trash directory path for a specific user (if provided)
|
||||||
fn get_user_trash_dir(&self, user_id: Option<&str>) -> PathBuf {
|
fn get_user_trash_dir(&self, user_id: Option<&str>) -> PathBuf {
|
||||||
let base_trash_dir = self.get_trash_dir();
|
let base_trash_dir = self.get_trash_dir();
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ impl FileFsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crea una ruta única en la papelera para el archivo
|
// Creates a unique path in the trash for the file
|
||||||
async fn create_trash_file_path(&self, file_id: &str) -> FileRepositoryResult<PathBuf> {
|
async fn create_trash_file_path(&self, file_id: &str) -> FileRepositoryResult<PathBuf> {
|
||||||
debug!("Creating trash file path for file ID: {}", file_id);
|
debug!("Creating trash file path for file ID: {}", file_id);
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ impl FileFsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementación de los métodos públicos del trait FileRepository relacionados con la papelera
|
// Implementation of the public methods of the FileRepository trait related to trash
|
||||||
// Note: The FileRepository trait implementation has been moved to file_fs_repository.rs
|
// Note: The FileRepository trait implementation has been moved to file_fs_repository.rs
|
||||||
// to avoid duplicate implementations
|
// to avoid duplicate implementations
|
||||||
|
|
||||||
@@ -70,101 +70,101 @@ impl FileFsRepository {
|
|||||||
impl FileFsRepository {
|
impl FileFsRepository {
|
||||||
/// Helper method that will be used for trash functionality
|
/// Helper method that will be used for trash functionality
|
||||||
pub(crate) async fn _trash_move_to_trash(&self, file_id: &str) -> FileRepositoryResult<()> {
|
pub(crate) async fn _trash_move_to_trash(&self, file_id: &str) -> FileRepositoryResult<()> {
|
||||||
debug!("Moviendo archivo a la papelera: {}", file_id);
|
debug!("Moving file to trash: {}", file_id);
|
||||||
|
|
||||||
// Obtener la ruta física del archivo
|
// Get the physical path of the file
|
||||||
// Creamos un método independiente para acceder al servicio de mapeo de IDs
|
// We create an independent method to access the ID mapping service
|
||||||
debug!("Obteniendo ruta del archivo con ID: {}", file_id);
|
debug!("Getting file path with ID: {}", file_id);
|
||||||
let file_path = match self.id_mapping_service().get_file_path(file_id).await {
|
let file_path = match self.id_mapping_service().get_file_path(file_id).await {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
debug!("Ruta del archivo obtenida: {}", path.display());
|
debug!("File path obtained: {}", path.display());
|
||||||
path
|
path
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error obteniendo ruta del archivo {}: {:?}", file_id, e);
|
error!("Error getting file path {}: {:?}", file_id, e);
|
||||||
return Err(FileRepositoryError::IdMappingError(format!("Failed to get file path: {}", e)));
|
return Err(FileRepositoryError::IdMappingError(format!("Failed to get file path: {}", e)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verificamos que el archivo existe
|
// Verify that the file exists
|
||||||
debug!("Verificando que el archivo existe: {}", file_path.display());
|
debug!("Verifying that the file exists: {}", file_path.display());
|
||||||
if !self.file_exists(&file_path).await? {
|
if !self.file_exists(&file_path).await? {
|
||||||
error!("Archivo no encontrado en la ruta especificada: {}", file_path.display());
|
error!("File not found at the specified path: {}", file_path.display());
|
||||||
return Err(FileRepositoryError::NotFound(format!("File not found: {}", file_id)));
|
return Err(FileRepositoryError::NotFound(format!("File not found: {}", file_id)));
|
||||||
}
|
}
|
||||||
debug!("Archivo encontrado, continuando con la operación");
|
debug!("File found, continuing with the operation");
|
||||||
|
|
||||||
// Crear directorio en la papelera si no existe
|
// Create directory in trash if it doesn't exist
|
||||||
debug!("Creando path para archivo en papelera");
|
debug!("Creating path for file in trash");
|
||||||
let trash_file_path = self.create_trash_file_path(file_id).await?;
|
let trash_file_path = self.create_trash_file_path(file_id).await?;
|
||||||
debug!("Path en papelera: {}", trash_file_path.display());
|
debug!("Path in trash: {}", trash_file_path.display());
|
||||||
|
|
||||||
// Mover el archivo físicamente a la papelera (no actualiza mappings)
|
// Physically move the file to trash (doesn't update mappings)
|
||||||
debug!("Moviendo archivo físicamente a papelera: {} -> {}", file_path.display(), trash_file_path.display());
|
debug!("Physically moving file to trash: {} -> {}", file_path.display(), trash_file_path.display());
|
||||||
match fs::rename(&file_path, &trash_file_path).await {
|
match fs::rename(&file_path, &trash_file_path).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Archivo movido a papelera exitosamente: {} -> {}", file_path.display(), trash_file_path.display());
|
debug!("File successfully moved to trash: {} -> {}", file_path.display(), trash_file_path.display());
|
||||||
|
|
||||||
// Invalidar la caché del archivo original
|
// Invalidate the cache for the original file
|
||||||
debug!("Invalidando caché para: {}", file_path.display());
|
debug!("Invalidating cache for: {}", file_path.display());
|
||||||
self.metadata_cache().invalidate(&file_path).await;
|
self.metadata_cache().invalidate(&file_path).await;
|
||||||
|
|
||||||
// Actualizar el mapeo al nuevo path en la papelera
|
// Update the mapping to the new path in trash
|
||||||
debug!("Actualizando mapeo de ID a nuevo path en papelera");
|
debug!("Updating ID mapping to new path in trash");
|
||||||
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &trash_file_path).await {
|
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &trash_file_path).await {
|
||||||
error!("Error actualizando mapeo de archivo en papelera: {}", e);
|
error!("Error updating file mapping in trash: {}", e);
|
||||||
return Err(FileRepositoryError::MappingError(format!("Failed to update mapping: {}", e)));
|
return Err(FileRepositoryError::MappingError(format!("Failed to update mapping: {}", e)));
|
||||||
}
|
}
|
||||||
debug!("Mapeo actualizado exitosamente");
|
debug!("Mapping successfully updated");
|
||||||
|
|
||||||
debug!("Operación de mover a papelera completada con éxito para el archivo: {}", file_id);
|
debug!("Move to trash operation completed successfully for file: {}", file_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error moviendo archivo a papelera: {} -> {}: {}",
|
error!("Error moving file to trash: {} -> {}: {}",
|
||||||
file_path.display(), trash_file_path.display(), e);
|
file_path.display(), trash_file_path.display(), e);
|
||||||
Err(FileRepositoryError::IoError(e))
|
Err(FileRepositoryError::IoError(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restaura un archivo desde la papelera a su ubicación original
|
/// Restores a file from trash to its original location
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub(crate) async fn _trash_restore_from_trash(&self, file_id: &str, original_path: &str) -> FileRepositoryResult<()> {
|
pub(crate) async fn _trash_restore_from_trash(&self, file_id: &str, original_path: &str) -> FileRepositoryResult<()> {
|
||||||
debug!("Restaurando archivo {} a {}", file_id, original_path);
|
debug!("Restoring file {} to {}", file_id, original_path);
|
||||||
|
|
||||||
// Try to get the current path from the ID mapping service
|
// Try to get the current path from the ID mapping service
|
||||||
let current_path_result = self.id_mapping_service().get_file_path(file_id).await;
|
let current_path_result = self.id_mapping_service().get_file_path(file_id).await;
|
||||||
|
|
||||||
match current_path_result {
|
match current_path_result {
|
||||||
Ok(current_path) => {
|
Ok(current_path) => {
|
||||||
debug!("Ruta actual en papelera: {}", current_path.display());
|
debug!("Current path in trash: {}", current_path.display());
|
||||||
|
|
||||||
// Check if the file exists in the trash
|
// Check if the file exists in the trash
|
||||||
let file_exists = match fs::metadata(¤t_path).await {
|
let file_exists = match fs::metadata(¤t_path).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Archivo existe en papelera");
|
debug!("File exists in trash");
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("Archivo no existe en papelera: {} - {}", current_path.display(), e);
|
debug!("File does not exist in trash: {} - {}", current_path.display(), e);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !file_exists {
|
if !file_exists {
|
||||||
error!("El archivo no existe físicamente en la papelera: {}", current_path.display());
|
error!("The file does not physically exist in the trash: {}", current_path.display());
|
||||||
return Err(FileRepositoryError::NotFound(format!("File not found in trash: {}", file_id)));
|
return Err(FileRepositoryError::NotFound(format!("File not found in trash: {}", file_id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the original path to a PathBuf
|
// Parse the original path to a PathBuf
|
||||||
let original_path_buf = PathBuf::from(original_path);
|
let original_path_buf = PathBuf::from(original_path);
|
||||||
debug!("Ruta original para restauración: {}", original_path_buf.display());
|
debug!("Original path for restoration: {}", original_path_buf.display());
|
||||||
|
|
||||||
// Check if a file already exists at the destination
|
// Check if a file already exists at the destination
|
||||||
let target_exists = fs::metadata(&original_path_buf).await.is_ok();
|
let target_exists = fs::metadata(&original_path_buf).await.is_ok();
|
||||||
if target_exists {
|
if target_exists {
|
||||||
debug!("Ya existe un archivo en la ruta de destino, generando ruta alternativa");
|
debug!("A file already exists at the destination path, generating alternative path");
|
||||||
|
|
||||||
// Generate a unique path by adding a suffix
|
// Generate a unique path by adding a suffix
|
||||||
// Extract filename and extension
|
// Extract filename and extension
|
||||||
@@ -187,16 +187,16 @@ impl FileFsRepository {
|
|||||||
|
|
||||||
// Create the alternative path
|
// Create the alternative path
|
||||||
let alternative_path = parent_dir.join(new_name);
|
let alternative_path = parent_dir.join(new_name);
|
||||||
debug!("Ruta alternativa para restauración: {}", alternative_path.display());
|
debug!("Alternative path for restoration: {}", alternative_path.display());
|
||||||
|
|
||||||
// Ensure the parent directory exists
|
// Ensure the parent directory exists
|
||||||
if let Some(parent) = alternative_path.parent() {
|
if let Some(parent) = alternative_path.parent() {
|
||||||
if !parent.exists() {
|
if !parent.exists() {
|
||||||
debug!("Creando directorio padre para restauración: {}", parent.display());
|
debug!("Creating parent directory for restoration: {}", parent.display());
|
||||||
match fs::create_dir_all(parent).await {
|
match fs::create_dir_all(parent).await {
|
||||||
Ok(_) => debug!("Directorio padre creado exitosamente"),
|
Ok(_) => debug!("Parent directory created successfully"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error creando directorio padre: {} - {}", parent.display(), e);
|
error!("Error creating parent directory: {} - {}", parent.display(), e);
|
||||||
return Err(FileRepositoryError::IoError(e));
|
return Err(FileRepositoryError::IoError(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,30 +204,30 @@ impl FileFsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move the file from trash to the alternative location
|
// Move the file from trash to the alternative location
|
||||||
debug!("Moviendo archivo de papelera a ubicación alternativa: {} -> {}",
|
debug!("Moving file from trash to alternative location: {} -> {}",
|
||||||
current_path.display(), alternative_path.display());
|
current_path.display(), alternative_path.display());
|
||||||
match fs::rename(¤t_path, &alternative_path).await {
|
match fs::rename(¤t_path, &alternative_path).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Archivo restaurado exitosamente a ubicación alternativa");
|
debug!("File successfully restored to alternative location");
|
||||||
|
|
||||||
// Invalidate cache entries
|
// Invalidate cache entries
|
||||||
debug!("Invalidando caché para archivo en papelera");
|
debug!("Invalidating cache for file in trash");
|
||||||
self.metadata_cache().invalidate(¤t_path).await;
|
self.metadata_cache().invalidate(¤t_path).await;
|
||||||
|
|
||||||
// Update the ID mapping
|
// Update the ID mapping
|
||||||
debug!("Actualizando mapeo de ID a nueva ubicación");
|
debug!("Updating ID mapping to new location");
|
||||||
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &alternative_path).await {
|
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &alternative_path).await {
|
||||||
error!("Error actualizando mapeo de archivo restaurado: {}", e);
|
error!("Error updating mapping of restored file: {}", e);
|
||||||
return Err(FileRepositoryError::MappingError(
|
return Err(FileRepositoryError::MappingError(
|
||||||
format!("Failed to update mapping: {}", e)
|
format!("Failed to update mapping: {}", e)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Restauración a ubicación alternativa completada con éxito");
|
debug!("Restoration to alternative location completed successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error restaurando archivo a ubicación alternativa: {}", e);
|
error!("Error restoring file to alternative location: {}", e);
|
||||||
Err(FileRepositoryError::IoError(e))
|
Err(FileRepositoryError::IoError(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,11 +235,11 @@ impl FileFsRepository {
|
|||||||
// Ensure the parent directory exists
|
// Ensure the parent directory exists
|
||||||
if let Some(parent) = original_path_buf.parent() {
|
if let Some(parent) = original_path_buf.parent() {
|
||||||
if !parent.exists() {
|
if !parent.exists() {
|
||||||
debug!("Creando directorio padre para restauración: {}", parent.display());
|
debug!("Creating parent directory for restoration: {}", parent.display());
|
||||||
match fs::create_dir_all(parent).await {
|
match fs::create_dir_all(parent).await {
|
||||||
Ok(_) => debug!("Directorio padre creado exitosamente"),
|
Ok(_) => debug!("Parent directory created successfully"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error creando directorio padre: {} - {}", parent.display(), e);
|
error!("Error creating parent directory: {} - {}", parent.display(), e);
|
||||||
return Err(FileRepositoryError::IoError(e));
|
return Err(FileRepositoryError::IoError(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,41 +247,41 @@ impl FileFsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move the file from trash to its original location
|
// Move the file from trash to its original location
|
||||||
debug!("Moviendo archivo de papelera a ubicación original: {} -> {}",
|
debug!("Moving file from trash to original location: {} -> {}",
|
||||||
current_path.display(), original_path_buf.display());
|
current_path.display(), original_path_buf.display());
|
||||||
match fs::rename(¤t_path, &original_path_buf).await {
|
match fs::rename(¤t_path, &original_path_buf).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
debug!("Archivo restaurado exitosamente a ubicación original");
|
debug!("File successfully restored to original location");
|
||||||
|
|
||||||
// Invalidate cache entries
|
// Invalidate cache entries
|
||||||
debug!("Invalidando caché para archivo en papelera");
|
debug!("Invalidating cache for file in trash");
|
||||||
self.metadata_cache().invalidate(¤t_path).await;
|
self.metadata_cache().invalidate(¤t_path).await;
|
||||||
|
|
||||||
// Update the ID mapping
|
// Update the ID mapping
|
||||||
debug!("Actualizando mapeo de ID a ubicación original");
|
debug!("Updating ID mapping to original location");
|
||||||
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &original_path_buf).await {
|
if let Err(e) = self.id_mapping_service().update_file_path(file_id, &original_path_buf).await {
|
||||||
error!("Error actualizando mapeo de archivo restaurado: {}", e);
|
error!("Error updating mapping of restored file: {}", e);
|
||||||
return Err(FileRepositoryError::MappingError(
|
return Err(FileRepositoryError::MappingError(
|
||||||
format!("Failed to update mapping: {}", e)
|
format!("Failed to update mapping: {}", e)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Restauración a ubicación original completada con éxito");
|
debug!("Restoration to original location completed successfully");
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error restaurando archivo a ubicación original: {}", e);
|
error!("Error restoring file to original location: {}", e);
|
||||||
Err(FileRepositoryError::IoError(e))
|
Err(FileRepositoryError::IoError(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error obteniendo ruta actual del archivo {}: {:?}", file_id, e);
|
error!("Error getting current path of file {}: {:?}", file_id, e);
|
||||||
|
|
||||||
// Check if the error is because the ID was not found
|
// Check if the error is because the ID was not found
|
||||||
if format!("{}", e).contains("not found") {
|
if format!("{}", e).contains("not found") {
|
||||||
debug!("ID no encontrado en mapeo, archivo ya no existe en papelera");
|
debug!("ID not found in mapping, file no longer exists in trash");
|
||||||
return Err(FileRepositoryError::NotFound(format!("File not found in trash: {}", file_id)));
|
return Err(FileRepositoryError::NotFound(format!("File not found in trash: {}", file_id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,48 +292,48 @@ impl FileFsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Elimina un archivo permanentemente (usado por la papelera)
|
/// Permanently deletes a file (used by trash)
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub(crate) async fn _trash_delete_file_permanently(&self, file_id: &str) -> FileRepositoryResult<()> {
|
pub(crate) async fn _trash_delete_file_permanently(&self, file_id: &str) -> FileRepositoryResult<()> {
|
||||||
debug!("Eliminando archivo permanentemente: {}", file_id);
|
debug!("Permanently deleting file: {}", file_id);
|
||||||
|
|
||||||
// Get the file path using the ID mapping service
|
// Get the file path using the ID mapping service
|
||||||
let file_path_result = self.id_mapping_service().get_file_path(file_id).await;
|
let file_path_result = self.id_mapping_service().get_file_path(file_id).await;
|
||||||
|
|
||||||
match file_path_result {
|
match file_path_result {
|
||||||
Ok(file_path) => {
|
Ok(file_path) => {
|
||||||
debug!("Encontrada ruta para archivo: {} -> {}", file_id, file_path.display());
|
debug!("Found path for file: {} -> {}", file_id, file_path.display());
|
||||||
|
|
||||||
// Check if the file physically exists before attempting to delete
|
// Check if the file physically exists before attempting to delete
|
||||||
let file_exists = fs::metadata(&file_path).await.is_ok();
|
let file_exists = fs::metadata(&file_path).await.is_ok();
|
||||||
|
|
||||||
if file_exists {
|
if file_exists {
|
||||||
debug!("Archivo existe físicamente, eliminando: {}", file_path.display());
|
debug!("File exists physically, deleting: {}", file_path.display());
|
||||||
|
|
||||||
// Delete the file physically
|
// Delete the file physically
|
||||||
if let Err(e) = fs::remove_file(&file_path).await {
|
if let Err(e) = fs::remove_file(&file_path).await {
|
||||||
error!("Error eliminando archivo permanentemente: {} - {}", file_path.display(), e);
|
error!("Error permanently deleting file: {} - {}", file_path.display(), e);
|
||||||
// Don't report error if the file already doesn't exist
|
// Don't report error if the file already doesn't exist
|
||||||
if e.kind() != std::io::ErrorKind::NotFound {
|
if e.kind() != std::io::ErrorKind::NotFound {
|
||||||
return Err(FileRepositoryError::IoError(e));
|
return Err(FileRepositoryError::IoError(e));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug!("Archivo eliminado físicamente con éxito");
|
debug!("File physically deleted successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate cache for this file
|
// Invalidate cache for this file
|
||||||
debug!("Invalidando caché para el archivo: {}", file_path.display());
|
debug!("Invalidating cache for file: {}", file_path.display());
|
||||||
self.metadata_cache().invalidate(&file_path).await;
|
self.metadata_cache().invalidate(&file_path).await;
|
||||||
} else {
|
} else {
|
||||||
debug!("Archivo no existe físicamente, solo limpiando mapeos: {}", file_path.display());
|
debug!("File does not exist physically, only cleaning mappings: {}", file_path.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always remove the ID mapping regardless of whether the file exists
|
// Always remove the ID mapping regardless of whether the file exists
|
||||||
debug!("Eliminando mapeo de ID: {}", file_id);
|
debug!("Removing ID mapping: {}", file_id);
|
||||||
match self.id_mapping_service().remove_id(file_id).await {
|
match self.id_mapping_service().remove_id(file_id).await {
|
||||||
Ok(_) => debug!("Mapeo de ID eliminado con éxito"),
|
Ok(_) => debug!("ID mapping successfully removed"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error eliminando mapeo del archivo: {}", e);
|
error!("Error removing file mapping: {}", e);
|
||||||
// Only return error for critical mapping errors, otherwise continue
|
// Only return error for critical mapping errors, otherwise continue
|
||||||
if format!("{}", e).contains("not found") {
|
if format!("{}", e).contains("not found") {
|
||||||
debug!("ID mapping not found, ignoring this error for deletion");
|
debug!("ID mapping not found, ignoring this error for deletion");
|
||||||
@@ -343,16 +343,16 @@ impl FileFsRepository {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Archivo eliminado permanentemente con éxito: {}", file_id);
|
debug!("File permanently deleted successfully: {}", file_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// This could happen if the file is already deleted or wasn't properly indexed
|
// This could happen if the file is already deleted or wasn't properly indexed
|
||||||
error!("Error obteniendo ruta del archivo {}: {:?}", file_id, e);
|
error!("Error getting file path {}: {:?}", file_id, e);
|
||||||
|
|
||||||
// Check if the error is because the ID was not found
|
// Check if the error is because the ID was not found
|
||||||
if format!("{}", e).contains("not found") {
|
if format!("{}", e).contains("not found") {
|
||||||
debug!("ID no encontrado en mapeo, considerando borrado exitoso: {}", file_id);
|
debug!("ID not found in mapping, considering deletion successful: {}", file_id);
|
||||||
// In this case, we consider the file already deleted
|
// In this case, we consider the file already deleted
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -363,5 +363,5 @@ impl FileFsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-exportaciones necesarias para el compilador
|
// Re-exports needed for the compiler
|
||||||
use crate::domain::repositories::file_repository::FileRepositoryError;
|
use crate::domain::repositories::file_repository::FileRepositoryError;
|
||||||
@@ -17,7 +17,7 @@ use crate::application::services::storage_mediator::StorageMediator;
|
|||||||
use crate::application::ports::outbound::FolderStoragePort;
|
use crate::application::ports::outbound::FolderStoragePort;
|
||||||
use crate::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
|
|
||||||
// Para poder usar streams en la función list_folders
|
// To be able to use streams in the list_folders function
|
||||||
use tokio_stream;
|
use tokio_stream;
|
||||||
|
|
||||||
/// Filesystem implementation of the FolderRepository interface
|
/// Filesystem implementation of the FolderRepository interface
|
||||||
@@ -79,7 +79,7 @@ impl FolderFsRepository {
|
|||||||
async fn count_directory_items(&self, directory_path: &Path) -> FolderRepositoryResult<usize> {
|
async fn count_directory_items(&self, directory_path: &Path) -> FolderRepositoryResult<usize> {
|
||||||
use tokio::fs::read_dir;
|
use tokio::fs::read_dir;
|
||||||
|
|
||||||
// Timeout para evitar bloqueos
|
// Timeout to avoid blocking
|
||||||
let read_dir_timeout = Duration::from_secs(30);
|
let read_dir_timeout = Duration::from_secs(30);
|
||||||
let read_dir_result = timeout(
|
let read_dir_result = timeout(
|
||||||
read_dir_timeout,
|
read_dir_timeout,
|
||||||
@@ -91,7 +91,7 @@ impl FolderFsRepository {
|
|||||||
let mut entries = result.map_err(FolderRepositoryError::IoError)?;
|
let mut entries = result.map_err(FolderRepositoryError::IoError)?;
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
// Contar entradas manualmente
|
// Count entries manually
|
||||||
while let Ok(Some(_)) = entries.next_entry().await {
|
while let Ok(Some(_)) = entries.next_entry().await {
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
@@ -261,10 +261,10 @@ impl From<FolderRepositoryError> for DomainError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementar Clone para poder usar en procesamiento concurrente
|
// Implement Clone to use in concurrent processing
|
||||||
impl Clone for FolderFsRepository {
|
impl Clone for FolderFsRepository {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
// Clonamos los Arc, lo que solo incrementa el contador de referencias
|
// Clone the Arcs, which only increments the reference counter
|
||||||
Self {
|
Self {
|
||||||
root_path: self.root_path.clone(),
|
root_path: self.root_path.clone(),
|
||||||
storage_mediator: self.storage_mediator.clone(),
|
storage_mediator: self.storage_mediator.clone(),
|
||||||
|
|||||||
@@ -13,18 +13,18 @@ use crate::common::config::AppConfig;
|
|||||||
use crate::domain::repositories::file_repository::FileRepositoryError;
|
use crate::domain::repositories::file_repository::FileRepositoryError;
|
||||||
use crate::infrastructure::services::buffer_pool::BufferPool;
|
use crate::infrastructure::services::buffer_pool::BufferPool;
|
||||||
|
|
||||||
/// Estructura para el rango de bytes a procesar
|
/// Structure for the byte range to process
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ChunkRange {
|
pub struct ChunkRange {
|
||||||
/// Índice del chunk
|
/// Chunk index
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
/// Posición de inicio en bytes
|
/// Start position in bytes
|
||||||
pub start: u64,
|
pub start: u64,
|
||||||
/// Tamaño del chunk en bytes
|
/// Chunk size in bytes
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Buffer pooling específico para BytesMut
|
/// Specific buffer pooling for BytesMut
|
||||||
pub struct BytesBufferPool {
|
pub struct BytesBufferPool {
|
||||||
buffers: Mutex<Vec<BytesMut>>,
|
buffers: Mutex<Vec<BytesMut>>,
|
||||||
buffer_size: usize,
|
buffer_size: usize,
|
||||||
@@ -40,53 +40,53 @@ impl BytesBufferPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtener un buffer del pool o crear uno nuevo
|
/// Get a buffer from the pool or create a new one
|
||||||
pub async fn get_buffer(&self) -> BytesMut {
|
pub async fn get_buffer(&self) -> BytesMut {
|
||||||
let mut buffers = self.buffers.lock().await;
|
let mut buffers = self.buffers.lock().await;
|
||||||
|
|
||||||
if let Some(mut buffer) = buffers.pop() {
|
if let Some(mut buffer) = buffers.pop() {
|
||||||
// Reutilizar buffer existente
|
// Reuse existing buffer
|
||||||
buffer.clear(); // Mantener capacidad, limpiar contenido
|
buffer.clear(); // Keep capacity, clear content
|
||||||
buffer
|
buffer
|
||||||
} else {
|
} else {
|
||||||
// Crear nuevo buffer si el pool está vacío
|
// Create new buffer if the pool is empty
|
||||||
BytesMut::with_capacity(self.buffer_size)
|
BytesMut::with_capacity(self.buffer_size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devolver un buffer al pool para reutilización
|
/// Return a buffer to the pool for reuse
|
||||||
pub async fn return_buffer(&self, mut buffer: BytesMut) {
|
pub async fn return_buffer(&self, mut buffer: BytesMut) {
|
||||||
// Restablece el buffer para reutilización
|
// Reset the buffer for reuse
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
|
||||||
let mut buffers = self.buffers.lock().await;
|
let mut buffers = self.buffers.lock().await;
|
||||||
|
|
||||||
// Solo mantener hasta max_buffers
|
// Only keep up to max_buffers
|
||||||
if buffers.len() < self.max_buffers {
|
if buffers.len() < self.max_buffers {
|
||||||
buffers.push(buffer);
|
buffers.push(buffer);
|
||||||
}
|
}
|
||||||
// Si ya tenemos suficientes buffers, este se descartará
|
// If we already have enough buffers, this one will be discarded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Procesador paralelo de archivos para operaciones IO intensivas
|
/// Parallel file processor for IO-intensive operations
|
||||||
pub struct ParallelFileProcessor {
|
pub struct ParallelFileProcessor {
|
||||||
/// Configuración de la aplicación
|
/// Application configuration
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
/// Semáforo para limitar concurrencia global
|
/// Semaphore to limit global concurrency
|
||||||
concurrency_limiter: Arc<Semaphore>,
|
concurrency_limiter: Arc<Semaphore>,
|
||||||
/// Pool de buffers para optimizar memoria
|
/// Buffer pool to optimize memory
|
||||||
buffer_pool: Option<Arc<BufferPool>>,
|
buffer_pool: Option<Arc<BufferPool>>,
|
||||||
/// Pool de buffers BytesMut para operaciones zero-copy
|
/// BytesMut buffer pool for zero-copy operations
|
||||||
bytes_pool: Arc<BytesBufferPool>,
|
bytes_pool: Arc<BytesBufferPool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParallelFileProcessor {
|
impl ParallelFileProcessor {
|
||||||
/// Crea una nueva instancia del procesador
|
/// Creates a new processor instance
|
||||||
pub fn new(config: AppConfig) -> Self {
|
pub fn new(config: AppConfig) -> Self {
|
||||||
let concurrency_limiter = Arc::new(Semaphore::new(config.concurrency.max_concurrent_io));
|
let concurrency_limiter = Arc::new(Semaphore::new(config.concurrency.max_concurrent_io));
|
||||||
|
|
||||||
// Crear pool de BytesMut para operaciones eficientes
|
// Create BytesMut pool for efficient operations
|
||||||
let chunk_size = config.resources.chunk_size_bytes;
|
let chunk_size = config.resources.chunk_size_bytes;
|
||||||
let max_chunks = config.concurrency.max_parallel_chunks;
|
let max_chunks = config.concurrency.max_parallel_chunks;
|
||||||
let bytes_pool = Arc::new(BytesBufferPool::new(chunk_size, max_chunks * 2));
|
let bytes_pool = Arc::new(BytesBufferPool::new(chunk_size, max_chunks * 2));
|
||||||
@@ -99,11 +99,11 @@ impl ParallelFileProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea una nueva instancia del procesador con un pool de buffers
|
/// Creates a new processor instance with a buffer pool
|
||||||
pub fn new_with_buffer_pool(config: AppConfig, buffer_pool: Arc<BufferPool>) -> Self {
|
pub fn new_with_buffer_pool(config: AppConfig, buffer_pool: Arc<BufferPool>) -> Self {
|
||||||
let concurrency_limiter = Arc::new(Semaphore::new(config.concurrency.max_concurrent_io));
|
let concurrency_limiter = Arc::new(Semaphore::new(config.concurrency.max_concurrent_io));
|
||||||
|
|
||||||
// Crear pool de BytesMut para operaciones eficientes
|
// Create BytesMut pool for efficient operations
|
||||||
let chunk_size = config.resources.chunk_size_bytes;
|
let chunk_size = config.resources.chunk_size_bytes;
|
||||||
let max_chunks = config.concurrency.max_parallel_chunks;
|
let max_chunks = config.concurrency.max_parallel_chunks;
|
||||||
let bytes_pool = Arc::new(BytesBufferPool::new(chunk_size, max_chunks * 2));
|
let bytes_pool = Arc::new(BytesBufferPool::new(chunk_size, max_chunks * 2));
|
||||||
@@ -116,15 +116,15 @@ impl ParallelFileProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Divide un archivo en chunks para procesamiento paralelo
|
/// Divides a file into chunks for parallel processing
|
||||||
pub fn calculate_chunks(&self, file_size: u64) -> Vec<ChunkRange> {
|
pub fn calculate_chunks(&self, file_size: u64) -> Vec<ChunkRange> {
|
||||||
// Determinar si el archivo necesita procesamiento paralelo
|
// Determine if the file needs parallel processing
|
||||||
let needs_parallel = self.config.resources.needs_parallel_processing(
|
let needs_parallel = self.config.resources.needs_parallel_processing(
|
||||||
file_size, &self.config.concurrency
|
file_size, &self.config.concurrency
|
||||||
);
|
);
|
||||||
|
|
||||||
if !needs_parallel {
|
if !needs_parallel {
|
||||||
// Para archivos pequeños, usar un solo chunk
|
// For small files, use a single chunk
|
||||||
return vec![ChunkRange {
|
return vec![ChunkRange {
|
||||||
index: 0,
|
index: 0,
|
||||||
start: 0,
|
start: 0,
|
||||||
@@ -132,21 +132,21 @@ impl ParallelFileProcessor {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular número óptimo de chunks
|
// Calculate optimal number of chunks
|
||||||
let chunk_count = self.config.resources.calculate_optimal_chunks(
|
let chunk_count = self.config.resources.calculate_optimal_chunks(
|
||||||
file_size, &self.config.concurrency
|
file_size, &self.config.concurrency
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calcular tamaño de cada chunk
|
// Calculate size of each chunk
|
||||||
let chunk_size = self.config.resources.calculate_chunk_size(file_size, chunk_count);
|
let chunk_size = self.config.resources.calculate_chunk_size(file_size, chunk_count);
|
||||||
|
|
||||||
// Crear los rangos de chunks
|
// Create chunk ranges
|
||||||
let mut chunks = Vec::with_capacity(chunk_count);
|
let mut chunks = Vec::with_capacity(chunk_count);
|
||||||
|
|
||||||
let mut start = 0;
|
let mut start = 0;
|
||||||
for i in 0..chunk_count {
|
for i in 0..chunk_count {
|
||||||
let current_chunk_size = if i == chunk_count - 1 {
|
let current_chunk_size = if i == chunk_count - 1 {
|
||||||
// Último chunk puede ser más pequeño
|
// Last chunk might be smaller
|
||||||
(file_size - start) as usize
|
(file_size - start) as usize
|
||||||
} else {
|
} else {
|
||||||
chunk_size
|
chunk_size
|
||||||
@@ -167,16 +167,16 @@ impl ParallelFileProcessor {
|
|||||||
chunks
|
chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lee un archivo en paralelo y devuelve el contenido completo
|
/// Reads a file in parallel and returns the complete content
|
||||||
/// Implementación optimizada usando BytesMut para reducir copias de memoria
|
/// Optimized implementation using BytesMut to reduce memory copies
|
||||||
pub async fn read_file_parallel(&self, file_path: &PathBuf) -> Result<Vec<u8>, FileRepositoryError> {
|
pub async fn read_file_parallel(&self, file_path: &PathBuf) -> Result<Vec<u8>, FileRepositoryError> {
|
||||||
// Obtener tamaño del archivo
|
// Get file size
|
||||||
let metadata = tokio::fs::metadata(file_path).await
|
let metadata = tokio::fs::metadata(file_path).await
|
||||||
.map_err(FileRepositoryError::IoError)?;
|
.map_err(FileRepositoryError::IoError)?;
|
||||||
|
|
||||||
let file_size = metadata.len();
|
let file_size = metadata.len();
|
||||||
|
|
||||||
// Verificar si el archivo es demasiado grande para memoria
|
// Check if the file is too large for memory
|
||||||
if !self.config.resources.can_load_in_memory(file_size) {
|
if !self.config.resources.can_load_in_memory(file_size) {
|
||||||
return Err(FileRepositoryError::Other(
|
return Err(FileRepositoryError::Other(
|
||||||
format!("File too large to load in memory: {} MB (max: {} MB)",
|
format!("File too large to load in memory: {} MB (max: {} MB)",
|
||||||
@@ -185,19 +185,19 @@ impl ParallelFileProcessor {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular chunks
|
// Calculate chunks
|
||||||
let chunks = self.calculate_chunks(file_size);
|
let chunks = self.calculate_chunks(file_size);
|
||||||
|
|
||||||
if chunks.len() == 1 {
|
if chunks.len() == 1 {
|
||||||
// Para un solo chunk, usar lectura simple con buffer pool si está disponible
|
// For a single chunk, use simple reading with buffer pool if available
|
||||||
info!("Reading file with size {}MB as a single chunk", file_size / (1024 * 1024));
|
info!("Reading file with size {}MB as a single chunk", file_size / (1024 * 1024));
|
||||||
|
|
||||||
if let Some(pool) = &self.buffer_pool {
|
if let Some(pool) = &self.buffer_pool {
|
||||||
// Usar buffer del pool para lectura eficiente
|
// Use buffer from the pool for efficient reading
|
||||||
debug!("Using buffer pool for single chunk read");
|
debug!("Using buffer pool for single chunk read");
|
||||||
let mut buffer = pool.get_buffer().await;
|
let mut buffer = pool.get_buffer().await;
|
||||||
|
|
||||||
// Si el buffer es demasiado pequeño, revertir a la implementación estándar
|
// If the buffer is too small, revert to standard implementation
|
||||||
if buffer.capacity() < file_size as usize {
|
if buffer.capacity() < file_size as usize {
|
||||||
debug!("Buffer from pool too small ({}), using standard read", buffer.capacity());
|
debug!("Buffer from pool too small ({}), using standard read", buffer.capacity());
|
||||||
let content = tokio::fs::read(file_path).await
|
let content = tokio::fs::read(file_path).await
|
||||||
@@ -206,7 +206,7 @@ impl ParallelFileProcessor {
|
|||||||
return Ok(content);
|
return Ok(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usar el buffer de memoria del pool
|
// Use memory buffer from the pool
|
||||||
let mut file = File::open(file_path).await
|
let mut file = File::open(file_path).await
|
||||||
.map_err(FileRepositoryError::IoError)?;
|
.map_err(FileRepositoryError::IoError)?;
|
||||||
|
|
||||||
@@ -215,11 +215,11 @@ impl ParallelFileProcessor {
|
|||||||
|
|
||||||
buffer.set_used(read_size);
|
buffer.set_used(read_size);
|
||||||
|
|
||||||
// Convertir en Vec<u8>
|
// Convert to Vec<u8>
|
||||||
let content = buffer.into_vec();
|
let content = buffer.into_vec();
|
||||||
return Ok(content);
|
return Ok(content);
|
||||||
} else {
|
} else {
|
||||||
// Implementación estándar sin pool
|
// Standard implementation without pool
|
||||||
let content = tokio::fs::read(file_path).await
|
let content = tokio::fs::read(file_path).await
|
||||||
.map_err(FileRepositoryError::IoError)?;
|
.map_err(FileRepositoryError::IoError)?;
|
||||||
|
|
||||||
@@ -227,51 +227,51 @@ impl ParallelFileProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Para múltiples chunks, usar lectura paralela
|
// For multiple chunks, use parallel reading
|
||||||
info!("Reading file with size {}MB in {} parallel chunks using BytesMut",
|
info!("Reading file with size {}MB in {} parallel chunks using BytesMut",
|
||||||
file_size / (1024 * 1024), chunks.len());
|
file_size / (1024 * 1024), chunks.len());
|
||||||
|
|
||||||
// Crear buffer de resultado final (pre-allocated)
|
// Create final result buffer (pre-allocated)
|
||||||
let mut result = BytesMut::with_capacity(file_size as usize);
|
let mut result = BytesMut::with_capacity(file_size as usize);
|
||||||
result.resize(file_size as usize, 0);
|
result.resize(file_size as usize, 0);
|
||||||
let result_mutex = Arc::new(Mutex::new(result));
|
let result_mutex = Arc::new(Mutex::new(result));
|
||||||
|
|
||||||
// Crear tareas para cada chunk
|
// Create tasks for each chunk
|
||||||
let mut tasks = Vec::with_capacity(chunks.len());
|
let mut tasks = Vec::with_capacity(chunks.len());
|
||||||
|
|
||||||
// Abrir archivo una sola vez y compartirlo
|
// Open file once and share it
|
||||||
let file = Arc::new(File::open(file_path).await
|
let file = Arc::new(File::open(file_path).await
|
||||||
.map_err(FileRepositoryError::IoError)?);
|
.map_err(FileRepositoryError::IoError)?);
|
||||||
|
|
||||||
// Referencia al pool de BytesMut
|
// Reference to BytesMut pool
|
||||||
let bytes_pool = self.bytes_pool.clone();
|
let bytes_pool = self.bytes_pool.clone();
|
||||||
|
|
||||||
// Procesar chunks en paralelo
|
// Process chunks in parallel
|
||||||
for chunk in chunks {
|
for chunk in chunks {
|
||||||
let file_clone = file.clone();
|
let file_clone = file.clone();
|
||||||
let result_clone = result_mutex.clone();
|
let result_clone = result_mutex.clone();
|
||||||
let semaphore_clone = self.concurrency_limiter.clone();
|
let semaphore_clone = self.concurrency_limiter.clone();
|
||||||
let bytes_pool_clone = bytes_pool.clone();
|
let bytes_pool_clone = bytes_pool.clone();
|
||||||
|
|
||||||
// Spawn task para este chunk - no hay necesidad de copiar los datos originales
|
// Spawn task for this chunk - no need to copy the original data
|
||||||
let task = task::spawn(async move {
|
let task = task::spawn(async move {
|
||||||
// Adquirir permiso del semáforo
|
// Acquire semaphore permit
|
||||||
let _permit = semaphore_clone.acquire().await.unwrap();
|
let _permit = semaphore_clone.acquire().await.unwrap();
|
||||||
|
|
||||||
// Obtener un buffer reusable del pool de BytesMut
|
// Get a reusable buffer from the BytesMut pool
|
||||||
let mut chunk_buffer = bytes_pool_clone.get_buffer().await;
|
let mut chunk_buffer = bytes_pool_clone.get_buffer().await;
|
||||||
|
|
||||||
// Asegurar que tenga suficiente capacidad
|
// Ensure it has sufficient capacity
|
||||||
if chunk_buffer.capacity() < chunk.size {
|
if chunk_buffer.capacity() < chunk.size {
|
||||||
chunk_buffer = BytesMut::with_capacity(chunk.size);
|
chunk_buffer = BytesMut::with_capacity(chunk.size);
|
||||||
}
|
}
|
||||||
// Resize al tamaño exacto necesario
|
// Resize to the exact size needed
|
||||||
chunk_buffer.resize(chunk.size, 0);
|
chunk_buffer.resize(chunk.size, 0);
|
||||||
|
|
||||||
// Crear un descriptor de archivo duplicado para uso independiente
|
// Create a duplicate file descriptor for independent use
|
||||||
let mut file_handle = file_clone.try_clone().await?;
|
let mut file_handle = file_clone.try_clone().await?;
|
||||||
|
|
||||||
// Posicionar y leer directamente en el BytesMut
|
// Position and read directly into the BytesMut
|
||||||
file_handle.seek(SeekFrom::Start(chunk.start)).await?;
|
file_handle.seek(SeekFrom::Start(chunk.start)).await?;
|
||||||
let bytes_read = file_handle.read_exact(&mut chunk_buffer[..chunk.size]).await?;
|
let bytes_read = file_handle.read_exact(&mut chunk_buffer[..chunk.size]).await?;
|
||||||
|
|
||||||
@@ -282,18 +282,18 @@ impl ParallelFileProcessor {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escribir en resultado final
|
// Write to final result
|
||||||
let mut result_lock = result_clone.lock().await;
|
let mut result_lock = result_clone.lock().await;
|
||||||
let start_pos = chunk.start as usize;
|
let start_pos = chunk.start as usize;
|
||||||
let end_pos = start_pos + chunk.size;
|
let end_pos = start_pos + chunk.size;
|
||||||
|
|
||||||
// Usar copy_from_slice para copiar desde BytesMut al buffer de resultado
|
// Use copy_from_slice to copy from BytesMut to result buffer
|
||||||
result_lock[start_pos..end_pos].copy_from_slice(&chunk_buffer[..chunk.size]);
|
result_lock[start_pos..end_pos].copy_from_slice(&chunk_buffer[..chunk.size]);
|
||||||
|
|
||||||
// Devolver el buffer al pool para su reutilización
|
// Return the buffer to the pool for reuse
|
||||||
bytes_pool_clone.return_buffer(chunk_buffer).await;
|
bytes_pool_clone.return_buffer(chunk_buffer).await;
|
||||||
|
|
||||||
// Registrar progreso
|
// Log progress
|
||||||
debug!("Chunk {} processed: {} bytes from offset {}",
|
debug!("Chunk {} processed: {} bytes from offset {}",
|
||||||
chunk.index, chunk.size, chunk.start);
|
chunk.index, chunk.size, chunk.start);
|
||||||
|
|
||||||
@@ -303,10 +303,10 @@ impl ParallelFileProcessor {
|
|||||||
tasks.push(task);
|
tasks.push(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Esperar a que todas las tareas terminen
|
// Wait for all tasks to complete
|
||||||
let results = join_all(tasks).await;
|
let results = join_all(tasks).await;
|
||||||
|
|
||||||
// Verificar errores
|
// Check for errors
|
||||||
for (i, task_result) in results.into_iter().enumerate() {
|
for (i, task_result) in results.into_iter().enumerate() {
|
||||||
match task_result {
|
match task_result {
|
||||||
Ok(Ok(())) => {},
|
Ok(Ok(())) => {},
|
||||||
@@ -321,7 +321,7 @@ impl ParallelFileProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtener el resultado final y convertir a Vec<u8>
|
// Get the final result and convert to Vec<u8>
|
||||||
let result_buffer = result_mutex.lock().await;
|
let result_buffer = result_mutex.lock().await;
|
||||||
let result_vec = result_buffer.to_vec();
|
let result_vec = result_buffer.to_vec();
|
||||||
|
|
||||||
@@ -329,8 +329,8 @@ impl ParallelFileProcessor {
|
|||||||
Ok(result_vec)
|
Ok(result_vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Escribe un archivo en paralelo desde un buffer
|
/// Writes a file in parallel from a buffer
|
||||||
/// Implementación optimizada usando BytesMut/Bytes para reducir copias de memoria
|
/// Optimized implementation using BytesMut/Bytes to reduce memory copies
|
||||||
pub async fn write_file_parallel(
|
pub async fn write_file_parallel(
|
||||||
&self,
|
&self,
|
||||||
file_path: &PathBuf,
|
file_path: &PathBuf,
|
||||||
@@ -338,56 +338,56 @@ impl ParallelFileProcessor {
|
|||||||
) -> Result<(), FileRepositoryError> {
|
) -> Result<(), FileRepositoryError> {
|
||||||
let file_size = content.len() as u64;
|
let file_size = content.len() as u64;
|
||||||
|
|
||||||
// Calcular chunks
|
// Calculate chunks
|
||||||
let chunks = self.calculate_chunks(file_size);
|
let chunks = self.calculate_chunks(file_size);
|
||||||
|
|
||||||
if chunks.len() == 1 {
|
if chunks.len() == 1 {
|
||||||
// Para un solo chunk, usar escritura simple
|
// For a single chunk, use simple writing
|
||||||
info!("Writing file with size {}MB as a single chunk", file_size / (1024 * 1024));
|
info!("Writing file with size {}MB as a single chunk", file_size / (1024 * 1024));
|
||||||
|
|
||||||
// Implementación estándar (el buffer pooling no ofrece ventajas para escritura simple)
|
// Standard implementation (buffer pooling offers no advantages for simple writing)
|
||||||
tokio::fs::write(file_path, content).await
|
tokio::fs::write(file_path, content).await
|
||||||
.map_err(FileRepositoryError::IoError)?;
|
.map_err(FileRepositoryError::IoError)?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Para múltiples chunks, usar escritura paralela
|
// For multiple chunks, use parallel writing
|
||||||
info!("Writing file with size {}MB in {} parallel chunks using Bytes",
|
info!("Writing file with size {}MB in {} parallel chunks using Bytes",
|
||||||
file_size / (1024 * 1024), chunks.len());
|
file_size / (1024 * 1024), chunks.len());
|
||||||
|
|
||||||
// Crear archivo (no usamos Mutex para reducir contención)
|
// Create file (we don't use Mutex to reduce contention)
|
||||||
let file = File::create(file_path).await
|
let file = File::create(file_path).await
|
||||||
.map_err(FileRepositoryError::IoError)?;
|
.map_err(FileRepositoryError::IoError)?;
|
||||||
|
|
||||||
// Convertir contenido a Bytes (un solo paso de copia)
|
// Convert content to Bytes (single copy step)
|
||||||
let content_bytes = Bytes::copy_from_slice(content);
|
let content_bytes = Bytes::copy_from_slice(content);
|
||||||
|
|
||||||
// Crear tareas para cada chunk
|
// Create tasks for each chunk
|
||||||
let mut tasks = Vec::with_capacity(chunks.len());
|
let mut tasks = Vec::with_capacity(chunks.len());
|
||||||
|
|
||||||
// Procesar chunks en paralelo
|
// Process chunks in parallel
|
||||||
for chunk in chunks {
|
for chunk in chunks {
|
||||||
let file_clone = file.try_clone().await
|
let file_clone = file.try_clone().await
|
||||||
.map_err(FileRepositoryError::IoError)?;
|
.map_err(FileRepositoryError::IoError)?;
|
||||||
let semaphore_clone = self.concurrency_limiter.clone();
|
let semaphore_clone = self.concurrency_limiter.clone();
|
||||||
|
|
||||||
// Crear slice de Bytes (no copia datos, solo referencia)
|
// Create Bytes slice (doesn't copy data, only references)
|
||||||
let start_idx = chunk.start as usize;
|
let start_idx = chunk.start as usize;
|
||||||
let end_idx = start_idx + chunk.size;
|
let end_idx = start_idx + chunk.size;
|
||||||
let chunk_data = content_bytes.slice(start_idx..end_idx);
|
let chunk_data = content_bytes.slice(start_idx..end_idx);
|
||||||
|
|
||||||
// Crear y lanzar tarea
|
// Create and launch task
|
||||||
let task = task::spawn(async move {
|
let task = task::spawn(async move {
|
||||||
// Adquirir permiso del semáforo
|
// Acquire semaphore permit
|
||||||
let _permit = semaphore_clone.acquire().await.unwrap();
|
let _permit = semaphore_clone.acquire().await.unwrap();
|
||||||
|
|
||||||
// Posicionar y escribir
|
// Position and write
|
||||||
let mut file_handle = file_clone;
|
let mut file_handle = file_clone;
|
||||||
file_handle.seek(SeekFrom::Start(chunk.start)).await?;
|
file_handle.seek(SeekFrom::Start(chunk.start)).await?;
|
||||||
file_handle.write_all(&chunk_data).await?;
|
file_handle.write_all(&chunk_data).await?;
|
||||||
|
|
||||||
// Registrar progreso
|
// Log progress
|
||||||
debug!("Chunk {} written: {} bytes at offset {}",
|
debug!("Chunk {} written: {} bytes at offset {}",
|
||||||
chunk.index, chunk.size, chunk.start);
|
chunk.index, chunk.size, chunk.start);
|
||||||
|
|
||||||
@@ -397,10 +397,10 @@ impl ParallelFileProcessor {
|
|||||||
tasks.push(task);
|
tasks.push(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Esperar a que todas las tareas terminen
|
// Wait for all tasks to complete
|
||||||
let results = join_all(tasks).await;
|
let results = join_all(tasks).await;
|
||||||
|
|
||||||
// Verificar errores
|
// Check for errors
|
||||||
for (i, task_result) in results.into_iter().enumerate() {
|
for (i, task_result) in results.into_iter().enumerate() {
|
||||||
match task_result {
|
match task_result {
|
||||||
Ok(Ok(())) => {},
|
Ok(Ok(())) => {},
|
||||||
@@ -415,7 +415,7 @@ impl ParallelFileProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Garantizar que todo se ha escrito correctamente
|
// Ensure everything has been written correctly
|
||||||
let mut file_handle = file;
|
let mut file_handle = file;
|
||||||
file_handle.flush().await.map_err(FileRepositoryError::IoError)?;
|
file_handle.flush().await.map_err(FileRepositoryError::IoError)?;
|
||||||
|
|
||||||
@@ -423,17 +423,17 @@ impl ParallelFileProcessor {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Escribe un chunk en un archivo en una posición específica
|
/// Writes a chunk to a file at a specific position
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
async fn write_chunk_optimized(
|
async fn write_chunk_optimized(
|
||||||
file: &mut File,
|
file: &mut File,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
data: Bytes
|
data: Bytes
|
||||||
) -> Result<(), std::io::Error> {
|
) -> Result<(), std::io::Error> {
|
||||||
// Preparar la escritura en la posición correcta
|
// Prepare writing at the correct position
|
||||||
file.seek(SeekFrom::Start(offset)).await?;
|
file.seek(SeekFrom::Start(offset)).await?;
|
||||||
|
|
||||||
// Escribir datos sin copias adicionales
|
// Write data without additional copies
|
||||||
file.write_all(&data).await?;
|
file.write_all(&data).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -447,59 +447,59 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_parallel_read_write() {
|
async fn test_parallel_read_write() {
|
||||||
// Crear configuración con umbral bajo para testing
|
// Create configuration with low threshold for testing
|
||||||
let mut config = AppConfig::default();
|
let mut config = AppConfig::default();
|
||||||
config.concurrency.min_size_for_parallel_chunks_mb = 1; // 1MB para testing
|
config.concurrency.min_size_for_parallel_chunks_mb = 1; // 1MB for testing
|
||||||
config.concurrency.max_parallel_chunks = 4;
|
config.concurrency.max_parallel_chunks = 4;
|
||||||
|
|
||||||
let processor = ParallelFileProcessor::new(config);
|
let processor = ParallelFileProcessor::new(config);
|
||||||
|
|
||||||
// Crear directorio temporal
|
// Create temporary directory
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let file_path = temp_dir.path().join("test_file.bin");
|
let file_path = temp_dir.path().join("test_file.bin");
|
||||||
|
|
||||||
// Crear datos de prueba (2MB)
|
// Create test data (2MB)
|
||||||
let size = 2 * 1024 * 1024;
|
let size = 2 * 1024 * 1024;
|
||||||
let mut test_data = Vec::with_capacity(size);
|
let mut test_data = Vec::with_capacity(size);
|
||||||
for i in 0..size {
|
for i in 0..size {
|
||||||
test_data.push((i % 256) as u8);
|
test_data.push((i % 256) as u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escribir archivo en paralelo
|
// Write file in parallel
|
||||||
processor.write_file_parallel(&file_path, &test_data).await.unwrap();
|
processor.write_file_parallel(&file_path, &test_data).await.unwrap();
|
||||||
|
|
||||||
// Leer archivo en paralelo
|
// Read file in parallel
|
||||||
let read_data = processor.read_file_parallel(&file_path).await.unwrap();
|
let read_data = processor.read_file_parallel(&file_path).await.unwrap();
|
||||||
|
|
||||||
// Verificar que los datos son idénticos
|
// Verify that the data is identical
|
||||||
assert_eq!(test_data.len(), read_data.len());
|
assert_eq!(test_data.len(), read_data.len());
|
||||||
assert_eq!(test_data, read_data);
|
assert_eq!(test_data, read_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_bytesmut_pool() {
|
async fn test_bytesmut_pool() {
|
||||||
// Crear pool
|
// Create pool
|
||||||
let pool = BytesBufferPool::new(1024, 5);
|
let pool = BytesBufferPool::new(1024, 5);
|
||||||
|
|
||||||
// Obtener buffer
|
// Get buffer
|
||||||
let mut buffer1 = pool.get_buffer().await;
|
let mut buffer1 = pool.get_buffer().await;
|
||||||
buffer1.put_slice(b"test data");
|
buffer1.put_slice(b"test data");
|
||||||
assert_eq!(&buffer1[..9], b"test data");
|
assert_eq!(&buffer1[..9], b"test data");
|
||||||
|
|
||||||
// Devolver buffer al pool
|
// Return buffer to the pool
|
||||||
pool.return_buffer(buffer1).await;
|
pool.return_buffer(buffer1).await;
|
||||||
|
|
||||||
// Obtener otro buffer (debería ser el mismo)
|
// Get another buffer (should be the same one)
|
||||||
let buffer2 = pool.get_buffer().await;
|
let buffer2 = pool.get_buffer().await;
|
||||||
assert_eq!(buffer2.capacity(), 1024);
|
assert_eq!(buffer2.capacity(), 1024);
|
||||||
|
|
||||||
// El buffer debería estar vacío (clear)
|
// The buffer should be empty (cleared)
|
||||||
assert_eq!(buffer2.len(), 0);
|
assert_eq!(buffer2.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chunk_calculation() {
|
fn test_chunk_calculation() {
|
||||||
// Crear configuración de prueba
|
// Create test configuration
|
||||||
let mut config = AppConfig::default();
|
let mut config = AppConfig::default();
|
||||||
config.concurrency.min_size_for_parallel_chunks_mb = 100; // 100MB
|
config.concurrency.min_size_for_parallel_chunks_mb = 100; // 100MB
|
||||||
config.concurrency.max_parallel_chunks = 4;
|
config.concurrency.max_parallel_chunks = 4;
|
||||||
@@ -507,18 +507,18 @@ mod tests {
|
|||||||
|
|
||||||
let processor = ParallelFileProcessor::new(config);
|
let processor = ParallelFileProcessor::new(config);
|
||||||
|
|
||||||
// Archivo pequeño (10MB)
|
// Small file (10MB)
|
||||||
let small_file_size = 10 * 1024 * 1024;
|
let small_file_size = 10 * 1024 * 1024;
|
||||||
let chunks = processor.calculate_chunks(small_file_size);
|
let chunks = processor.calculate_chunks(small_file_size);
|
||||||
assert_eq!(chunks.len(), 1);
|
assert_eq!(chunks.len(), 1);
|
||||||
assert_eq!(chunks[0].size as u64, small_file_size);
|
assert_eq!(chunks[0].size as u64, small_file_size);
|
||||||
|
|
||||||
// Archivo grande (300MB)
|
// Large file (300MB)
|
||||||
let large_file_size = 300 * 1024 * 1024;
|
let large_file_size = 300 * 1024 * 1024;
|
||||||
let chunks = processor.calculate_chunks(large_file_size);
|
let chunks = processor.calculate_chunks(large_file_size);
|
||||||
assert_eq!(chunks.len(), 4); // Limitado a max_parallel_chunks
|
assert_eq!(chunks.len(), 4); // Limited to max_parallel_chunks
|
||||||
|
|
||||||
// Verificar que todos los chunks suman el tamaño total
|
// Verify that all chunks add up to the total size
|
||||||
let total_size: u64 = chunks.iter().map(|c| c.size as u64).sum();
|
let total_size: u64 = chunks.iter().map(|c| c.size as u64).sum();
|
||||||
assert_eq!(total_size, large_file_size);
|
assert_eq!(total_size, large_file_size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,60 +10,60 @@ use crate::infrastructure::services::id_mapping_service::{IdMappingService, IdMa
|
|||||||
use crate::common::errors::DomainError;
|
use crate::common::errors::DomainError;
|
||||||
use crate::application::ports::outbound::IdMappingPort;
|
use crate::application::ports::outbound::IdMappingPort;
|
||||||
|
|
||||||
/// Tamaño máximo de entradas en el caché
|
/// Maximum number of entries in the cache
|
||||||
const MAX_CACHE_SIZE: usize = 10_000;
|
const MAX_CACHE_SIZE: usize = 10_000;
|
||||||
|
|
||||||
/// Tiempo de vida del caché (en segundos)
|
/// Cache time-to-live (in seconds)
|
||||||
const CACHE_TTL_SECONDS: u64 = 60 * 5; // 5 minutos
|
const CACHE_TTL_SECONDS: u64 = 60 * 5; // 5 minutes
|
||||||
|
|
||||||
/// Optimizador para operaciones masivas de mapeo de IDs
|
/// Optimizer for batch ID mapping operations
|
||||||
pub struct IdMappingOptimizer {
|
pub struct IdMappingOptimizer {
|
||||||
/// Servicio base de mapeo de IDs
|
/// Base ID mapping service
|
||||||
base_service: Arc<IdMappingService>,
|
base_service: Arc<IdMappingService>,
|
||||||
|
|
||||||
/// Caché de ID por ruta (path -> id)
|
/// Path to ID cache (path -> id)
|
||||||
path_to_id_cache: RwLock<HashMap<String, (String, Instant)>>,
|
path_to_id_cache: RwLock<HashMap<String, (String, Instant)>>,
|
||||||
|
|
||||||
/// Caché de ruta por ID (id -> path)
|
/// ID to path cache (id -> path)
|
||||||
id_to_path_cache: RwLock<HashMap<String, (String, Instant)>>,
|
id_to_path_cache: RwLock<HashMap<String, (String, Instant)>>,
|
||||||
|
|
||||||
/// Contador de hits
|
/// Hit counter
|
||||||
stats: RwLock<OptimizerStats>,
|
stats: RwLock<OptimizerStats>,
|
||||||
|
|
||||||
/// Semáforo para limitar operaciones de batch
|
/// Semaphore to limit batch operations
|
||||||
batch_limiter: Semaphore,
|
batch_limiter: Semaphore,
|
||||||
|
|
||||||
/// Cola de batch pendientes
|
/// Pending batch queue
|
||||||
pending_batch: Mutex<BatchQueue>,
|
pending_batch: Mutex<BatchQueue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Estadísticas del optimizador
|
/// Optimizer statistics
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct OptimizerStats {
|
pub struct OptimizerStats {
|
||||||
/// Número total de consultas get_path_by_id
|
/// Total number of get_path_by_id queries
|
||||||
pub path_by_id_queries: usize,
|
pub path_by_id_queries: usize,
|
||||||
/// Número de hits en caché get_path_by_id
|
/// Number of cache hits for get_path_by_id
|
||||||
pub path_by_id_hits: usize,
|
pub path_by_id_hits: usize,
|
||||||
|
|
||||||
/// Número total de consultas get_or_create_id
|
/// Total number of get_or_create_id queries
|
||||||
pub get_id_queries: usize,
|
pub get_id_queries: usize,
|
||||||
/// Número de hits en caché get_or_create_id
|
/// Number of cache hits for get_or_create_id
|
||||||
pub get_id_hits: usize,
|
pub get_id_hits: usize,
|
||||||
|
|
||||||
/// Número de batch realizados
|
/// Number of batch operations performed
|
||||||
pub batch_operations: usize,
|
pub batch_operations: usize,
|
||||||
/// Número total de IDs procesados en batch
|
/// Total number of IDs processed in batch
|
||||||
pub batch_items_processed: usize,
|
pub batch_items_processed: usize,
|
||||||
|
|
||||||
/// Último momento de limpieza de caché
|
/// Last cache cleanup timestamp
|
||||||
pub last_cleanup: Option<Instant>,
|
pub last_cleanup: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cola para operaciones batch
|
/// Queue for batch operations
|
||||||
struct BatchQueue {
|
struct BatchQueue {
|
||||||
/// Rutas pendientes para obtener/crear ID
|
/// Pending paths to get/create ID
|
||||||
path_to_id_requests: HashSet<String>,
|
path_to_id_requests: HashSet<String>,
|
||||||
/// IDs pendientes para obtener ruta
|
/// Pending IDs to get path
|
||||||
id_to_path_requests: HashSet<String>,
|
id_to_path_requests: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,43 +76,43 @@ impl Default for BatchQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resultado de una operación batch
|
/// Result of a batch operation
|
||||||
struct BatchResult {
|
struct BatchResult {
|
||||||
/// Mapeo de ruta a ID
|
/// Path to ID mapping
|
||||||
path_to_id: HashMap<String, String>,
|
path_to_id: HashMap<String, String>,
|
||||||
/// Mapeo de ID a ruta
|
/// ID to path mapping
|
||||||
id_to_path: HashMap<String, String>,
|
id_to_path: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IdMappingOptimizer {
|
impl IdMappingOptimizer {
|
||||||
/// Crea un nuevo optimizador para el servicio de mapeo de IDs
|
/// Creates a new optimizer for the ID mapping service
|
||||||
pub fn new(base_service: Arc<IdMappingService>) -> Self {
|
pub fn new(base_service: Arc<IdMappingService>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base_service,
|
base_service,
|
||||||
path_to_id_cache: RwLock::new(HashMap::with_capacity(1000)),
|
path_to_id_cache: RwLock::new(HashMap::with_capacity(1000)),
|
||||||
id_to_path_cache: RwLock::new(HashMap::with_capacity(1000)),
|
id_to_path_cache: RwLock::new(HashMap::with_capacity(1000)),
|
||||||
stats: RwLock::new(OptimizerStats::default()),
|
stats: RwLock::new(OptimizerStats::default()),
|
||||||
batch_limiter: Semaphore::new(2), // Limitar a 2 operaciones batch concurrentes
|
batch_limiter: Semaphore::new(2), // Limit to 2 concurrent batch operations
|
||||||
pending_batch: Mutex::new(BatchQueue::default()),
|
pending_batch: Mutex::new(BatchQueue::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtiene estadísticas del optimizador
|
/// Gets optimizer statistics
|
||||||
pub async fn get_stats(&self) -> OptimizerStats {
|
pub async fn get_stats(&self) -> OptimizerStats {
|
||||||
self.stats.read().await.clone()
|
self.stats.read().await.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Limpia entradas expiradas del caché
|
/// Cleans expired cache entries
|
||||||
pub async fn cleanup_cache(&self) {
|
pub async fn cleanup_cache(&self) {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let ttl = Duration::from_secs(CACHE_TTL_SECONDS);
|
let ttl = Duration::from_secs(CACHE_TTL_SECONDS);
|
||||||
|
|
||||||
// Limpiar caché path_to_id
|
// Clean path_to_id cache
|
||||||
{
|
{
|
||||||
let mut cache = self.path_to_id_cache.write().await;
|
let mut cache = self.path_to_id_cache.write().await;
|
||||||
let initial_size = cache.len();
|
let initial_size = cache.len();
|
||||||
|
|
||||||
// Retener solo entradas no expiradas
|
// Retain only non-expired entries
|
||||||
cache.retain(|_, (_, timestamp)| {
|
cache.retain(|_, (_, timestamp)| {
|
||||||
now.duration_since(*timestamp) < ttl
|
now.duration_since(*timestamp) < ttl
|
||||||
});
|
});
|
||||||
@@ -123,12 +123,12 @@ impl IdMappingOptimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limpiar caché id_to_path
|
// Clean id_to_path cache
|
||||||
{
|
{
|
||||||
let mut cache = self.id_to_path_cache.write().await;
|
let mut cache = self.id_to_path_cache.write().await;
|
||||||
let initial_size = cache.len();
|
let initial_size = cache.len();
|
||||||
|
|
||||||
// Retener solo entradas no expiradas
|
// Retain only non-expired entries
|
||||||
cache.retain(|_, (_, timestamp)| {
|
cache.retain(|_, (_, timestamp)| {
|
||||||
now.duration_since(*timestamp) < ttl
|
now.duration_since(*timestamp) < ttl
|
||||||
});
|
});
|
||||||
@@ -139,14 +139,14 @@ impl IdMappingOptimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar estadísticas
|
// Update statistics
|
||||||
{
|
{
|
||||||
let mut stats = self.stats.write().await;
|
let mut stats = self.stats.write().await;
|
||||||
stats.last_cleanup = Some(now);
|
stats.last_cleanup = Some(now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inicia tarea de limpieza periódica
|
/// Starts periodic cleanup task
|
||||||
pub fn start_cleanup_task(optimizer: Arc<Self>) {
|
pub fn start_cleanup_task(optimizer: Arc<Self>) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let cleanup_interval = Duration::from_secs(CACHE_TTL_SECONDS / 2);
|
let cleanup_interval = Duration::from_secs(CACHE_TTL_SECONDS / 2);
|
||||||
@@ -155,7 +155,7 @@ impl IdMappingOptimizer {
|
|||||||
tokio::time::sleep(cleanup_interval).await;
|
tokio::time::sleep(cleanup_interval).await;
|
||||||
optimizer.cleanup_cache().await;
|
optimizer.cleanup_cache().await;
|
||||||
|
|
||||||
// Loguear estadísticas periódicamente
|
// Log statistics periodically
|
||||||
let stats = optimizer.get_stats().await;
|
let stats = optimizer.get_stats().await;
|
||||||
info!("ID Mapping Optimizer stats - Path queries: {}, hits: {} ({}%), ID queries: {}, hits: {} ({}%), Batch ops: {}, items: {}",
|
info!("ID Mapping Optimizer stats - Path queries: {}, hits: {} ({}%), ID queries: {}, hits: {} ({}%), Batch ops: {}, items: {}",
|
||||||
stats.path_by_id_queries,
|
stats.path_by_id_queries,
|
||||||
@@ -171,11 +171,11 @@ impl IdMappingOptimizer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Agrega una solicitud a la cola pendiente para procesamiento batch
|
/// Adds a request to the pending queue for batch processing
|
||||||
async fn queue_path_to_id_request(&self, path: &StoragePath) -> Result<Option<String>, IdMappingError> {
|
async fn queue_path_to_id_request(&self, path: &StoragePath) -> Result<Option<String>, IdMappingError> {
|
||||||
let path_str = path.to_string();
|
let path_str = path.to_string();
|
||||||
|
|
||||||
// Verificar primero en el caché
|
// Check first in the cache
|
||||||
{
|
{
|
||||||
let cache = self.path_to_id_cache.read().await;
|
let cache = self.path_to_id_cache.read().await;
|
||||||
if let Some((id, _)) = cache.get(&path_str) {
|
if let Some((id, _)) = cache.get(&path_str) {
|
||||||
@@ -204,7 +204,7 @@ impl IdMappingOptimizer {
|
|||||||
// Adquirir permiso para operación batch
|
// Adquirir permiso para operación batch
|
||||||
let _permit = self.batch_limiter.acquire().await.unwrap();
|
let _permit = self.batch_limiter.acquire().await.unwrap();
|
||||||
|
|
||||||
// Obtener las solicitudes pendientes
|
// Get pending requests
|
||||||
let (path_requests, id_requests) = {
|
let (path_requests, id_requests) = {
|
||||||
let mut batch_queue = self.pending_batch.lock().await;
|
let mut batch_queue = self.pending_batch.lock().await;
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ impl IdMappingOptimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar caché con los resultados del batch
|
// Update cache with batch results
|
||||||
{
|
{
|
||||||
let mut path_cache = self.path_to_id_cache.write().await;
|
let mut path_cache = self.path_to_id_cache.write().await;
|
||||||
let mut id_cache = self.id_to_path_cache.write().await;
|
let mut id_cache = self.id_to_path_cache.write().await;
|
||||||
@@ -397,7 +397,7 @@ impl IdMappingPort for IdMappingOptimizer {
|
|||||||
{
|
{
|
||||||
let cache = self.path_to_id_cache.read().await;
|
let cache = self.path_to_id_cache.read().await;
|
||||||
if let Some((id, _)) = cache.get(&path_str) {
|
if let Some((id, _)) = cache.get(&path_str) {
|
||||||
// Actualizar estadísticas
|
// Update statistics
|
||||||
{
|
{
|
||||||
let mut stats = self.stats.write().await;
|
let mut stats = self.stats.write().await;
|
||||||
stats.get_id_hits += 1;
|
stats.get_id_hits += 1;
|
||||||
@@ -407,7 +407,7 @@ impl IdMappingPort for IdMappingOptimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si no está en caché, intentar agregar a cola de batch primero
|
// If not in cache, try adding to batch queue first
|
||||||
let queued_result = self.queue_path_to_id_request(path).await?;
|
let queued_result = self.queue_path_to_id_request(path).await?;
|
||||||
if let Some(id) = queued_result {
|
if let Some(id) = queued_result {
|
||||||
return Ok(id);
|
return Ok(id);
|
||||||
@@ -416,17 +416,17 @@ impl IdMappingPort for IdMappingOptimizer {
|
|||||||
// Trigger batch processing if enough items accumulated
|
// Trigger batch processing if enough items accumulated
|
||||||
self.trigger_batch_if_needed(20).await?;
|
self.trigger_batch_if_needed(20).await?;
|
||||||
|
|
||||||
// Intentar obtener del servicio base
|
// Try to get from the base service
|
||||||
let id = self.base_service.get_or_create_id(path).await?;
|
let id = self.base_service.get_or_create_id(path).await?;
|
||||||
|
|
||||||
// Actualizar caché con el nuevo ID
|
// Update cache with the new ID
|
||||||
{
|
{
|
||||||
let mut path_cache = self.path_to_id_cache.write().await;
|
let mut path_cache = self.path_to_id_cache.write().await;
|
||||||
let mut id_cache = self.id_to_path_cache.write().await;
|
let mut id_cache = self.id_to_path_cache.write().await;
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
// Controlar tamaño del caché
|
// Control cache size
|
||||||
if path_cache.len() >= MAX_CACHE_SIZE {
|
if path_cache.len() >= MAX_CACHE_SIZE {
|
||||||
warn!("Path-to-ID cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE);
|
warn!("Path-to-ID cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE);
|
||||||
path_cache.clear();
|
path_cache.clear();
|
||||||
@@ -451,11 +451,11 @@ impl IdMappingPort for IdMappingOptimizer {
|
|||||||
stats.path_by_id_queries += 1;
|
stats.path_by_id_queries += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar primero en el caché
|
// Check first in the cache
|
||||||
{
|
{
|
||||||
let cache = self.id_to_path_cache.read().await;
|
let cache = self.id_to_path_cache.read().await;
|
||||||
if let Some((path_str, _)) = cache.get(id) {
|
if let Some((path_str, _)) = cache.get(id) {
|
||||||
// Actualizar estadísticas
|
// Update statistics
|
||||||
{
|
{
|
||||||
let mut stats = self.stats.write().await;
|
let mut stats = self.stats.write().await;
|
||||||
stats.path_by_id_hits += 1;
|
stats.path_by_id_hits += 1;
|
||||||
@@ -465,10 +465,10 @@ impl IdMappingPort for IdMappingOptimizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtener del servicio base
|
// Get from the base service
|
||||||
let path = self.base_service.get_path_by_id(id).await?;
|
let path = self.base_service.get_path_by_id(id).await?;
|
||||||
|
|
||||||
// Actualizar caché
|
// Update cache
|
||||||
{
|
{
|
||||||
let mut id_cache = self.id_to_path_cache.write().await;
|
let mut id_cache = self.id_to_path_cache.write().await;
|
||||||
let mut path_cache = self.path_to_id_cache.write().await;
|
let mut path_cache = self.path_to_id_cache.write().await;
|
||||||
@@ -476,7 +476,7 @@ impl IdMappingPort for IdMappingOptimizer {
|
|||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let path_str = path.to_string();
|
let path_str = path.to_string();
|
||||||
|
|
||||||
// Controlar tamaño del caché
|
// Control cache size
|
||||||
if id_cache.len() >= MAX_CACHE_SIZE {
|
if id_cache.len() >= MAX_CACHE_SIZE {
|
||||||
warn!("ID-to-path cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE);
|
warn!("ID-to-path cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE);
|
||||||
id_cache.clear();
|
id_cache.clear();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
dtos::share_dto::{CreateShareDto, UpdateShareDto},
|
dtos::share_dto::{CreateShareDto, UpdateShareDto},
|
||||||
ports::share_ports::ShareUseCase
|
ports::share_ports::ShareUseCase
|
||||||
},
|
},
|
||||||
common::errors::{DomainError, ErrorKind},
|
common::errors::ErrorKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|||||||
+32
-32
@@ -1,4 +1,4 @@
|
|||||||
/* Reset y estilos base */
|
/* Reset and base styles */
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -13,7 +13,7 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Barra lateral */
|
/* Sidebar */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
background-color: #2a3042;
|
background-color: #2a3042;
|
||||||
@@ -86,7 +86,7 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Indicador de almacenamiento */
|
/* Storage indicator */
|
||||||
.storage-container {
|
.storage-container {
|
||||||
margin: 20px 15px;
|
margin: 20px 15px;
|
||||||
background-color: #374e65;
|
background-color: #374e65;
|
||||||
@@ -120,7 +120,7 @@ body {
|
|||||||
color: #f5f5f5;
|
color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Contenido principal */
|
/* Main content */
|
||||||
.main-content {
|
.main-content {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -128,7 +128,7 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Barra superior */
|
/* Top bar */
|
||||||
.top-bar {
|
.top-bar {
|
||||||
height: 70px;
|
height: 70px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
@@ -186,7 +186,7 @@ body {
|
|||||||
background-color: #e64a29;
|
background-color: #e64a29;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilos para resultados de búsqueda */
|
/* Styles for search results */
|
||||||
.search-results-header {
|
.search-results-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -261,7 +261,7 @@ body {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Área de contenido */
|
/* Content area */
|
||||||
.content-area {
|
.content-area {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -358,11 +358,11 @@ body {
|
|||||||
border: 2px dashed #ffc107;
|
border: 2px dashed #ffc107;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilos para las carpetas como en el mockup */
|
/* Styles for folders as in the mockup */
|
||||||
.file-icon.folder-icon {
|
.file-icon.folder-icon {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
background-color: #ffeaa7; /* Color amarillo claro */
|
background-color: #ffeaa7; /* Light yellow color */
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@@ -378,15 +378,15 @@ body {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #fdcb6e; /* Color amarillo más oscuro para la pestaña */
|
background-color: #fdcb6e; /* Darker yellow color for the tab */
|
||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.folder-icon i {
|
.file-icon.folder-icon i {
|
||||||
display: none; /* Ocultar el icono Font Awesome */
|
display: none; /* Hide Font Awesome icon */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilo para documentos */
|
/* Style for documents */
|
||||||
.file-icon.doc-icon {
|
.file-icon.doc-icon {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
@@ -418,11 +418,11 @@ body {
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilo para imágenes */
|
/* Style for images */
|
||||||
.file-icon.image-icon {
|
.file-icon.image-icon {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
background-color: #74b9ff; /* Fondo azul claro */
|
background-color: #74b9ff; /* Light blue background */
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@@ -436,15 +436,15 @@ body {
|
|||||||
left: 15px;
|
left: 15px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #ffda79; /* Círculo amarillo como un sol */
|
background-color: #ffda79; /* Yellow circle like a sun */
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilo para videos */
|
/* Style for videos */
|
||||||
.file-icon.video-icon {
|
.file-icon.video-icon {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
background-color: #111; /* Fondo negro */
|
background-color: #111; /* Black background */
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@@ -459,10 +459,10 @@ body {
|
|||||||
height: 0;
|
height: 0;
|
||||||
border-top: 15px solid transparent;
|
border-top: 15px solid transparent;
|
||||||
border-bottom: 15px solid transparent;
|
border-bottom: 15px solid transparent;
|
||||||
border-left: 20px solid white; /* Triángulo de reproducción */
|
border-left: 20px solid white; /* Play triangle */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilos para archivos de código */
|
/* Styles for code files */
|
||||||
.file-icon.code-icon {
|
.file-icon.code-icon {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
@@ -470,11 +470,11 @@ body {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border-top: 3px solid #556ee6; /* Borde superior azul */
|
border-top: 3px solid #556ee6; /* Blue top border */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Líneas que simulan código */
|
/* Lines that simulate code */
|
||||||
.file-icon.code-icon::before,
|
.file-icon.code-icon::before,
|
||||||
.file-icon.code-icon::after {
|
.file-icon.code-icon::after {
|
||||||
content: "";
|
content: "";
|
||||||
@@ -482,7 +482,7 @@ body {
|
|||||||
left: 10px;
|
left: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: #556ee6; /* Color azul para el código */
|
background-color: #556ee6; /* Blue color for code */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.code-icon::before {
|
.file-icon.code-icon::before {
|
||||||
@@ -495,14 +495,14 @@ body {
|
|||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Agregar líneas adicionales para simular código */
|
/* Add additional lines to simulate code */
|
||||||
.file-icon.code-icon .code-line-1,
|
.file-icon.code-icon .code-line-1,
|
||||||
.file-icon.code-icon .code-line-2,
|
.file-icon.code-icon .code-line-2,
|
||||||
.file-icon.code-icon .code-line-3 {
|
.file-icon.code-icon .code-line-3 {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: #a0aec0; /* Color gris para el código */
|
background-color: #a0aec0; /* Gray color for code */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.code-icon .code-line-1 {
|
.file-icon.code-icon .code-line-1 {
|
||||||
@@ -520,9 +520,9 @@ body {
|
|||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Colores específicos para distintos tipos de archivo de código */
|
/* Specific colors for different code file types */
|
||||||
.file-icon.json-icon {
|
.file-icon.json-icon {
|
||||||
border-top-color: #ffb86c; /* Naranja para JSON */
|
border-top-color: #ffb86c; /* Orange for JSON */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.json-icon::before,
|
.file-icon.json-icon::before,
|
||||||
@@ -531,7 +531,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.js-icon {
|
.file-icon.js-icon {
|
||||||
border-top-color: #ffd43b; /* Amarillo para JavaScript */
|
border-top-color: #ffd43b; /* Yellow for JavaScript */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.js-icon::before,
|
.file-icon.js-icon::before,
|
||||||
@@ -540,7 +540,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.html-icon {
|
.file-icon.html-icon {
|
||||||
border-top-color: #e34c26; /* Rojo para HTML */
|
border-top-color: #e34c26; /* Red for HTML */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.html-icon::before,
|
.file-icon.html-icon::before,
|
||||||
@@ -549,7 +549,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.css-icon {
|
.file-icon.css-icon {
|
||||||
border-top-color: #2965f1; /* Azul para CSS */
|
border-top-color: #2965f1; /* Blue for CSS */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.css-icon::before,
|
.file-icon.css-icon::before,
|
||||||
@@ -558,7 +558,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.py-icon {
|
.file-icon.py-icon {
|
||||||
border-top-color: #3776ab; /* Azul oscuro para Python */
|
border-top-color: #3776ab; /* Dark blue for Python */
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon.py-icon::before,
|
.file-icon.py-icon::before,
|
||||||
@@ -1570,7 +1570,7 @@ header {
|
|||||||
color: #ffc107;
|
color: #ffc107;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Estilos para la papelera */
|
/* Styles for trash */
|
||||||
.trash-item {
|
.trash-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -1634,7 +1634,7 @@ header {
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Botón de peligro para vaciar papelera */
|
/* Danger button for emptying trash */
|
||||||
.btn-danger {
|
.btn-danger {
|
||||||
background-color: #f44336;
|
background-color: #f44336;
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
Reference in New Issue
Block a user