chore(i18n): finish translating code comments and scripts to English
CI / Format and clippy (push) Successful in 1m57s
CI / Dependency audit (push) Successful in 13s
CI / Check desktop app (Linux) (push) Successful in 3m54s
CI / Tests (Linux) (push) Failing after 7m25s
Release / Create release (push) Successful in 6s
Release / CLI (linux-aarch64) (push) Successful in 1m48s
Release / CLI (linux-x86_64) (push) Successful in 2m39s
Release / Desktop app (linux-aarch64) (push) Successful in 10m9s
Release / Desktop app (linux-x86_64) (push) Successful in 12m20s
Release / Desktop app (windows) (push) Successful in 7m32s
Release / Build latest.json (push) Successful in 5s
CI / Format and clippy (push) Successful in 1m57s
CI / Dependency audit (push) Successful in 13s
CI / Check desktop app (Linux) (push) Successful in 3m54s
CI / Tests (Linux) (push) Failing after 7m25s
Release / Create release (push) Successful in 6s
Release / CLI (linux-aarch64) (push) Successful in 1m48s
Release / CLI (linux-x86_64) (push) Successful in 2m39s
Release / Desktop app (linux-aarch64) (push) Successful in 10m9s
Release / Desktop app (linux-x86_64) (push) Successful in 12m20s
Release / Desktop app (windows) (push) Successful in 7m32s
Release / Build latest.json (push) Successful in 5s
Completes the English-only standardization for code, comments, scripts and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en, untouched). Covers the remaining crates/, apps/gui/ui/ and apps/gui/src-tauri/ comments, .gitea/ workflow and template comments, .githooks/ hooks, and the scripts/ source comments for the files already renamed in a prior commit (check.sh, generate-logo.py, install-hooks.sh). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
This commit is contained in:
1 parent
da212e6926
commit
6328220c60
129 files changed
+1961
-1834
No files matched your search
@@ -1,5 +1,5 @@
|
||||
//! Camada de serviço usada pela interface gráfica (Tauri e modo `guara ui`):
|
||||
//! jobs em segundo plano (varredura, recuperação, imagem) e despachante RPC.
|
||||
//! Background service layer backing the GUI (Tauri and `guara ui` mode):
|
||||
//! manages scan/recovery/imaging jobs, sessions, and JSON-RPC dispatch.
|
||||
|
||||
use crate::device;
|
||||
use crate::error::{Error, Result};
|
||||
@@ -49,7 +49,7 @@ struct State {
|
||||
report: Option<Value>,
|
||||
started: Instant,
|
||||
finished_in: Option<f64>,
|
||||
/// Leitura de datas EXIF/dimensões em andamento: (feitos, total).
|
||||
/// Background EXIF/dimension enrichment progress: `(done, total)`.
|
||||
enrich: Option<(u64, u64)>,
|
||||
recoveries: Vec<recover::RecoverReport>,
|
||||
started_unix: Option<i64>,
|
||||
@@ -64,11 +64,11 @@ pub struct Job {
|
||||
cancel: AtomicBool,
|
||||
stop_requested: AtomicBool,
|
||||
src: Mutex<Option<Arc<dyn Source>>>,
|
||||
/// Caminho atualmente usado para ler a origem (pode mudar se o disco voltar com outro nome).
|
||||
/// Active device/image path used to read the source (may update if reattached under a new node).
|
||||
src_path: Mutex<String>,
|
||||
/// Impressão digital do disco (reconhecer o mesmo disco depois de reconectar).
|
||||
/// Source disk content fingerprint (used to recognize the drive after reconnection).
|
||||
fingerprint: Mutex<Option<String>>,
|
||||
/// Arquivo de sessão salvo automaticamente.
|
||||
/// Path to the autosaved session file.
|
||||
session_file: Mutex<Option<PathBuf>>,
|
||||
st: Mutex<State>,
|
||||
}
|
||||
@@ -213,7 +213,7 @@ impl Engine {
|
||||
job
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- varredura
|
||||
// ---------------------------------------------------------------- scan
|
||||
|
||||
pub fn start_scan(&self, path: &str, mut opts: ScanOptions) -> Result<String> {
|
||||
let raw = open_path(Path::new(path)).map_err(|e| open_error(path, e))?;
|
||||
@@ -282,7 +282,7 @@ impl Engine {
|
||||
}
|
||||
s.checkpoint.as_ref().is_some_and(|c| c.io_lost)
|
||||
};
|
||||
// Sem origem, ou depois de uma desconexão: reabre o disco (mesmo com outro nome).
|
||||
// Source not yet open or previously disconnected: try to reopen (even under a new device name).
|
||||
if j.source().is_none() || io_lost {
|
||||
self.reconnect(&j).map_err(|e| {
|
||||
Error::msg(format!(
|
||||
@@ -359,14 +359,14 @@ impl Engine {
|
||||
Ok(json!({ "total": total, "items": page }))
|
||||
}
|
||||
|
||||
/// Contagens para a navegação lateral (tipos, datas, pastas, câmeras).
|
||||
/// Sidebar navigation facet counts (categories, dates, folders, cameras).
|
||||
pub fn facets(&self, id: &str) -> Result<Value> {
|
||||
let j = self.job(id)?;
|
||||
let s = j.st.lock().unwrap();
|
||||
Ok(crate::explorer::facets(&s.found))
|
||||
}
|
||||
|
||||
/// Esquema de um banco SQLite achado, com o número de linhas de cada tabela.
|
||||
/// Schema of a recovered SQLite database, including row counts per table.
|
||||
pub fn db_info(&self, id: &str, file: u64) -> Result<Value> {
|
||||
let (f, src) = self.open_file(id, file)?;
|
||||
let fs = recover::FoundSource {
|
||||
@@ -383,7 +383,7 @@ impl Engine {
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Uma página de linhas de uma tabela (ou os registros apagados prováveis dela).
|
||||
/// A page of live table rows (or carvable deleted records from free blocks).
|
||||
pub fn db_rows(
|
||||
&self,
|
||||
id: &str,
|
||||
@@ -415,13 +415,13 @@ impl Engine {
|
||||
Ok(serde_json::to_value(&page)?)
|
||||
}
|
||||
|
||||
/// Metadados detalhados (EXIF, PDF, Office, vídeo…) de um arquivo.
|
||||
/// Detailed format metadata (EXIF, PDF, Office, video, etc.) for a recovered file.
|
||||
pub fn metadata(&self, id: &str, file: u64) -> Result<crate::meta::Meta> {
|
||||
let (f, src) = self.open_file(id, file)?;
|
||||
Ok(crate::meta::extract(src.as_ref(), &f))
|
||||
}
|
||||
|
||||
/// Miniatura JPEG (cache em memória; decodificações limitadas em paralelo).
|
||||
/// Generates or fetches an in-memory cached JPEG thumbnail (with bounded concurrency).
|
||||
pub fn thumb(&self, job: &str, id: u64, size: u32) -> std::result::Result<Arc<Vec<u8>>, u16> {
|
||||
let size = size.clamp(48, 640);
|
||||
let key = (job.to_string(), id, size);
|
||||
@@ -445,7 +445,7 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Achado + origem para leitura posicional (streaming de pré-visualização).
|
||||
/// Returns `(Found, Source)` for positional streaming preview.
|
||||
pub fn open_file(&self, id: &str, file: u64) -> Result<(Found, Arc<dyn Source>)> {
|
||||
let j = self.job(id)?;
|
||||
let s = j.st.lock().unwrap();
|
||||
@@ -456,7 +456,7 @@ impl Engine {
|
||||
Ok((s.found[i].clone(), j.source().ok_or_else(no_source)?))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- recuperação
|
||||
// ---------------------------------------------------------------- recovery
|
||||
|
||||
pub fn start_recover(
|
||||
&self,
|
||||
@@ -575,13 +575,13 @@ impl Engine {
|
||||
Ok(job.id.clone())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- imagem
|
||||
// ---------------------------------------------------------------- disk imaging
|
||||
|
||||
pub fn start_image(&self, path: &str, dest: &str) -> Result<String> {
|
||||
self.start_image_as(path, dest, &image::ImageFormat::Raw)
|
||||
}
|
||||
|
||||
/// Cria a imagem em bruto ou em outro formato forense (E01 ou AFF4).
|
||||
/// Creates a raw, E01, or AFF4 forensic disk image.
|
||||
pub fn start_image_as(
|
||||
&self,
|
||||
path: &str,
|
||||
@@ -644,9 +644,9 @@ impl Engine {
|
||||
Ok(job.id.clone())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- exportação e relatório
|
||||
// ---------------------------------------------------------------- export and report
|
||||
|
||||
/// CSV dos achados que atendem ao filtro (ou todos). Devolve quantas linhas gravou.
|
||||
/// Exports matching findings (or all findings) to CSV. Returns the number of rows written.
|
||||
pub fn export_csv(&self, id: &str, path: &str, q: Option<&Query>) -> Result<usize> {
|
||||
let j = self.job(id)?;
|
||||
let s = j.st.lock().unwrap();
|
||||
@@ -662,7 +662,7 @@ impl Engine {
|
||||
Ok(items.len())
|
||||
}
|
||||
|
||||
/// Relatório HTML de recuperação (identificação do caso, origem, achados, hashes).
|
||||
/// Generates an HTML or PDF forensic recovery report (case info, source, findings, hashes).
|
||||
pub fn export_report(
|
||||
&self,
|
||||
id: &str,
|
||||
@@ -714,7 +714,7 @@ impl Engine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- busca de texto e hash da origem
|
||||
// ---------------------------------------------------------------- text search and source hashing
|
||||
|
||||
pub fn start_search(&self, path: &str, opts: crate::search::SearchOpts) -> Result<String> {
|
||||
if opts.terms.iter().all(|t| t.trim().is_empty()) {
|
||||
@@ -776,7 +776,7 @@ impl Engine {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Calcula MD5/SHA-1/SHA-256 da origem inteira (verificação de integridade).
|
||||
/// Computes MD5/SHA-1/SHA-256 across the entire source (integrity verification job).
|
||||
pub fn start_hash(&self, path: &str) -> Result<String> {
|
||||
let src = open_path(Path::new(path)).map_err(|e| open_error(path, e))?;
|
||||
let job = self.register(
|
||||
@@ -833,7 +833,7 @@ impl Engine {
|
||||
Ok(job.id.clone())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- sessões
|
||||
// ---------------------------------------------------------------- sessions
|
||||
|
||||
pub fn save_session(&self, id: &str, path: &str) -> Result<()> {
|
||||
let j = self.job(id)?;
|
||||
@@ -860,7 +860,7 @@ impl Engine {
|
||||
"O arquivo {path} não é uma sessão do Guará válida (ou foi criado por uma versão incompatível): {e}"
|
||||
))
|
||||
})?;
|
||||
// Clicar de novo numa sessão já aberta reaproveita o job em vez de criar outro igual.
|
||||
// Re-opening an already loaded session reuses its existing job ID.
|
||||
let want = Path::new(path);
|
||||
if let Some(j) = self
|
||||
.jobs
|
||||
@@ -899,8 +899,8 @@ impl Engine {
|
||||
Status::Paused
|
||||
};
|
||||
}
|
||||
// A sessão sempre abre. Tenta religar o disco (inclusive se ele voltou com outro nome);
|
||||
// sem a origem ainda dá para navegar, exportar e gerar relatório.
|
||||
// Always load the session metadata. Attempt to reconnect the source disk (even under a new path);
|
||||
// if the source is offline, browsing, filtering, CSV export, and reporting still work.
|
||||
match self.reconnect(&job) {
|
||||
Ok(Some(nome)) => job.st.lock().unwrap().warnings.push(format!(
|
||||
"O disco foi reconhecido de novo como {nome} (o nome mudou desde a varredura)."
|
||||
@@ -911,7 +911,7 @@ impl Engine {
|
||||
data.target
|
||||
)),
|
||||
}
|
||||
// continua salvando automaticamente no mesmo arquivo se a varredura for retomada
|
||||
// keep autosaving to the same file if the scan is resumed
|
||||
*job.session_file.lock().unwrap() = Some(PathBuf::from(path));
|
||||
crate::audit::record(
|
||||
"session.load",
|
||||
@@ -923,9 +923,9 @@ impl Engine {
|
||||
Ok(job.id.clone())
|
||||
}
|
||||
|
||||
/// Reabre a origem da sessão: tenta o caminho atual e o original e, se o disco voltou com
|
||||
/// outro nome, procura entre os dispositivos conectados o que tem o mesmo conteúdo.
|
||||
/// `Ok(Some(caminho))` quando achou o disco num caminho diferente do esperado.
|
||||
/// Reopens the session's source: tries the current and original paths first, then scans attached
|
||||
/// devices for a matching fingerprint if the drive reconnected under a new device node.
|
||||
/// Returns `Ok(Some(new_path))` when found at a different device path.
|
||||
fn reconnect(&self, j: &Arc<Job>) -> Result<Option<String>> {
|
||||
let fp = j.fingerprint.lock().unwrap().clone();
|
||||
let size =
|
||||
@@ -970,8 +970,8 @@ impl Engine {
|
||||
))
|
||||
}
|
||||
|
||||
/// Aponta uma sessão carregada para a origem (disco ou imagem) atual.
|
||||
/// Recusa uma origem que não seja o mesmo disco, a menos que `force`.
|
||||
/// Rebinds a loaded session to a user-selected source device or disk image.
|
||||
/// Rejects a source whose fingerprint/size mismatches the session unless `force` is set.
|
||||
pub fn relink(&self, id: &str, path: &str, force: bool) -> Result<()> {
|
||||
let j = self.job(id)?;
|
||||
let src = open_path(Path::new(path)).map_err(|e| open_error(path, e))?;
|
||||
@@ -1010,7 +1010,7 @@ impl Engine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Discos conectados que podem ser a origem desta sessão (mesmo tamanho primeiro).
|
||||
/// Lists attached disks that could match this session's source (same-size candidates first).
|
||||
pub fn candidates(&self, id: &str) -> Result<Value> {
|
||||
let j = self.job(id)?;
|
||||
let fp = j.fingerprint.lock().unwrap().clone();
|
||||
@@ -1042,9 +1042,9 @@ impl Engine {
|
||||
Ok(json!(out))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- sessões automáticas
|
||||
// ---------------------------------------------------------------- autosaved sessions
|
||||
|
||||
/// Sessões salvas automaticamente (mais recentes primeiro).
|
||||
/// Lists autosaved sessions (most recent first).
|
||||
pub fn sessions_list(&self) -> Value {
|
||||
let dir = sessions_dir();
|
||||
let mut items: Vec<Value> = vec![];
|
||||
@@ -1071,7 +1071,7 @@ impl Engine {
|
||||
json!(items)
|
||||
}
|
||||
|
||||
/// Apaga uma sessão automática (e a miniatura de metadados).
|
||||
/// Deletes an autosaved session file (and its `.meta.json` sidecar).
|
||||
pub fn session_delete(&self, path: &str) -> Result<()> {
|
||||
let p = Path::new(path);
|
||||
if p.parent() != Some(sessions_dir().as_path()) {
|
||||
@@ -1162,7 +1162,7 @@ impl Engine {
|
||||
"health" => json!(crate::health::check(&s("path")?)),
|
||||
"open_url" => {
|
||||
let u = s("url")?;
|
||||
// só mapas do OpenStreetMap: evita abrir endereços arbitrários a partir de metadados
|
||||
// restrict to OpenStreetMap URLs: never open arbitrary URLs derived from untrusted file metadata
|
||||
if !u.starts_with("https://www.openstreetmap.org/") {
|
||||
return Err(Error::msg("endereço não permitido"));
|
||||
}
|
||||
@@ -1332,7 +1332,7 @@ struct SessionFile {
|
||||
|
||||
use crate::explorer::cat_key;
|
||||
|
||||
/// 503 quando a origem da sessão está desconectada; 404 nos demais casos.
|
||||
/// Returns HTTP 503 when the session source is disconnected, or 404 otherwise.
|
||||
fn status_of(e: Error) -> u16 {
|
||||
if e.to_string().contains("não está disponível") {
|
||||
503
|
||||
@@ -1356,7 +1356,7 @@ fn open_error(path: &str, e: std::io::Error) -> Error {
|
||||
}
|
||||
|
||||
fn spawn_scan(job: Arc<Job>, cp: Option<Checkpoint>) {
|
||||
// Salva a sessão sozinho enquanto a varredura roda (e ao terminar/pausar).
|
||||
// Periodically autosaves the session while scanning (and upon completion or pause).
|
||||
let saver = job.clone();
|
||||
std::thread::spawn(move || autosave_loop(saver));
|
||||
std::thread::spawn(move || {
|
||||
@@ -1420,8 +1420,8 @@ fn spawn_scan(job: Arc<Job>, cp: Option<Checkpoint>) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Lê EXIF/dimensões das imagens achadas (em segundo plano) para permitir
|
||||
/// navegar por data, câmera e resolução.
|
||||
/// Background worker that extracts EXIF dates, camera models, GPS, and image dimensions
|
||||
/// so findings can be browsed by date, camera, and resolution.
|
||||
fn spawn_enrich(job: Arc<Job>) {
|
||||
std::thread::spawn(move || {
|
||||
let Some(src) = job.source() else { return };
|
||||
@@ -1487,8 +1487,8 @@ pub fn base64(data: &[u8]) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
/// Tipo MIME para servir a pré-visualização. Formatos que poderiam executar
|
||||
/// script (HTML/SVG/XML) saem como texto puro.
|
||||
/// Returns a safe MIME type for preview streaming. Formats capable of executing
|
||||
/// scripts (HTML/SVG/XML) are served as `text/plain`.
|
||||
pub fn mime_for(ext: &str) -> &'static str {
|
||||
match ext.to_ascii_lowercase().as_str() {
|
||||
"jpg" | "jpeg" => "image/jpeg",
|
||||
@@ -1513,13 +1513,13 @@ pub fn mime_for(ext: &str) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trecho de um arquivo achado, pronto para responder a uma requisição HTTP
|
||||
/// (com suporte a `Range`) tanto no modo `guara ui` quanto no app Tauri.
|
||||
/// Byte slice of a recovered file, ready to answer an HTTP `Range` request
|
||||
/// in both `guara ui` mode and the Tauri custom protocol handler.
|
||||
pub struct FileSlice {
|
||||
found: Found,
|
||||
src: Arc<dyn Source>,
|
||||
pub start: u64,
|
||||
/// Inclusivo.
|
||||
/// Inclusive end byte offset.
|
||||
pub end: u64,
|
||||
pub total: u64,
|
||||
pub partial: bool,
|
||||
@@ -1549,7 +1549,7 @@ impl FileSlice {
|
||||
("Accept-Ranges", "bytes".into()),
|
||||
("Cache-Control", "no-store".into()),
|
||||
("X-Content-Type-Options", "nosniff".into()),
|
||||
// Conteúdo recuperado é não confiável: nunca executa script nem carrega nada.
|
||||
// Recovered content is untrusted: forbid script execution and external resource loading via CSP.
|
||||
(
|
||||
"Content-Security-Policy",
|
||||
"sandbox; default-src 'none'".into(),
|
||||
@@ -1569,7 +1569,7 @@ impl FileSlice {
|
||||
}
|
||||
h
|
||||
}
|
||||
/// Lê o trecho inteiro em memória (limitado a `cap` bytes).
|
||||
/// Reads the requested byte range into memory (capped at `cap` bytes).
|
||||
pub fn read_all(&self, cap: u64) -> std::io::Result<Vec<u8>> {
|
||||
let n = self.len().min(cap) as usize;
|
||||
let mut out = vec![0u8; n];
|
||||
@@ -1621,7 +1621,7 @@ impl std::io::Read for FileSliceReader {
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Resolve `/file/<job>/<id>` + cabeçalho Range. `Err(status)` em caso de falha.
|
||||
/// Resolves `/file/<job>/<id>` with optional `Range` header. Returns `Err(http_status)` on failure.
|
||||
pub fn file_slice(
|
||||
&self,
|
||||
job: &str,
|
||||
@@ -1663,9 +1663,9 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- sessões automáticas
|
||||
// ---------------------------------------------------------------- autosaved sessions
|
||||
|
||||
/// Reaplica ao disco reaberto a montagem (volumes desbloqueados, RAID) da sessão.
|
||||
/// Re-applies the session's virtual disk assembly (unlocked volumes, RAID, LVM) to a reopened source.
|
||||
fn finish_source(j: &Job, raw: Arc<dyn Source>) -> Arc<dyn Source> {
|
||||
match crate::assemble::assemble_source(raw.clone(), &j.opts.assembly) {
|
||||
Ok(a) => a.source,
|
||||
@@ -1700,7 +1700,7 @@ fn new_session_path(target: &str) -> PathBuf {
|
||||
|
||||
fn snapshot(j: &Job) -> SessionFile {
|
||||
let s = j.st.lock().unwrap();
|
||||
// Senhas e chaves nunca vão para o arquivo de sessão (só as chaves de volume, se o usuário pediu).
|
||||
// Passwords and raw keyfiles are stripped before saving (only derived volume keys are kept if opted in).
|
||||
let mut options = j.opts.clone();
|
||||
options.assembly.secrets.clear();
|
||||
if !options.assembly.remember_keys {
|
||||
@@ -1722,14 +1722,14 @@ fn snapshot(j: &Job) -> SessionFile {
|
||||
}
|
||||
}
|
||||
|
||||
/// Grava a sessão de forma atômica (arquivo temporário + renomear), com o dono certo.
|
||||
/// Atomically writes the session file (temp file + rename) owned by the invoking user.
|
||||
fn write_session(j: &Job, path: &Path) -> Result<()> {
|
||||
let data = snapshot(j);
|
||||
let tmp = path.with_extension("json.tmp");
|
||||
crate::owner::write_owned(&tmp, serde_json::to_vec(&data)?)?;
|
||||
std::fs::rename(&tmp, path)?;
|
||||
crate::owner::give_to_user(path);
|
||||
// metadados pequenos para listar as sessões recentes sem ler o arquivo inteiro
|
||||
// lightweight `.meta.json` sidecar so recent sessions can be listed without parsing full snapshots
|
||||
let status = if data.done {
|
||||
"concluída"
|
||||
} else if data.checkpoint.is_some() {
|
||||
@@ -1749,7 +1749,7 @@ fn write_session(j: &Job, path: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Salva de tempos em tempos (o intervalo cresce se a sessão for grande) e no fim.
|
||||
/// Autosaves periodically (backing off as snapshot write time grows) and once on completion.
|
||||
fn autosave_loop(job: Arc<Job>) {
|
||||
let mut last = Instant::now();
|
||||
let mut interval = std::time::Duration::from_secs(45);
|
||||
@@ -1766,7 +1766,7 @@ fn autosave_loop(job: Arc<Job>) {
|
||||
if write_session(&job, &path).is_err() {
|
||||
return;
|
||||
}
|
||||
// nunca gasta mais de ~10% do tempo salvando
|
||||
// spend at most ~10% of wall-clock time writing snapshots
|
||||
interval = (t0.elapsed() * 10).max(std::time::Duration::from_secs(45));
|
||||
last = Instant::now();
|
||||
if !running {
|
||||
|
||||
Reference in new issue
Block a user