Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -127,15 +127,15 @@ 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
// ThumbnailGenerator) roda direto na JVM, sem emulador/Robolectric,
// 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)
Expand Down
2 changes: 1 addition & 1 deletion rust/media-logic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 2 additions & 2 deletions rust/media-logic/src/subtitle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
12 changes: 4 additions & 8 deletions rust/protocols/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`
Expand Down
23 changes: 7 additions & 16 deletions rust/protocols/src/ftp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -133,7 +133,7 @@ pub fn list_directory(t: &FtpTarget, path: &str) -> Result<Vec<FtpDirEntry>, Str
fn list_directory_on(stream: &mut FtpStream, path: &str) -> Result<Vec<FtpDirEntry>, 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
Expand Down Expand Up @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions rust/protocols/src/prefetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
13 changes: 8 additions & 5 deletions rust/protocols/src/sftp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<bool, Self::Error> {
async fn check_server_key(&mut self, _server_public_key: &PublicKeyOrCertificate) -> Result<bool, Self::Error> {
Ok(true)
}
}
Expand Down Expand Up @@ -195,8 +194,12 @@ async fn connect_and_auth(t: &SftpTarget, keepalive: bool) -> Result<client::Han

let authenticated = if let Some(pem) = t.private_key.as_deref().filter(|k| !k.is_empty()) {
let key_pair = russh::keys::decode_secret_key(pem, None).map_err(|e| format!("chave privada invalida: {e}"))?;
let key_with_hash = PrivateKeyWithHashAlg::new(
Arc::new(key_pair),
session.best_supported_rsa_hash().await.map_err(|e| e.to_string())?.flatten(),
);
session
.authenticate_publickey(t.username.clone(), Arc::new(key_pair))
.authenticate_publickey(t.username.clone(), key_with_hash)
.await
.map_err(|e| e.to_string())?
} else {
Expand All @@ -206,7 +209,7 @@ async fn connect_and_auth(t: &SftpTarget, keepalive: bool) -> Result<client::Han
.map_err(|e| e.to_string())?
};

if !authenticated {
if !authenticated.success() {
log::warn!("SFTP: autenticacao rejeitada para usuario {}", t.username);
return Err("autenticacao SFTP falhou (usuario/senha ou chave privada invalidos)".to_string());
}
Expand Down
1 change: 1 addition & 0 deletions rust/protocols/src/smb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ fn client_config(t: &SmbTarget, auto_reconnect: bool) -> ClientConfig {
compression: true,
dfs_enabled: true,
dfs_target_overrides: Default::default(),
connect_options: None,
}
}

Expand Down
Loading