diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8544125..fa73ec8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -108,11 +108,11 @@ android { } dependencies { - implementation("androidx.core:core-ktx:1.12.0") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.11.0") + implementation("androidx.core:core-ktx:1.13.1") + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("com.google.android.material:material:1.12.0") implementation("org.khronos.openxr:openxr_loader_for_android:1.0.34") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1") // T6.4: EncryptedSharedPreferences para credenciais de servidores SMB — // NUNCA armazenar senha em texto plano (doc, secao 6, aviso "Credenciais"). implementation("androidx.security:security-crypto:1.1.0") @@ -127,7 +127,7 @@ dependencies { ksp("androidx.room:room-compiler:2.6.1") // T8.1 / T8.2: ExifInterface para metadados de fotos (orientação EXIF e XMP GPano 360) - implementation("androidx.exifinterface:exifinterface:1.3.7") + implementation("androidx.exifinterface:exifinterface:1.4.0") // JVM unit tests (app/src/test) — logica pura do file browser // (MediaSorter, DirectoryNavigator, DirectoryLister, cache-key do @@ -135,7 +135,7 @@ dependencies { // porque nenhuma dessas classes toca em APIs Android reais nos // caminhos testados. Ver docs/TESTING-PLAN.md. testImplementation("junit:junit:4.13.2") - testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1") // R-05 (PHASE-0.4-08-VERIFICACAO-PROFUNDA.md): driver SQLite puro-JVM (sem dependencia // Android) para exercitar o SQL bruto das migrations do Room (AppDatabase.MIGRATION_*_SQL) diff --git a/rust/media-logic/Cargo.toml b/rust/media-logic/Cargo.toml index 632d982..b42f9f7 100644 --- a/rust/media-logic/Cargo.toml +++ b/rust/media-logic/Cargo.toml @@ -13,5 +13,5 @@ edition = "2021" # run under plain `cargo test` on a laptop has to live in a crate that # doesn't depend on `core` or `audio`. [dependencies] -chardetng = "0.1" +chardetng = "1.0" encoding_rs = "0.8" diff --git a/rust/media-logic/src/subtitle.rs b/rust/media-logic/src/subtitle.rs index 7e75e94..df2d1b3 100644 --- a/rust/media-logic/src/subtitle.rs +++ b/rust/media-logic/src/subtitle.rs @@ -314,9 +314,9 @@ pub fn detect_and_decode(bytes: &[u8]) -> String { } // 3. Auto-detecção de encoding com chardetng (Mozilla) - let mut detector = chardetng::EncodingDetector::new(); + let mut detector = chardetng::EncodingDetector::new(chardetng::Iso2022JpDetection::Allow); detector.feed(bytes, true); - let encoding = detector.guess(None, true); + let encoding = detector.guess(None, chardetng::Utf8Detection::Allow); let (cow, _, _) = encoding.decode(bytes); cow.into_owned() diff --git a/rust/protocols/Cargo.toml b/rust/protocols/Cargo.toml index dddcf1b..a3db9db 100644 --- a/rust/protocols/Cargo.toml +++ b/rust/protocols/Cargo.toml @@ -9,7 +9,7 @@ edition = "2024" # timeout/reconexao em Wi-Fi instavel do Quest. Async/tokio por baixo; ver # smb/mod.rs sobre como isso e ponte para o mundo sincrono que o FFmpeg # custom I/O exige. -smb2 = "0.18" +smb2 = "0.27" tokio = { version = "1", features = ["rt", "net", "time", "macros"] } # Cliente HTTPS para o fallback de custom I/O (T7.2) — usado apenas quando o # libavformat bundled neste projeto nao tem suporte nativo a TLS (ver nota em @@ -20,18 +20,14 @@ log = "0.4" # Cliente FTP (T6.1) — puro Rust, API bloqueante nativa (nao precisa de # tokio::block_on como SMB/SFTP). Sem feature de TLS: FTP em si e sempre # texto claro (ver aviso do doc, secao 6); FTPS fica fora do escopo do MVP. -suppaftp = { version = "6", default-features = false } +suppaftp = { version = "12", default-features = false } # Cliente SFTP (T6.2) — russh + russh-sftp sao puro Rust (nenhuma dependencia # em libssh2/openssl nativos), evitando o cross-compile C descrito como # armadilha no doc (secao 6, aviso "libssh2 cross-compile"). Async/tokio por # baixo, igual ao cliente SMB (ver smb/mod.rs) — ponte pro FFmpeg custom I/O # sincrono via block_on por chamada, mesmo padrao. -russh = "0.45" -russh-sftp = "2" -# Exigido para implementar `russh::client::Handler` (trait `async fn` via -# `#[async_trait]`) — russh usa a mesma crate internamente, mas o atributo -# precisa estar disponivel para quem implementa o trait tambem. -async-trait = "0.1" +russh = "0.64" +russh-sftp = "3.0.1" # `join_all` para disparar sub-leituras de um mesmo read_range CONCORRENTEMENTE # sobre a mesma sessao SMB/SFTP (ambas as crates multiplexam por MessageId/ # request-id — ver docs de smb/mod.rs e sftp/mod.rs). So `future::join_all` diff --git a/rust/protocols/src/ftp/mod.rs b/rust/protocols/src/ftp/mod.rs index 4ef3b56..ef9e4b5 100644 --- a/rust/protocols/src/ftp/mod.rs +++ b/rust/protocols/src/ftp/mod.rs @@ -48,7 +48,7 @@ use crate::prefetch::RangeSource; use std::io::{self, Read}; use std::net::ToSocketAddrs; use std::time::Duration; -use suppaftp::list::File as FtpListFile; +use suppaftp::list::{File as FtpListFile, ListParser}; use suppaftp::types::FileType; use suppaftp::{FtpStream, Mode}; @@ -133,7 +133,7 @@ pub fn list_directory(t: &FtpTarget, path: &str) -> Result, Str fn list_directory_on(stream: &mut FtpStream, path: &str) -> Result, String> { let dir = if path.is_empty() { None } else { Some(path) }; match stream.mlsd(dir) { - Ok(lines) => Ok(lines.into_iter().filter_map(|line| FtpListFile::from_mlsx_line(&line).ok()).map(from_list_file).collect()), + Ok(lines) => Ok(lines.into_iter().filter_map(|line| ListParser::parse_mlsd(&line).ok()).map(from_list_file).collect()), Err(_) => { let lines = stream.list(dir).map_err(|e| e.to_string())?; Ok(lines @@ -238,21 +238,12 @@ impl FtpFileSource { Ok(()) } - /// Fecha o stream de dados atual (se houver), com o encerramento correto - /// pro estado em que ele esta: `finalize_retr_stream` se o RETR ja - /// terminou sozinho, `abort` (envia `ABOR`) se foi interrompido no meio — - /// ver doc de `data_exhausted` no struct. + /// Fecha o stream de dados atual (se houver). Em suppaftp 12+, o + /// `TransferStream` implementa `Drop` que encerra o socket de dados e + /// lê a resposta de conclusão (`226`) da conexão de controle automaticamente, + /// garantindo que o canal de controle continue sincronizado. fn close_data_stream(&mut self) { - if let Some(stream) = self.data.take() { - let result = if self.data_exhausted { - self.control.finalize_retr_stream(stream) - } else { - self.control.abort(stream) - }; - if let Err(e) = result { - log::warn!("FtpFileSource: erro ao fechar stream de dados anterior: {e}"); - } - } + let _ = self.data.take(); self.data_exhausted = false; } diff --git a/rust/protocols/src/prefetch.rs b/rust/protocols/src/prefetch.rs index 76b106e..406fa5f 100644 --- a/rust/protocols/src/prefetch.rs +++ b/rust/protocols/src/prefetch.rs @@ -837,8 +837,8 @@ mod tests { } assert_eq!(out, data[jump_to as usize..jump_to as usize + out.len()]); - // Aguarda determinísticamente os 3 passos da rampa - let requested = wait_for_requests(&sizes, 3); + // Aguarda determinísticamente os 4 passos totais (construção + rampa de 3 passos) + let requested = wait_for_requests(&sizes, 4); // `ends_with` (nao `==`): a construcao ja dispara um kick(0, seek_block) // proprio antes do seek acontecer, que tambem aparece na lista. assert!( diff --git a/rust/protocols/src/sftp/mod.rs b/rust/protocols/src/sftp/mod.rs index 2764e37..4ed3483 100644 --- a/rust/protocols/src/sftp/mod.rs +++ b/rust/protocols/src/sftp/mod.rs @@ -78,7 +78,7 @@ use crate::chunking::split_range; use crate::prefetch::RangeSource; use futures_util::future::join_all; use russh::client::{self, Handler}; -use russh::keys::key::PublicKey; +use russh::keys::{PrivateKeyWithHashAlg, PublicKeyOrCertificate}; use russh::Disconnect; use russh_sftp::client::error::Error as SftpError; use russh_sftp::client::{RawSftpSession, SftpSession}; @@ -155,11 +155,10 @@ const SFTP_CHUNK_RETRY_TIMEOUT: Duration = Duration::from_millis(2000); /// outros callbacks usam a implementacao default do trait. struct SftpHandler; -#[async_trait::async_trait] impl Handler for SftpHandler { type Error = russh::Error; - async fn check_server_key(&mut self, _server_public_key: &PublicKey) -> Result { + async fn check_server_key(&mut self, _server_public_key: &PublicKeyOrCertificate) -> Result { Ok(true) } } @@ -195,8 +194,12 @@ async fn connect_and_auth(t: &SftpTarget, keepalive: bool) -> Result Result ClientConfig { compression: true, dfs_enabled: true, dfs_target_overrides: Default::default(), + connect_options: None, } }