From e79ca8304bb27171f1eb38af5ab8b25dfb56011d Mon Sep 17 00:00:00 2001 From: dionidev Date: Sun, 30 Mar 2025 14:17:09 +0000 Subject: [PATCH] adding comments, moving technical documentation, improve Dockerfile, delete unuseful files --- CLAUDE.md | 55 -- Cargo.lock | 18 +- Cargo.toml | 3 - Dockerfile | 64 +- README.md | 2 +- TODO-LIST.md | 6 +- apply-migration.sh | 39 - apply-migrations.sh | 62 -- benches/file_operations.rs | 53 -- .../LTO-OPTIMIZATIONS.md | 0 README-AUTH.md => doc/README-AUTH.md | 0 .../SHARE-INTEGRATION.md | 0 .../TRASH-FEATURE-SUMMARY.md | 0 docker-compose-migration.yml | 22 - server_log.txt | 883 ------------------ src/application/dtos/share_dto.rs | 2 +- .../services/file_management_service.rs | 12 +- src/application/services/file_service.rs | 20 +- src/application/services/share_service.rs | 2 +- src/application/services/trash_service.rs | 108 +-- src/domain/entities/file.rs | 48 +- src/domain/entities/folder.rs | 44 +- src/domain/repositories/share_repository.rs | 1 - .../repositories/file_fs_repository.rs | 30 +- .../repositories/file_fs_repository_trash.rs | 154 +-- .../repositories/folder_fs_repository.rs | 10 +- .../repositories/parallel_file_processor.rs | 196 ++-- .../services/id_mapping_optimizer.rs | 98 +- src/interfaces/api/handlers/share_handler.rs | 2 +- static/css/style.css | 64 +- 30 files changed, 461 insertions(+), 1537 deletions(-) delete mode 100644 CLAUDE.md delete mode 100755 apply-migration.sh delete mode 100755 apply-migrations.sh delete mode 100644 benches/file_operations.rs rename LTO-OPTIMIZATIONS.md => doc/LTO-OPTIMIZATIONS.md (100%) rename README-AUTH.md => doc/README-AUTH.md (100%) rename SHARE-INTEGRATION.md => doc/SHARE-INTEGRATION.md (100%) rename TRASH-FEATURE-SUMMARY.md => doc/TRASH-FEATURE-SUMMARY.md (100%) delete mode 100644 docker-compose-migration.yml delete mode 100644 server_log.txt diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 3fe55b4a..00000000 --- a/CLAUDE.md +++ /dev/null @@ -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 # 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 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) -- **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. \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index eddc7269..8d97e068 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,9 +153,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288" dependencies = [ "axum-core", "axum-macros", @@ -189,12 +189,12 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", - "futures-util", + "futures-core", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -1466,9 +1466,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2806eaa3524762875e21c3dcd057bc4b7bfa01ce4da8d46be1cd43649e1cc6b" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" @@ -2197,9 +2197,9 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", diff --git a/Cargo.toml b/Cargo.toml index 8738859e..ff62805a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,6 @@ name = "oxicloud" version = "0.1.0" edition = "2021" -[[bench]] -name = "file_operations" -harness = true [dependencies] axum = { version = "0.8.1", features = ["multipart", "http1", "tokio", "macros"] } diff --git a/Dockerfile b/Dockerfile index f645b894..be9eaf0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 && \ - apk add cargo pkgconfig openssl-dev +# Create a new empty project and copy only dependency files first +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 -# /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 -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 -COPY --from=builder /usr/lib/libgcc_s.so.1 /usr/lib/libgcc_s.so.1 +# Create a non-root user +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"] \ No newline at end of file +# 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"] \ No newline at end of file diff --git a/README.md b/README.md index 7a06c85d..36757df9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- OxiCloud + OxiCloud

diff --git a/TODO-LIST.md b/TODO-LIST.md index d28beea5..bb8ed7c0 100644 --- a/TODO-LIST.md +++ b/TODO-LIST.md @@ -38,9 +38,9 @@ This document contains the task list for the development of OxiCloud, a minimali ## Phase 2: Authentication and Multi-User ### User System -- [ ] Design data model for users +- [x] Design data model for users - [ ] Implement user registration -- [ ] Create login system +- [x] Create login system - [ ] Add user profile page - [ ] Implement password recovery - [ ] 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 ### File Sharing -- [ ] Implement shared link generation +- [x] Implement shared link generation - [ ] Add permission configuration for links - [ ] Implement password protection for links - [ ] Add expiration dates for shared links diff --git a/apply-migration.sh b/apply-migration.sh deleted file mode 100755 index 6b0bd2c7..00000000 --- a/apply-migration.sh +++ /dev/null @@ -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." \ No newline at end of file diff --git a/apply-migrations.sh b/apply-migrations.sh deleted file mode 100755 index 67965393..00000000 --- a/apply-migrations.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/benches/file_operations.rs b/benches/file_operations.rs deleted file mode 100644 index 4f8d189c..00000000 --- a/benches/file_operations.rs +++ /dev/null @@ -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 - ) -} \ No newline at end of file diff --git a/LTO-OPTIMIZATIONS.md b/doc/LTO-OPTIMIZATIONS.md similarity index 100% rename from LTO-OPTIMIZATIONS.md rename to doc/LTO-OPTIMIZATIONS.md diff --git a/README-AUTH.md b/doc/README-AUTH.md similarity index 100% rename from README-AUTH.md rename to doc/README-AUTH.md diff --git a/SHARE-INTEGRATION.md b/doc/SHARE-INTEGRATION.md similarity index 100% rename from SHARE-INTEGRATION.md rename to doc/SHARE-INTEGRATION.md diff --git a/TRASH-FEATURE-SUMMARY.md b/doc/TRASH-FEATURE-SUMMARY.md similarity index 100% rename from TRASH-FEATURE-SUMMARY.md rename to doc/TRASH-FEATURE-SUMMARY.md diff --git a/docker-compose-migration.yml b/docker-compose-migration.yml deleted file mode 100644 index 39e14131..00000000 --- a/docker-compose-migration.yml +++ /dev/null @@ -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: \ No newline at end of file diff --git a/server_log.txt b/server_log.txt deleted file mode 100644 index 900d321b..00000000 --- a/server_log.txt +++ /dev/null @@ -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>, - | ^^^^^ 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>>>, - | ^^^^^^^^^^^^^^^^^ 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, 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> + 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>, - | ^^^^^^^^^^^^^^^^^^ - -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, - | ^^^^^^^^^^^^^^^^ -22 | config: AppConfig, -23 | parallel_processor: Option>, - | ^^^^^^^^^^^^^^^^^^ - -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>(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) -> Self { - | ^^^^^^^^^^^ -... -260 | pub fn forbidden(message: impl Into) -> Self { - | ^^^^^^^^^ -... -264 | pub fn not_found(message: impl Into) -> 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; - | ^^^^^^^^^^^^^^^^^ -... -48 | async fn get_sessions_by_user_id(&self, user_id: &str) -> SessionRepositoryResult>; - | ^^^^^^^^^^^^^^^^^^^^^^^ -... -57 | async fn delete_expired_sessions(&self) -> SessionRepositoryResult; - | ^^^^^^^^^^^^^^^^^^^^^^^ - -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 { - | ^^^^^^^^^^^^^^ - -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; - | ^^^^^^^^ -... -26 | async fn list_files(&self, folder_id: Option<&str>) -> Result, DomainError>; - | ^^^^^^^^^^ -... -29 | async fn delete_file(&self, id: &str) -> Result<(), DomainError>; - | ^^^^^^^^^^^ -... -32 | async fn get_file_content(&self, id: &str) -> Result, DomainError>; - | ^^^^^^^^^^^^^^^^ -... -35 | async fn get_file_stream(&self, id: &str) -> Result> + Send... - | ^^^^^^^^^^^^^^^ -... -38 | async fn move_file(&self, file_id: &str, folder_id: Option) -> Result; - | ^^^^^^^^^ - -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; - | ^^^^^^^^^^^^^^^^^^ - -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; - | ^^^^^^^^^^^ -... -24 | async fn directory_exists(&self, storage_path: &StoragePath) -> Result; - | ^^^^^^^^^^^^^^^^ - -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; - | ^^^^^^^^^^^^^ - -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; - | ^^^^^^^^^^^^^ -... -98 | async fn get_folder_path(&self, id: &str) -> Result; - | ^^^^^^^^^^^^^^^ - -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; - | ^^^^^^^^ -... -29 | async fn list_files(&self, folder_id: Option<&str>) -> Result, DomainError>; - | ^^^^^^^^^^ -... -32 | async fn get_file_content(&self, id: &str) -> Result, DomainError>; - | ^^^^^^^^^^^^^^^^ -... -35 | async fn get_file_stream(&self, id: &str) -> Result> + 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) -> Result; - | ^^^^^^^^^ -... -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; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -51 | fn create_file_retrieval_use_case(&self) -> Arc; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -52 | fn create_file_management_use_case(&self) -> Arc; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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; - | ^^^^^^^^^^^^^ -... -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, 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; - | ^^^^^^^^^^^^^^^^^^^^^^^ -... -72 | async fn get_folder(&self, folder_id: &str) -> StorageMediatorResult; - | ^^^^^^^^^^ -... -75 | async fn file_exists_at_path(&self, path: &Path) -> StorageMediatorResult; - | ^^^^^^^^^^^^^^^^^^^ -... -78 | async fn file_exists_at_storage_path(&self, storage_path: &StoragePath) -> StorageMediatorResult; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -81 | async fn folder_exists_at_path(&self, path: &Path) -> StorageMediatorResult; - | ^^^^^^^^^^^^^^^^^^^^^ -... -84 | async fn folder_exists_at_storage_path(&self, storage_path: &StoragePath) -> StorageMediatorResult; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -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 { - | ^^^^^^^^^^ -... -317 | pub async fn list_users(&self, limit: i64, offset: i64) -> Result, 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, -15 | storage_mediator: Arc, - | ^^^^^^^^^^^^^^^^ - -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 { - | ^^^^^^^^^^^^^^^^ -... -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` -2025-03-23T17:05:10.052229Z  INFO oxicloud::infrastructure::services::id_mapping_service: Loaded ID map with 0 entries (version: 0) -2025-03-23T17:05:10.052304Z  INFO oxicloud: ID mapping optimizer initialized with batch processing and caching -2025-03-23T17:05:10.052341Z  INFO oxicloud: Buffer pool initialized with 50 buffers of 256KB each -2025-03-23T17:05:10.052362Z  INFO oxicloud::infrastructure::services::file_system_i18n_service: Loading translations for locale en from "./static/locales/en.json" -2025-03-23T17:05:10.052497Z  INFO oxicloud::infrastructure::services::file_system_i18n_service: Translations loaded for locale en -2025-03-23T17:05:10.052506Z  INFO oxicloud::infrastructure::services::file_system_i18n_service: Loading translations for locale es from "./static/locales/es.json" -2025-03-23T17:05:10.052595Z  INFO oxicloud::infrastructure::services::file_system_i18n_service: Translations loaded for locale es -2025-03-23T17:05:10.052602Z  INFO oxicloud: Compression service initialized with buffer pool support -2025-03-23T17:05:10.053417Z  INFO oxicloud: Preloading common directories to warm up cache... -2025-03-23T17:05:10.054362Z  INFO oxicloud: Preloaded 4 directory entries into cache -2025-03-23T17:05:10.054377Z  INFO oxicloud: Starting OxiCloud server on http://127.0.0.1:8085 -2025-03-23T17:05:10.054382Z  INFO oxicloud: Authentication system initialized successfully -2025-03-23T17:05:10.054387Z  INFO oxicloud: Server binding to http://127.0.0.1:8085 -2025-03-23T17:05:10.054405Z DEBUG oxicloud::interfaces::middleware::cache: 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 -2025-03-23T17:05:10.054424Z  INFO oxicloud::interfaces::middleware::cache: HTTP Cache cleanup: removed 0, current: 0/0 diff --git a/src/application/dtos/share_dto.rs b/src/application/dtos/share_dto.rs index 74147dc0..30d8cd6b 100644 --- a/src/application/dtos/share_dto.rs +++ b/src/application/dtos/share_dto.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::domain::entities::share::{Share, ShareItemType, SharePermissions}; +use crate::domain::entities::share::{Share, SharePermissions}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShareDto { diff --git a/src/application/services/file_management_service.rs b/src/application/services/file_management_service.rs index 1e54314c..f29b81ff 100644 --- a/src/application/services/file_management_service.rs +++ b/src/application/services/file_management_service.rs @@ -6,18 +6,18 @@ use crate::application::ports::file_ports::FileManagementUseCase; use crate::application::ports::storage_ports::FileWritePort; use crate::common::errors::DomainError; -/// Servicio para operaciones de gestión de archivos +/// Service for file management operations pub struct FileManagementService { file_repository: Arc, } impl FileManagementService { - /// Crea un nuevo servicio de gestión de archivos + /// Creates a new file management service pub fn new(file_repository: Arc) -> Self { Self { file_repository } } - /// Crea un stub para pruebas + /// Creates a stub for testing pub fn default_stub() -> Self { Self { file_repository: Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub()) @@ -28,15 +28,15 @@ impl FileManagementService { #[async_trait] impl FileManagementUseCase for FileManagementService { async fn move_file(&self, file_id: &str, folder_id: Option) -> Result { - 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 .map_err(|e| { - tracing::error!("Error al mover archivo (ID: {}): {}", file_id, e); + tracing::error!("Error moving file (ID: {}): {}", file_id, 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()); Ok(FileDto::from(moved_file)) diff --git a/src/application/services/file_service.rs b/src/application/services/file_service.rs index 88d1e720..3c669ff4 100644 --- a/src/application/services/file_service.rs +++ b/src/application/services/file_service.rs @@ -19,23 +19,23 @@ use bytes::Bytes; #[derive(Debug, Error)] pub enum FileServiceError { /// Returned when a requested file cannot be found - #[error("Archivo no encontrado: {0}")] + #[error("File not found: {0}")] NotFound(String), /// Returned when a file operation conflicts with existing files - #[error("Archivo ya existe: {0}")] + #[error("File already exists: {0}")] Conflict(String), /// 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), /// Returned when a file path is invalid - #[error("Ruta de archivo inválida: {0}")] + #[error("Invalid file path: {0}")] InvalidPath(String), /// Generic internal error for unexpected failures - #[error("Error interno: {0}")] + #[error("Internal error: {0}")] InternalError(String), } @@ -52,7 +52,7 @@ impl From for FileServiceError { FileRepositoryError::AlreadyExists(path) => FileServiceError::Conflict(path), FileRepositoryError::InvalidPath(path) => FileServiceError::InvalidPath(path), 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()), } } @@ -213,16 +213,16 @@ impl FileService { /// Moves a file to a new folder using filesystem operations directly pub async fn move_file(&self, file_id: &str, folder_id: Option) -> FileServiceResult { - 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 .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) })?; - 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()); Ok(FileDto::from(moved_file)) diff --git a/src/application/services/share_service.rs b/src/application/services/share_service.rs index c819d347..162e4604 100644 --- a/src/application/services/share_service.rs +++ b/src/application/services/share_service.rs @@ -7,7 +7,7 @@ use crate::{ application::{ dtos::{ pagination::PaginatedResponseDto, - share_dto::{CreateShareDto, ShareDto, SharePermissionsDto, UpdateShareDto}, + share_dto::{CreateShareDto, ShareDto, UpdateShareDto}, }, ports::{ outbound::{FileStoragePort, FolderStoragePort}, diff --git a/src/application/services/trash_service.rs b/src/application/services/trash_service.rs index 11239d50..43357eed 100644 --- a/src/application/services/trash_service.rs +++ b/src/application/services/trash_service.rs @@ -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 { - // 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(); TrashedItemDto { @@ -72,12 +72,12 @@ impl TrashService { } } - /// Valida los permisos del usuario sobre un elemento + /// Validates user permissions over an item #[instrument(skip(self))] async fn validate_user_ownership(&self, _item_id: &str, _user_id: &str) -> Result<()> { - // Aquí implementaríamos la validación de permisos - // Por ahora, simplemente devolvemos Ok ya que no tenemos una implementación completa - // de permisos por usuario + // Here we would implement permission validation + // For now, we simply return Ok since we don't have a complete + // implementation of user permissions Ok(()) } } @@ -86,7 +86,7 @@ impl TrashService { impl TrashUseCase for TrashService { #[instrument(skip(self))] async fn get_trash_items(&self, user_id: &str) -> Result> { - 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) .map_err(|e| DomainError::validation_error("User", format!("Invalid user ID: {}", e)))?; @@ -102,52 +102,52 @@ impl TrashUseCase for TrashService { #[instrument(skip(self))] 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); // Validate user ownership - debug!("Validando permisos de usuario"); + debug!("Validating user permissions"); self.validate_user_ownership(item_id, user_id).await?; - debug!("Permisos de usuario validados"); + debug!("User permissions validated"); // 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) { Ok(uuid) => { - debug!("UUID del item válido: {}", uuid); + debug!("Valid item UUID: {}", uuid); uuid }, 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))); } }; - debug!("Validando UUID del usuario: {}", user_id); + debug!("Validating user UUID: {}", user_id); let user_uuid = match Uuid::parse_str(user_id) { Ok(uuid) => { - debug!("UUID del usuario válido: {}", uuid); + debug!("Valid user UUID: {}", uuid); uuid }, 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))); } }; match item_type { "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 - debug!("Obteniendo datos del archivo: {}", item_id); + // Get the file to verify it exists and capture its data + debug!("Getting file data: {}", item_id); let file = match self.file_repository.get_file_by_id(item_id).await { Ok(file) => { - debug!("Archivo encontrado: {} ({})", file.name(), item_id); + debug!("File found: {} ({})", file.name(), item_id); file }, Err(e) => { - error!("Error al obtener archivo: {} - {}", item_id, e); + error!("Error getting file: {} - {}", item_id, e); return Err(DomainError::new( ErrorKind::NotFound, "File", @@ -157,10 +157,10 @@ impl TrashUseCase for TrashService { }; 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 - debug!("Creando objeto TrashedItem para el archivo"); + // Create the trash item + debug!("Creating TrashedItem object for the file"); let trashed_item = TrashedItem::new( item_uuid, user_uuid, @@ -169,28 +169,28 @@ impl TrashUseCase for TrashService { original_path, 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 - info!("Añadiendo archivo {} a índice de papelera", item_id); + // First add to trash index to register the item + info!("Adding file {} to trash index", item_id); match self.trash_repository.add_to_trash(&trashed_item).await { Ok(_) => { - debug!("Archivo añadido al índice de papelera con éxito"); + debug!("File added to trash index successfully"); }, 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))); } }; - // Luego movemos el archivo físicamente a la papelera - info!("Moviendo archivo físicamente a la papelera: {}", item_id); + // Then physically move the file to trash + info!("Physically moving file to trash: {}", item_id); match self.file_repository.move_to_trash(item_id).await { Ok(_) => { - debug!("Archivo movido físicamente a papelera con éxito: {}", item_id); + debug!("File physically moved to trash successfully: {}", item_id); }, 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( ErrorKind::InternalError, "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(()) }, "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 .map_err(|e| DomainError::new( ErrorKind::NotFound, @@ -213,7 +213,7 @@ impl TrashUseCase for TrashService { let original_path = folder.storage_path().to_string(); - // Crear el elemento de papelera + // Create the trash item let trashed_item = TrashedItem::new( item_uuid, user_uuid, @@ -223,7 +223,7 @@ impl TrashUseCase for TrashService { 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); match self.trash_repository.add_to_trash(&trashed_item).await { 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 .map_err(|e| DomainError::new( ErrorKind::InternalError, @@ -241,7 +241,7 @@ impl TrashUseCase for TrashService { format!("Error moving folder {} to trash: {}", item_id, e) ))?; - debug!("Carpeta movida a papelera: {}", item_id); + debug!("Folder moved to trash: {}", item_id); Ok(()) }, _ => Err(DomainError::validation_error("Item", format!("Invalid item type: {}", item_type))), @@ -250,7 +250,7 @@ impl TrashUseCase for TrashService { #[instrument(skip(self))] 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) { Ok(id) => { @@ -283,10 +283,10 @@ impl TrashUseCase for TrashService { info!("Found item in trash: ID={}, Type={:?}, OriginalID={}", trash_id, item.item_type, item.original_id); - // Restaurar según tipo + // Restore based on type match item.item_type { 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 original_path = item.original_path.clone(); @@ -313,7 +313,7 @@ impl TrashUseCase for TrashService { } }, 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 original_path = item.original_path.clone(); @@ -408,7 +408,7 @@ impl TrashUseCase for TrashService { info!("Found item in trash: ID={}, Type={:?}, OriginalID={}", trash_id, item.item_type, item.original_id); - // Eliminar permanentemente según tipo + // Permanently delete based on type match item.item_type { TrashedItemType::File => { // 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); match self.trash_repository.delete_permanently(&trash_uuid, &user_uuid).await { Ok(_) => { @@ -497,38 +497,38 @@ impl TrashUseCase for TrashService { #[instrument(skip(self))] 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) .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?; - // Eliminar permanentemente cada elemento + // Permanently delete each item for item in items { match item.item_type { TrashedItemType::File => { - // Eliminar el archivo permanentemente + // Permanently delete the file let file_id = item.original_id.to_string(); 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 => { - // Eliminar la carpeta permanentemente + // Permanently delete the folder let folder_id = item.original_id.to_string(); 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?; - info!("Papelera vaciada completamente para usuario {}", user_id); + info!("Trash completely emptied for user {}", user_id); Ok(()) } } \ No newline at end of file diff --git a/src/domain/entities/file.rs b/src/domain/entities/file.rs index 694899dd..4980e770 100644 --- a/src/domain/entities/file.rs +++ b/src/domain/entities/file.rs @@ -10,11 +10,11 @@ use crate::domain::services::path_service::StoragePath; #[derive(Debug, thiserror::Error)] pub enum FileError { /// 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), /// Occurs when validation fails for any file entity attribute. - #[error("Error en la validación: {0}")] + #[error("Validation error: {0}")] #[allow(dead_code)] ValidationError(String), } @@ -68,7 +68,7 @@ pub struct File { 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 { fn default() -> Self { @@ -87,7 +87,7 @@ impl Default for File { } impl File { - /// Crea un nuevo archivo con validación + /// Creates a new file with validation pub fn new( id: String, name: String, @@ -96,7 +96,7 @@ impl File { mime_type: String, folder_id: Option, ) -> FileResult { - // Validar nombre de archivo + // Validate file name if name.is_empty() || name.contains('/') || name.contains('\\') { return Err(FileError::InvalidFileName(name)); } @@ -106,7 +106,7 @@ impl File { .unwrap_or_default() .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(); 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( id: String, name: String, @@ -133,12 +133,12 @@ impl File { created_at: u64, modified_at: u64, ) -> FileResult { - // Validar nombre de archivo + // Validate file name if name.is_empty() || name.contains('/') || name.contains('\\') { 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(); Ok(Self { @@ -191,8 +191,8 @@ impl File { self.modified_at } - /// Crea una nueva instancia de File desde un DTO - /// Esta función es principalmente para conversiones en los batch handlers + /// Creates a new File instance from a DTO + /// This function is primarily for conversions in batch handlers pub fn from_dto( id: String, name: String, @@ -203,10 +203,10 @@ impl File { created_at: u64, modified_at: u64, ) -> Self { - // Crear storage_path desde el string + // Create storage_path from string 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 { id, 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)] pub fn with_name(&self, new_name: String) -> FileResult { - // Validar nombre de archivo + // Validate file name if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') { 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 new_storage_path = match parent_path { Some(parent) => parent.join(&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 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, folder_path: Option) -> FileResult { - // 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 { 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 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)] pub fn with_size(&self, new_size: u64) -> Self { let now = std::time::SystemTime::now() @@ -333,7 +333,7 @@ mod tests { let storage_path = StoragePath::from_string("/test/invalid/file.txt"); let file = File::new( "123".to_string(), - "file/with/slash.txt".to_string(), // Nombre inválido + "file/with/slash.txt".to_string(), // Invalid name storage_path, 100, "text/plain".to_string(), diff --git a/src/domain/entities/folder.rs b/src/domain/entities/folder.rs index b94fe9ea..f5dd9b93 100644 --- a/src/domain/entities/folder.rs +++ b/src/domain/entities/folder.rs @@ -1,18 +1,18 @@ use serde::{Serialize, Deserialize}; 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)] pub enum FolderError { - #[error("Nombre de carpeta inválido: {0}")] + #[error("Invalid folder name: {0}")] InvalidFolderName(String), - #[error("Error en la validación: {0}")] + #[error("Validation error: {0}")] #[allow(dead_code)] ValidationError(String), } -/// Tipo de resultado para operaciones con entidades de carpeta +/// Result type for folder entity operations pub type FolderResult = Result; /// Represents a folder entity in the domain @@ -42,7 +42,7 @@ pub struct Folder { 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 { fn default() -> Self { @@ -66,7 +66,7 @@ impl Folder { storage_path: StoragePath, parent_id: Option, ) -> FolderResult { - // Validar nombre de carpeta + // Validate folder name if name.is_empty() || name.contains('/') || name.contains('\\') { return Err(FolderError::InvalidFolderName(name)); } @@ -76,7 +76,7 @@ impl Folder { .unwrap_or_default() .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(); Ok(Self { @@ -99,12 +99,12 @@ impl Folder { created_at: u64, modified_at: u64, ) -> FolderResult { - // Validar nombre de carpeta + // Validate folder name if name.is_empty() || name.contains('/') || name.contains('\\') { 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(); Ok(Self { @@ -147,8 +147,8 @@ impl Folder { self.modified_at } - /// Crea una nueva instancia de Folder desde un DTO - /// Esta función es principalmente para conversiones en los batch handlers + /// Creates a new Folder instance from a DTO + /// This function is primarily for conversions in batch handlers pub fn from_dto( id: String, name: String, @@ -157,10 +157,10 @@ impl Folder { created_at: u64, modified_at: u64, ) -> Self { - // Crear storage_path desde el string + // Create storage_path from the string 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 { id, 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 pub fn with_name(&self, new_name: String) -> FolderResult { - // Validar nombre de carpeta + // Validate folder name if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') { 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 new_storage_path = match parent_path { Some(parent) => parent.join(&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 now = std::time::SystemTime::now() @@ -209,13 +209,13 @@ impl Folder { /// Creates a new version of the folder with updated parent pub fn with_parent(&self, parent_id: Option, parent_path: Option) -> FolderResult { - // 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 { 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 now = std::time::SystemTime::now() @@ -276,7 +276,7 @@ mod tests { let storage_path = StoragePath::from_string("/test/invalid/folder"); let folder = Folder::new( "123".to_string(), - "folder/with/slash".to_string(), // Nombre inválido + "folder/with/slash".to_string(), // Invalid name storage_path, None, ); @@ -302,6 +302,6 @@ mod tests { assert!(renamed.is_ok()); let renamed = renamed.unwrap(); 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 } } \ No newline at end of file diff --git a/src/domain/repositories/share_repository.rs b/src/domain/repositories/share_repository.rs index b833e6fe..f208b2bd 100644 --- a/src/domain/repositories/share_repository.rs +++ b/src/domain/repositories/share_repository.rs @@ -1,4 +1,3 @@ -use std::sync::Arc; use async_trait::async_trait; use thiserror::Error; diff --git a/src/infrastructure/repositories/file_fs_repository.rs b/src/infrastructure/repositories/file_fs_repository.rs index 5acfbe91..1b416105 100644 --- a/src/infrastructure/repositories/file_fs_repository.rs +++ b/src/infrastructure/repositories/file_fs_repository.rs @@ -45,8 +45,8 @@ use crate::infrastructure::repositories::parallel_file_processor::ParallelFilePr * filesystem-specific details. */ -// Usar constantes de la configuración centralizada en lugar de valores fijos -// Esto se reemplaza con self.config.concurrency.max_concurrent_files más adelante +// Use constants from centralized configuration instead of fixed values +// This is replaced with self.config.concurrency.max_concurrent_files later /// Filesystem implementation of the FileRepository interface pub struct FileFsRepository { @@ -130,16 +130,16 @@ impl FileFsRepository { async fn file_exists_at_storage_path(&self, storage_path: &StoragePath) -> FileRepositoryResult { 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 { tracing::debug!("Metadata cache hit for existence check: {} - path: {}", is_file, abs_path.display()); 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()); - // Utilizar timeout para evitar bloqueo + // Use timeout to avoid blocking match time::timeout( self.config.timeouts.file_timeout(), fs::metadata(&abs_path) @@ -147,7 +147,7 @@ impl FileFsRepository { Ok(Ok(metadata)) => { 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 { tracing::warn!("Failed to update cache for {}: {}", abs_path.display(), e); } @@ -163,7 +163,7 @@ impl FileFsRepository { Ok(Err(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 file_metadata = crate::infrastructure::services::file_metadata_cache::FileMetadata::new( abs_path.clone(), @@ -191,13 +191,13 @@ impl FileFsRepository { pub async fn file_exists(&self, path: &std::path::Path) -> FileRepositoryResult { 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 { tracing::debug!("Metadata cache hit for legacy existence check: {} - path: {}", is_file, abs_path.display()); 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()); match time::timeout( @@ -207,7 +207,7 @@ impl FileFsRepository { Ok(Ok(metadata)) => { 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 { 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 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(size), Some(created_at), Some(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( self.config.timeouts.file_timeout(), fs::metadata(&abs_path) @@ -304,7 +304,7 @@ impl FileFsRepository { .map(|time| time.duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs()) .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 { 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::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())) } @@ -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 impl Clone for FileFsRepository { diff --git a/src/infrastructure/repositories/file_fs_repository_trash.rs b/src/infrastructure/repositories/file_fs_repository_trash.rs index ac051d55..68cc6cd3 100644 --- a/src/infrastructure/repositories/file_fs_repository_trash.rs +++ b/src/infrastructure/repositories/file_fs_repository_trash.rs @@ -5,19 +5,19 @@ use tracing::{debug, error, instrument}; use crate::domain::repositories::file_repository::FileRepositoryResult; use crate::infrastructure::repositories::file_fs_repository::FileFsRepository; -// Este archivo contiene la implementación de los métodos relacionados con la papelera -// para el repositorio de archivos FileFsRepository +// This file contains the implementation of trash-related methods +// 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 { - // Obtiene la ruta completa a la papelera + // Gets the complete path to the trash directory fn get_trash_dir(&self) -> PathBuf { let trash_dir = self.get_root_path().join(".trash").join("files"); debug!("Base trash directory: {}", trash_dir.display()); 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 { 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 { 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 // to avoid duplicate implementations @@ -70,101 +70,101 @@ impl FileFsRepository { impl FileFsRepository { /// Helper method that will be used for trash functionality 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 - // Creamos un método independiente para acceder al servicio de mapeo de IDs - debug!("Obteniendo ruta del archivo con ID: {}", file_id); + // Get the physical path of the file + // We create an independent method to access the ID mapping service + debug!("Getting file path with ID: {}", file_id); let file_path = match self.id_mapping_service().get_file_path(file_id).await { Ok(path) => { - debug!("Ruta del archivo obtenida: {}", path.display()); + debug!("File path obtained: {}", path.display()); path }, 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))); } }; - // Verificamos que el archivo existe - debug!("Verificando que el archivo existe: {}", file_path.display()); + // Verify that the file exists + debug!("Verifying that the file exists: {}", file_path.display()); 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))); } - debug!("Archivo encontrado, continuando con la operación"); + debug!("File found, continuing with the operation"); - // Crear directorio en la papelera si no existe - debug!("Creando path para archivo en papelera"); + // Create directory in trash if it doesn't exist + debug!("Creating path for file in trash"); 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) - debug!("Moviendo archivo físicamente a papelera: {} -> {}", file_path.display(), trash_file_path.display()); + // Physically move the file to trash (doesn't update mappings) + debug!("Physically moving file to trash: {} -> {}", file_path.display(), trash_file_path.display()); match fs::rename(&file_path, &trash_file_path).await { 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 - debug!("Invalidando caché para: {}", file_path.display()); + // Invalidate the cache for the original file + debug!("Invalidating cache for: {}", file_path.display()); self.metadata_cache().invalidate(&file_path).await; - // Actualizar el mapeo al nuevo path en la papelera - debug!("Actualizando mapeo de ID a nuevo path en papelera"); + // Update the mapping to the new path in trash + 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 { - 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))); } - 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(()) }, Err(e) => { - error!("Error moviendo archivo a papelera: {} -> {}: {}", + error!("Error moving file to trash: {} -> {}: {}", file_path.display(), trash_file_path.display(), 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))] 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 let current_path_result = self.id_mapping_service().get_file_path(file_id).await; match current_path_result { 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 let file_exists = match fs::metadata(¤t_path).await { Ok(_) => { - debug!("Archivo existe en papelera"); + debug!("File exists in trash"); true }, Err(e) => { - debug!("Archivo no existe en papelera: {} - {}", current_path.display(), e); + debug!("File does not exist in trash: {} - {}", current_path.display(), e); false } }; 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))); } // Parse the original path to a PathBuf 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 let target_exists = fs::metadata(&original_path_buf).await.is_ok(); 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 // Extract filename and extension @@ -187,16 +187,16 @@ impl FileFsRepository { // Create the alternative path 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 if let Some(parent) = alternative_path.parent() { 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 { - Ok(_) => debug!("Directorio padre creado exitosamente"), + Ok(_) => debug!("Parent directory created successfully"), Err(e) => { - error!("Error creando directorio padre: {} - {}", parent.display(), e); + error!("Error creating parent directory: {} - {}", parent.display(), e); return Err(FileRepositoryError::IoError(e)); } } @@ -204,30 +204,30 @@ impl FileFsRepository { } // 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()); match fs::rename(¤t_path, &alternative_path).await { Ok(_) => { - debug!("Archivo restaurado exitosamente a ubicación alternativa"); + debug!("File successfully restored to alternative location"); // Invalidate cache entries - debug!("Invalidando caché para archivo en papelera"); + debug!("Invalidating cache for file in trash"); self.metadata_cache().invalidate(¤t_path).await; // 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 { - error!("Error actualizando mapeo de archivo restaurado: {}", e); + error!("Error updating mapping of restored file: {}", e); return Err(FileRepositoryError::MappingError( format!("Failed to update mapping: {}", e) )); } - debug!("Restauración a ubicación alternativa completada con éxito"); + debug!("Restoration to alternative location completed successfully"); Ok(()) }, Err(e) => { - error!("Error restaurando archivo a ubicación alternativa: {}", e); + error!("Error restoring file to alternative location: {}", e); Err(FileRepositoryError::IoError(e)) } } @@ -235,11 +235,11 @@ impl FileFsRepository { // Ensure the parent directory exists if let Some(parent) = original_path_buf.parent() { 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 { - Ok(_) => debug!("Directorio padre creado exitosamente"), + Ok(_) => debug!("Parent directory created successfully"), Err(e) => { - error!("Error creando directorio padre: {} - {}", parent.display(), e); + error!("Error creating parent directory: {} - {}", parent.display(), e); return Err(FileRepositoryError::IoError(e)); } } @@ -247,41 +247,41 @@ impl FileFsRepository { } // 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()); match fs::rename(¤t_path, &original_path_buf).await { Ok(_) => { - debug!("Archivo restaurado exitosamente a ubicación original"); + debug!("File successfully restored to original location"); // Invalidate cache entries - debug!("Invalidando caché para archivo en papelera"); + debug!("Invalidating cache for file in trash"); self.metadata_cache().invalidate(¤t_path).await; // 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 { - error!("Error actualizando mapeo de archivo restaurado: {}", e); + error!("Error updating mapping of restored file: {}", e); return Err(FileRepositoryError::MappingError( format!("Failed to update mapping: {}", e) )); } - debug!("Restauración a ubicación original completada con éxito"); + debug!("Restoration to original location completed successfully"); Ok(()) }, Err(e) => { - error!("Error restaurando archivo a ubicación original: {}", e); + error!("Error restoring file to original location: {}", e); Err(FileRepositoryError::IoError(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 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))); } @@ -292,48 +292,48 @@ impl FileFsRepository { } } - /// Elimina un archivo permanentemente (usado por la papelera) + /// Permanently deletes a file (used by trash) #[instrument(skip(self))] 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 let file_path_result = self.id_mapping_service().get_file_path(file_id).await; match file_path_result { 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 let file_exists = fs::metadata(&file_path).await.is_ok(); if file_exists { - debug!("Archivo existe físicamente, eliminando: {}", file_path.display()); + debug!("File exists physically, deleting: {}", file_path.display()); // Delete the file physically 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 if e.kind() != std::io::ErrorKind::NotFound { return Err(FileRepositoryError::IoError(e)); } } else { - debug!("Archivo eliminado físicamente con éxito"); + debug!("File physically deleted successfully"); } // 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; } 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 - debug!("Eliminando mapeo de ID: {}", file_id); + debug!("Removing ID mapping: {}", file_id); 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) => { - error!("Error eliminando mapeo del archivo: {}", e); + error!("Error removing file mapping: {}", e); // Only return error for critical mapping errors, otherwise continue if format!("{}", e).contains("not found") { 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(()) }, Err(e) => { // 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 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 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; \ No newline at end of file diff --git a/src/infrastructure/repositories/folder_fs_repository.rs b/src/infrastructure/repositories/folder_fs_repository.rs index 67bf7c53..5ae1e316 100644 --- a/src/infrastructure/repositories/folder_fs_repository.rs +++ b/src/infrastructure/repositories/folder_fs_repository.rs @@ -17,7 +17,7 @@ use crate::application::services::storage_mediator::StorageMediator; use crate::application::ports::outbound::FolderStoragePort; 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; /// Filesystem implementation of the FolderRepository interface @@ -79,7 +79,7 @@ impl FolderFsRepository { async fn count_directory_items(&self, directory_path: &Path) -> FolderRepositoryResult { use tokio::fs::read_dir; - // Timeout para evitar bloqueos + // Timeout to avoid blocking let read_dir_timeout = Duration::from_secs(30); let read_dir_result = timeout( read_dir_timeout, @@ -91,7 +91,7 @@ impl FolderFsRepository { let mut entries = result.map_err(FolderRepositoryError::IoError)?; let mut count = 0; - // Contar entradas manualmente + // Count entries manually while let Ok(Some(_)) = entries.next_entry().await { count += 1; } @@ -261,10 +261,10 @@ impl From for DomainError { } } -// Implementar Clone para poder usar en procesamiento concurrente +// Implement Clone to use in concurrent processing impl Clone for FolderFsRepository { 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 { root_path: self.root_path.clone(), storage_mediator: self.storage_mediator.clone(), diff --git a/src/infrastructure/repositories/parallel_file_processor.rs b/src/infrastructure/repositories/parallel_file_processor.rs index 9c5266f0..ba85c2e4 100644 --- a/src/infrastructure/repositories/parallel_file_processor.rs +++ b/src/infrastructure/repositories/parallel_file_processor.rs @@ -13,18 +13,18 @@ use crate::common::config::AppConfig; use crate::domain::repositories::file_repository::FileRepositoryError; 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)] pub struct ChunkRange { - /// Índice del chunk + /// Chunk index pub index: usize, - /// Posición de inicio en bytes + /// Start position in bytes pub start: u64, - /// Tamaño del chunk en bytes + /// Chunk size in bytes pub size: usize, } -/// Buffer pooling específico para BytesMut +/// Specific buffer pooling for BytesMut pub struct BytesBufferPool { buffers: Mutex>, 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 { let mut buffers = self.buffers.lock().await; if let Some(mut buffer) = buffers.pop() { - // Reutilizar buffer existente - buffer.clear(); // Mantener capacidad, limpiar contenido + // Reuse existing buffer + buffer.clear(); // Keep capacity, clear content buffer } else { - // Crear nuevo buffer si el pool está vacío + // Create new buffer if the pool is empty 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) { - // Restablece el buffer para reutilización + // Reset the buffer for reuse buffer.clear(); let mut buffers = self.buffers.lock().await; - // Solo mantener hasta max_buffers + // Only keep up to max_buffers if buffers.len() < self.max_buffers { 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 { - /// Configuración de la aplicación + /// Application configuration config: AppConfig, - /// Semáforo para limitar concurrencia global + /// Semaphore to limit global concurrency concurrency_limiter: Arc, - /// Pool de buffers para optimizar memoria + /// Buffer pool to optimize memory buffer_pool: Option>, - /// Pool de buffers BytesMut para operaciones zero-copy + /// BytesMut buffer pool for zero-copy operations bytes_pool: Arc, } impl ParallelFileProcessor { - /// Crea una nueva instancia del procesador + /// Creates a new processor instance pub fn new(config: AppConfig) -> Self { 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 max_chunks = config.concurrency.max_parallel_chunks; 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) -> Self { 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 max_chunks = config.concurrency.max_parallel_chunks; 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 { - // Determinar si el archivo necesita procesamiento paralelo + // Determine if the file needs parallel processing let needs_parallel = self.config.resources.needs_parallel_processing( file_size, &self.config.concurrency ); if !needs_parallel { - // Para archivos pequeños, usar un solo chunk + // For small files, use a single chunk return vec![ChunkRange { index: 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( 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); - // Crear los rangos de chunks + // Create chunk ranges let mut chunks = Vec::with_capacity(chunk_count); let mut start = 0; for i in 0..chunk_count { 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 } else { chunk_size @@ -167,16 +167,16 @@ impl ParallelFileProcessor { chunks } - /// Lee un archivo en paralelo y devuelve el contenido completo - /// Implementación optimizada usando BytesMut para reducir copias de memoria + /// Reads a file in parallel and returns the complete content + /// Optimized implementation using BytesMut to reduce memory copies pub async fn read_file_parallel(&self, file_path: &PathBuf) -> Result, FileRepositoryError> { - // Obtener tamaño del archivo + // Get file size let metadata = tokio::fs::metadata(file_path).await .map_err(FileRepositoryError::IoError)?; 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) { return Err(FileRepositoryError::Other( 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); 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)); 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"); 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 { debug!("Buffer from pool too small ({}), using standard read", buffer.capacity()); let content = tokio::fs::read(file_path).await @@ -206,7 +206,7 @@ impl ParallelFileProcessor { return Ok(content); } - // Usar el buffer de memoria del pool + // Use memory buffer from the pool let mut file = File::open(file_path).await .map_err(FileRepositoryError::IoError)?; @@ -215,11 +215,11 @@ impl ParallelFileProcessor { buffer.set_used(read_size); - // Convertir en Vec + // Convert to Vec let content = buffer.into_vec(); return Ok(content); } else { - // Implementación estándar sin pool + // Standard implementation without pool let content = tokio::fs::read(file_path).await .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", 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); result.resize(file_size as usize, 0); 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()); - // Abrir archivo una sola vez y compartirlo + // Open file once and share it let file = Arc::new(File::open(file_path).await .map_err(FileRepositoryError::IoError)?); - // Referencia al pool de BytesMut + // Reference to BytesMut pool let bytes_pool = self.bytes_pool.clone(); - // Procesar chunks en paralelo + // Process chunks in parallel for chunk in chunks { let file_clone = file.clone(); let result_clone = result_mutex.clone(); let semaphore_clone = self.concurrency_limiter.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 { - // Adquirir permiso del semáforo + // Acquire semaphore permit 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; - // Asegurar que tenga suficiente capacidad + // Ensure it has sufficient capacity if chunk_buffer.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); - // 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?; - // Posicionar y leer directamente en el BytesMut + // Position and read directly into the BytesMut file_handle.seek(SeekFrom::Start(chunk.start)).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 start_pos = chunk.start as usize; 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]); - // 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; - // Registrar progreso + // Log progress debug!("Chunk {} processed: {} bytes from offset {}", chunk.index, chunk.size, chunk.start); @@ -303,10 +303,10 @@ impl ParallelFileProcessor { tasks.push(task); } - // Esperar a que todas las tareas terminen + // Wait for all tasks to complete let results = join_all(tasks).await; - // Verificar errores + // Check for errors for (i, task_result) in results.into_iter().enumerate() { match task_result { Ok(Ok(())) => {}, @@ -321,7 +321,7 @@ impl ParallelFileProcessor { } } - // Obtener el resultado final y convertir a Vec + // Get the final result and convert to Vec let result_buffer = result_mutex.lock().await; let result_vec = result_buffer.to_vec(); @@ -329,8 +329,8 @@ impl ParallelFileProcessor { Ok(result_vec) } - /// Escribe un archivo en paralelo desde un buffer - /// Implementación optimizada usando BytesMut/Bytes para reducir copias de memoria + /// Writes a file in parallel from a buffer + /// Optimized implementation using BytesMut/Bytes to reduce memory copies pub async fn write_file_parallel( &self, file_path: &PathBuf, @@ -338,56 +338,56 @@ impl ParallelFileProcessor { ) -> Result<(), FileRepositoryError> { let file_size = content.len() as u64; - // Calcular chunks + // Calculate chunks let chunks = self.calculate_chunks(file_size); 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)); - // 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 .map_err(FileRepositoryError::IoError)?; 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", 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 .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); - // Crear tareas para cada chunk + // Create tasks for each chunk let mut tasks = Vec::with_capacity(chunks.len()); - // Procesar chunks en paralelo + // Process chunks in parallel for chunk in chunks { let file_clone = file.try_clone().await .map_err(FileRepositoryError::IoError)?; 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 end_idx = start_idx + chunk.size; let chunk_data = content_bytes.slice(start_idx..end_idx); - // Crear y lanzar tarea + // Create and launch task let task = task::spawn(async move { - // Adquirir permiso del semáforo + // Acquire semaphore permit let _permit = semaphore_clone.acquire().await.unwrap(); - // Posicionar y escribir + // Position and write let mut file_handle = file_clone; file_handle.seek(SeekFrom::Start(chunk.start)).await?; file_handle.write_all(&chunk_data).await?; - // Registrar progreso + // Log progress debug!("Chunk {} written: {} bytes at offset {}", chunk.index, chunk.size, chunk.start); @@ -397,10 +397,10 @@ impl ParallelFileProcessor { tasks.push(task); } - // Esperar a que todas las tareas terminen + // Wait for all tasks to complete let results = join_all(tasks).await; - // Verificar errores + // Check for errors for (i, task_result) in results.into_iter().enumerate() { match task_result { 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; file_handle.flush().await.map_err(FileRepositoryError::IoError)?; @@ -423,17 +423,17 @@ impl ParallelFileProcessor { 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)] async fn write_chunk_optimized( file: &mut File, offset: u64, data: Bytes ) -> Result<(), std::io::Error> { - // Preparar la escritura en la posición correcta + // Prepare writing at the correct position file.seek(SeekFrom::Start(offset)).await?; - // Escribir datos sin copias adicionales + // Write data without additional copies file.write_all(&data).await?; Ok(()) @@ -447,59 +447,59 @@ mod tests { #[tokio::test] 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(); - 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; let processor = ParallelFileProcessor::new(config); - // Crear directorio temporal + // Create temporary directory let temp_dir = tempdir().unwrap(); 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 mut test_data = Vec::with_capacity(size); for i in 0..size { 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(); - // Leer archivo en paralelo + // Read file in parallel 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, read_data); } #[tokio::test] async fn test_bytesmut_pool() { - // Crear pool + // Create pool let pool = BytesBufferPool::new(1024, 5); - // Obtener buffer + // Get buffer let mut buffer1 = pool.get_buffer().await; buffer1.put_slice(b"test data"); assert_eq!(&buffer1[..9], b"test data"); - // Devolver buffer al pool + // Return buffer to the pool 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; assert_eq!(buffer2.capacity(), 1024); - // El buffer debería estar vacío (clear) + // The buffer should be empty (cleared) assert_eq!(buffer2.len(), 0); } #[test] fn test_chunk_calculation() { - // Crear configuración de prueba + // Create test configuration let mut config = AppConfig::default(); config.concurrency.min_size_for_parallel_chunks_mb = 100; // 100MB config.concurrency.max_parallel_chunks = 4; @@ -507,18 +507,18 @@ mod tests { let processor = ParallelFileProcessor::new(config); - // Archivo pequeño (10MB) + // Small file (10MB) let small_file_size = 10 * 1024 * 1024; let chunks = processor.calculate_chunks(small_file_size); assert_eq!(chunks.len(), 1); assert_eq!(chunks[0].size as u64, small_file_size); - // Archivo grande (300MB) + // Large file (300MB) let large_file_size = 300 * 1024 * 1024; 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(); assert_eq!(total_size, large_file_size); } diff --git a/src/infrastructure/services/id_mapping_optimizer.rs b/src/infrastructure/services/id_mapping_optimizer.rs index 5bbdc3f1..fb342f80 100644 --- a/src/infrastructure/services/id_mapping_optimizer.rs +++ b/src/infrastructure/services/id_mapping_optimizer.rs @@ -10,60 +10,60 @@ use crate::infrastructure::services::id_mapping_service::{IdMappingService, IdMa use crate::common::errors::DomainError; 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; -/// Tiempo de vida del caché (en segundos) -const CACHE_TTL_SECONDS: u64 = 60 * 5; // 5 minutos +/// Cache time-to-live (in seconds) +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 { - /// Servicio base de mapeo de IDs + /// Base ID mapping service base_service: Arc, - /// Caché de ID por ruta (path -> id) + /// Path to ID cache (path -> id) path_to_id_cache: RwLock>, - /// Caché de ruta por ID (id -> path) + /// ID to path cache (id -> path) id_to_path_cache: RwLock>, - /// Contador de hits + /// Hit counter stats: RwLock, - /// Semáforo para limitar operaciones de batch + /// Semaphore to limit batch operations batch_limiter: Semaphore, - /// Cola de batch pendientes + /// Pending batch queue pending_batch: Mutex, } -/// Estadísticas del optimizador +/// Optimizer statistics #[derive(Debug, Default, Clone)] 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, - /// 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, - /// Número total de consultas get_or_create_id + /// Total number of get_or_create_id queries 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, - /// Número de batch realizados + /// Number of batch operations performed pub batch_operations: usize, - /// Número total de IDs procesados en batch + /// Total number of IDs processed in batch pub batch_items_processed: usize, - /// Último momento de limpieza de caché + /// Last cache cleanup timestamp pub last_cleanup: Option, } -/// Cola para operaciones batch +/// Queue for batch operations struct BatchQueue { - /// Rutas pendientes para obtener/crear ID + /// Pending paths to get/create ID path_to_id_requests: HashSet, - /// IDs pendientes para obtener ruta + /// Pending IDs to get path id_to_path_requests: HashSet, } @@ -76,43 +76,43 @@ impl Default for BatchQueue { } } -/// Resultado de una operación batch +/// Result of a batch operation struct BatchResult { - /// Mapeo de ruta a ID + /// Path to ID mapping path_to_id: HashMap, - /// Mapeo de ID a ruta + /// ID to path mapping id_to_path: HashMap, } 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) -> Self { Self { base_service, path_to_id_cache: RwLock::new(HashMap::with_capacity(1000)), id_to_path_cache: RwLock::new(HashMap::with_capacity(1000)), 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()), } } - /// Obtiene estadísticas del optimizador + /// Gets optimizer statistics pub async fn get_stats(&self) -> OptimizerStats { self.stats.read().await.clone() } - /// Limpia entradas expiradas del caché + /// Cleans expired cache entries pub async fn cleanup_cache(&self) { let now = Instant::now(); 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 initial_size = cache.len(); - // Retener solo entradas no expiradas + // Retain only non-expired entries cache.retain(|_, (_, timestamp)| { 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 initial_size = cache.len(); - // Retener solo entradas no expiradas + // Retain only non-expired entries cache.retain(|_, (_, timestamp)| { now.duration_since(*timestamp) < ttl }); @@ -139,14 +139,14 @@ impl IdMappingOptimizer { } } - // Actualizar estadísticas + // Update statistics { let mut stats = self.stats.write().await; stats.last_cleanup = Some(now); } } - /// Inicia tarea de limpieza periódica + /// Starts periodic cleanup task pub fn start_cleanup_task(optimizer: Arc) { tokio::spawn(async move { let cleanup_interval = Duration::from_secs(CACHE_TTL_SECONDS / 2); @@ -155,7 +155,7 @@ impl IdMappingOptimizer { tokio::time::sleep(cleanup_interval).await; optimizer.cleanup_cache().await; - // Loguear estadísticas periódicamente + // Log statistics periodically let stats = optimizer.get_stats().await; info!("ID Mapping Optimizer stats - Path queries: {}, hits: {} ({}%), ID queries: {}, hits: {} ({}%), Batch ops: {}, items: {}", 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, IdMappingError> { 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; if let Some((id, _)) = cache.get(&path_str) { @@ -204,7 +204,7 @@ impl IdMappingOptimizer { // Adquirir permiso para operación batch let _permit = self.batch_limiter.acquire().await.unwrap(); - // Obtener las solicitudes pendientes + // Get pending requests let (path_requests, id_requests) = { 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 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; if let Some((id, _)) = cache.get(&path_str) { - // Actualizar estadísticas + // Update statistics { let mut stats = self.stats.write().await; 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?; if let Some(id) = queued_result { return Ok(id); @@ -416,17 +416,17 @@ impl IdMappingPort for IdMappingOptimizer { // Trigger batch processing if enough items accumulated 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?; - // 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 id_cache = self.id_to_path_cache.write().await; let now = Instant::now(); - // Controlar tamaño del caché + // Control cache size if path_cache.len() >= MAX_CACHE_SIZE { warn!("Path-to-ID cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE); path_cache.clear(); @@ -451,11 +451,11 @@ impl IdMappingPort for IdMappingOptimizer { 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; if let Some((path_str, _)) = cache.get(id) { - // Actualizar estadísticas + // Update statistics { let mut stats = self.stats.write().await; 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?; - // Actualizar caché + // Update cache { let mut id_cache = self.id_to_path_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 path_str = path.to_string(); - // Controlar tamaño del caché + // Control cache size if id_cache.len() >= MAX_CACHE_SIZE { warn!("ID-to-path cache size reached limit ({}), clearing oldest entries", MAX_CACHE_SIZE); id_cache.clear(); diff --git a/src/interfaces/api/handlers/share_handler.rs b/src/interfaces/api/handlers/share_handler.rs index 97071be6..311ba21e 100644 --- a/src/interfaces/api/handlers/share_handler.rs +++ b/src/interfaces/api/handlers/share_handler.rs @@ -14,7 +14,7 @@ use crate::{ dtos::share_dto::{CreateShareDto, UpdateShareDto}, ports::share_ports::ShareUseCase }, - common::errors::{DomainError, ErrorKind}, + common::errors::ErrorKind, }; #[derive(Debug, Deserialize)] diff --git a/static/css/style.css b/static/css/style.css index 52817d26..a6a5e568 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,4 +1,4 @@ -/* Reset y estilos base */ +/* Reset and base styles */ * { box-sizing: border-box; margin: 0; @@ -13,7 +13,7 @@ body { overflow: hidden; } -/* Barra lateral */ +/* Sidebar */ .sidebar { width: 250px; background-color: #2a3042; @@ -86,7 +86,7 @@ body { text-align: center; } -/* Indicador de almacenamiento */ +/* Storage indicator */ .storage-container { margin: 20px 15px; background-color: #374e65; @@ -120,7 +120,7 @@ body { color: #f5f5f5; } -/* Contenido principal */ +/* Main content */ .main-content { flex-grow: 1; display: flex; @@ -128,7 +128,7 @@ body { overflow: hidden; } -/* Barra superior */ +/* Top bar */ .top-bar { height: 70px; background-color: white; @@ -186,7 +186,7 @@ body { background-color: #e64a29; } -/* Estilos para resultados de búsqueda */ +/* Styles for search results */ .search-results-header { display: flex; justify-content: space-between; @@ -261,7 +261,7 @@ body { font-weight: bold; } -/* Área de contenido */ +/* Content area */ .content-area { flex-grow: 1; padding: 20px; @@ -358,11 +358,11 @@ body { border: 2px dashed #ffc107; } -/* Estilos para las carpetas como en el mockup */ +/* Styles for folders as in the mockup */ .file-icon.folder-icon { width: 100px; height: 70px; - background-color: #ffeaa7; /* Color amarillo claro */ + background-color: #ffeaa7; /* Light yellow color */ border-radius: 8px; position: relative; margin-bottom: 10px; @@ -378,15 +378,15 @@ body { left: 0; right: 0; 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; } .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 { width: 100px; height: 70px; @@ -418,11 +418,11 @@ body { border-radius: 2px; } -/* Estilo para imágenes */ +/* Style for images */ .file-icon.image-icon { width: 100px; height: 70px; - background-color: #74b9ff; /* Fondo azul claro */ + background-color: #74b9ff; /* Light blue background */ border-radius: 4px; position: relative; margin-bottom: 10px; @@ -436,15 +436,15 @@ body { left: 15px; width: 20px; height: 20px; - background-color: #ffda79; /* Círculo amarillo como un sol */ + background-color: #ffda79; /* Yellow circle like a sun */ border-radius: 50%; } -/* Estilo para videos */ +/* Style for videos */ .file-icon.video-icon { width: 100px; height: 70px; - background-color: #111; /* Fondo negro */ + background-color: #111; /* Black background */ border-radius: 4px; position: relative; margin-bottom: 10px; @@ -459,10 +459,10 @@ body { height: 0; border-top: 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 { width: 100px; height: 70px; @@ -470,11 +470,11 @@ body { border-radius: 4px; position: relative; margin-bottom: 10px; - border-top: 3px solid #556ee6; /* Borde superior azul */ + border-top: 3px solid #556ee6; /* Blue top border */ overflow: hidden; } -/* Líneas que simulan código */ +/* Lines that simulate code */ .file-icon.code-icon::before, .file-icon.code-icon::after { content: ""; @@ -482,7 +482,7 @@ body { left: 10px; right: 10px; height: 2px; - background-color: #556ee6; /* Color azul para el código */ + background-color: #556ee6; /* Blue color for code */ } .file-icon.code-icon::before { @@ -495,14 +495,14 @@ body { 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-2, .file-icon.code-icon .code-line-3 { position: absolute; left: 10px; 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 { @@ -520,9 +520,9 @@ body { width: 50%; } -/* Colores específicos para distintos tipos de archivo de código */ +/* Specific colors for different code file types */ .file-icon.json-icon { - border-top-color: #ffb86c; /* Naranja para JSON */ + border-top-color: #ffb86c; /* Orange for JSON */ } .file-icon.json-icon::before, @@ -531,7 +531,7 @@ body { } .file-icon.js-icon { - border-top-color: #ffd43b; /* Amarillo para JavaScript */ + border-top-color: #ffd43b; /* Yellow for JavaScript */ } .file-icon.js-icon::before, @@ -540,7 +540,7 @@ body { } .file-icon.html-icon { - border-top-color: #e34c26; /* Rojo para HTML */ + border-top-color: #e34c26; /* Red for HTML */ } .file-icon.html-icon::before, @@ -549,7 +549,7 @@ body { } .file-icon.css-icon { - border-top-color: #2965f1; /* Azul para CSS */ + border-top-color: #2965f1; /* Blue for CSS */ } .file-icon.css-icon::before, @@ -558,7 +558,7 @@ body { } .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, @@ -1570,7 +1570,7 @@ header { color: #ffc107; } -/* Estilos para la papelera */ +/* Styles for trash */ .trash-item { position: relative; } @@ -1634,7 +1634,7 @@ header { grid-column: 1 / -1; } -/* Botón de peligro para vaciar papelera */ +/* Danger button for emptying trash */ .btn-danger { background-color: #f44336; color: white;