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
+123
-48
@@ -1,4 +1,4 @@
|
||||
// Evita a janela de console extra no Windows em release.
|
||||
// Prevents an extra console window on Windows in release builds.
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use guara_core::service::Engine;
|
||||
@@ -6,23 +6,29 @@ use std::sync::Arc;
|
||||
|
||||
struct State(Arc<Engine>);
|
||||
|
||||
/// Ponte única entre a interface e o núcleo (mesmos métodos do modo `guara ui`).
|
||||
/// Single bridge between the UI and the core (same methods as `guara ui` mode).
|
||||
#[tauri::command]
|
||||
async fn rpc(state: tauri::State<'_, State>, method: String, params: serde_json::Value) -> Result<serde_json::Value, String> {
|
||||
async fn rpc(
|
||||
state: tauri::State<'_, State>,
|
||||
method: String,
|
||||
params: serde_json::Value,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
let eng = state.0.clone();
|
||||
tauri::async_runtime::spawn_blocking(move || eng.rpc(&method, params).map_err(|e| e.to_string()))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
eng.rpc(&method, params).map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
/// Versão nova encontrada na release do Gitea.
|
||||
/// New version found on Gitea releases.
|
||||
#[derive(serde::Serialize)]
|
||||
struct UpdateInfo {
|
||||
current: String,
|
||||
version: String,
|
||||
notes: String,
|
||||
/// O app consegue se atualizar sozinho (AppImage, Windows e macOS). Instalação por pacote
|
||||
/// (.deb/.rpm) só avisa e leva à página da release.
|
||||
/// Whether the app can self-update (AppImage, Windows, and macOS). Package installs
|
||||
/// (.deb/.rpm) only notify the user and open the release page.
|
||||
auto: bool,
|
||||
url: String,
|
||||
}
|
||||
@@ -31,22 +37,30 @@ fn self_update_possible() -> bool {
|
||||
!cfg!(target_os = "linux") || std::env::var_os("APPIMAGE").is_some()
|
||||
}
|
||||
|
||||
/// Procura uma versão mais nova. `None` = já está na última.
|
||||
/// Checks for a newer version. `None` = already up to date.
|
||||
#[tauri::command]
|
||||
async fn update_check(app: tauri::AppHandle) -> Result<Option<UpdateInfo>, String> {
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
let found = app.updater().map_err(|e| e.to_string())?.check().await.map_err(|e| e.to_string())?;
|
||||
let found = app
|
||||
.updater()
|
||||
.map_err(|e| e.to_string())?
|
||||
.check()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(found.map(|u| UpdateInfo {
|
||||
current: u.current_version.clone(),
|
||||
notes: u.body.clone().unwrap_or_default(),
|
||||
url: format!("https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/tag/v{}", u.version),
|
||||
url: format!(
|
||||
"https://git.lucasrguerra.dev.br/lucasrguerra/guara/releases/tag/v{}",
|
||||
u.version
|
||||
),
|
||||
version: u.version,
|
||||
auto: self_update_possible(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Baixa, confere a assinatura (chave pública embutida no app), instala e reabre o app.
|
||||
/// O progresso sai no evento `update-progress`.
|
||||
/// Downloads, verifies the signature (embedded public key), installs, and restarts the app.
|
||||
/// Progress is emitted via the `update-progress` event.
|
||||
#[tauri::command]
|
||||
async fn update_install(app: tauri::AppHandle) -> Result<(), String> {
|
||||
use tauri::Emitter;
|
||||
@@ -67,7 +81,10 @@ async fn update_install(app: tauri::AppHandle) -> Result<(), String> {
|
||||
.download_and_install(
|
||||
move |chunk, total| {
|
||||
got += chunk as u64;
|
||||
let _ = a.emit("update-progress", serde_json::json!({ "downloaded": got, "total": total }));
|
||||
let _ = a.emit(
|
||||
"update-progress",
|
||||
serde_json::json!({ "downloaded": got, "total": total }),
|
||||
);
|
||||
},
|
||||
move || {
|
||||
let _ = b.emit("update-progress", serde_json::json!({ "installing": true }));
|
||||
@@ -75,16 +92,16 @@ async fn update_install(app: tauri::AppHandle) -> Result<(), String> {
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Não foi possível atualizar: {e}"))?;
|
||||
// Rodando como administrador, o arquivo novo nasce do root: devolve ao usuário que abriu o app.
|
||||
// When running as administrator, the new file is owned by root: hand it back to the invoking user.
|
||||
if let Some(p) = std::env::var_os("APPIMAGE") {
|
||||
guara_core::owner::give_to_user(std::path::Path::new(&p));
|
||||
}
|
||||
app.restart()
|
||||
}
|
||||
|
||||
/// `true` quando o processo já tem privilégio suficiente para ler discos brutos.
|
||||
/// No Windows o manifesto já exige administrador antes mesmo do app abrir,
|
||||
/// então aqui é sempre `true`.
|
||||
/// `true` when the process already has sufficient privileges to read raw disks.
|
||||
/// On Windows the manifest already requires administrator privileges before launch,
|
||||
/// so this is always `true` here.
|
||||
#[tauri::command]
|
||||
fn is_elevated() -> bool {
|
||||
#[cfg(unix)]
|
||||
@@ -97,9 +114,9 @@ fn is_elevated() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reabre o app com privilégios de administrador (Linux/macOS). No Windows o
|
||||
/// manifesto já exige elevação. O app novo encerra este quando abre; se a
|
||||
/// autorização falhar, a janela continua aqui e a interface mostra o motivo.
|
||||
/// Relaunches the app with administrator privileges (Linux/macOS). On Windows the
|
||||
/// manifest already enforces elevation. The new instance terminates this one on startup; if
|
||||
/// authorization fails, this window stays open and the UI displays the reason.
|
||||
#[tauri::command]
|
||||
fn relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
@@ -120,7 +137,10 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
let cfg = guara_core::settings::config_dir();
|
||||
let log = std::env::temp_dir().join(format!("guara-elevate-{}.log", std::process::id()));
|
||||
let _ = std::fs::write(&log, b"");
|
||||
let out = std::fs::OpenOptions::new().append(true).open(&log).map_err(|e| e.to_string())?;
|
||||
let out = std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(&log)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let err = out.try_clone().map_err(|e| e.to_string())?;
|
||||
let pid = std::process::id().to_string();
|
||||
|
||||
@@ -128,7 +148,14 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
let mut cmd = {
|
||||
let mut c = std::process::Command::new("pkexec");
|
||||
c.arg("/usr/bin/env");
|
||||
for k in ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR", "DBUS_SESSION_BUS_ADDRESS", "GDK_BACKEND"] {
|
||||
for k in [
|
||||
"DISPLAY",
|
||||
"WAYLAND_DISPLAY",
|
||||
"XAUTHORITY",
|
||||
"XDG_RUNTIME_DIR",
|
||||
"DBUS_SESSION_BUS_ADDRESS",
|
||||
"GDK_BACKEND",
|
||||
] {
|
||||
if let Ok(v) = std::env::var(k) {
|
||||
c.arg(format!("{k}={v}"));
|
||||
}
|
||||
@@ -146,16 +173,21 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
pid,
|
||||
exe.display()
|
||||
);
|
||||
let script = format!("do shell script \"{}\" with administrator privileges", shell.replace('\\', "\\\\").replace('"', "\\\""));
|
||||
let script = format!(
|
||||
"do shell script \"{}\" with administrator privileges",
|
||||
shell.replace('\\', "\\\\").replace('"', "\\\"")
|
||||
);
|
||||
let mut c = std::process::Command::new("osascript");
|
||||
c.args(["-e", &script]);
|
||||
c
|
||||
};
|
||||
cmd.stdout(out).stderr(err);
|
||||
let mut child = cmd.spawn().map_err(|e| format!("não foi possível pedir privilégios: {e}"))?;
|
||||
let mut child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| format!("não foi possível pedir privilégios: {e}"))?;
|
||||
std::thread::spawn(move || {
|
||||
// No Linux o pkexec só termina com sucesso quando o app elevado é fechado (e o app
|
||||
// elevado já terá encerrado este processo). Qualquer falha antes disso vira aviso.
|
||||
// On Linux, pkexec only exits with success when the elevated app closes (and the elevated
|
||||
// app will have already terminated this process). Any failure before that becomes a warning.
|
||||
let status = child.wait();
|
||||
if matches!(&status, Ok(s) if s.success()) {
|
||||
let _ = std::fs::remove_file(&log);
|
||||
@@ -165,7 +197,10 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
let msg = match status.ok().and_then(|s| s.code()) {
|
||||
Some(126) => "Autorização cancelada.".to_string(),
|
||||
Some(127) => "Não foi possível autenticar como administrador.".to_string(),
|
||||
_ => format!("Não foi possível reiniciar como administrador. {}", detail.lines().last().unwrap_or("").trim()),
|
||||
_ => format!(
|
||||
"Não foi possível reiniciar como administrador. {}",
|
||||
detail.lines().last().unwrap_or("").trim()
|
||||
),
|
||||
};
|
||||
let _ = app.emit("elevate-failed", msg);
|
||||
let _ = std::fs::remove_file(&log);
|
||||
@@ -173,31 +208,43 @@ fn do_relaunch_elevated(app: tauri::AppHandle) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Caminho que o root consegue executar. No AppImage, `current_exe` aponta para
|
||||
/// dentro de um ponto de montagem FUSE do usuário, inacessível ao root: usa o
|
||||
/// próprio arquivo `.AppImage` (variável `APPIMAGE`).
|
||||
/// Executable path accessible to root. Inside an AppImage, `current_exe` points
|
||||
/// inside a user FUSE mount that root cannot access: use the
|
||||
/// `.AppImage` file itself (`APPIMAGE` environment variable).
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn elevated_exe() -> Result<std::path::PathBuf, String> {
|
||||
if let Some(p) = std::env::var_os("APPIMAGE").map(std::path::PathBuf::from).filter(|p| p.is_file()) {
|
||||
if let Some(p) = std::env::var_os("APPIMAGE")
|
||||
.map(std::path::PathBuf::from)
|
||||
.filter(|p| p.is_file())
|
||||
{
|
||||
return Ok(p);
|
||||
}
|
||||
std::env::current_exe().map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Serve os arquivos recuperados para pré-visualização (`guara://localhost/file/<job>/<id>`),
|
||||
/// com suporte a Range para vídeo/áudio/PDF, lendo direto da origem.
|
||||
/// Serves recovered files for preview (`guara://localhost/file/<job>/<id>`),
|
||||
/// with Range support for video/audio/PDF, reading directly from the source.
|
||||
fn serve_file(eng: &Engine, req: &tauri::http::Request<Vec<u8>>) -> tauri::http::Response<Vec<u8>> {
|
||||
use tauri::http::Response;
|
||||
if let Some(rest) = req.uri().path().strip_prefix("/thumb/") {
|
||||
let mut it = rest.split('/');
|
||||
let (job, id) = (it.next().unwrap_or(""), it.next().and_then(|s| s.parse::<u64>().ok()));
|
||||
let (job, id) = (
|
||||
it.next().unwrap_or(""),
|
||||
it.next().and_then(|s| s.parse::<u64>().ok()),
|
||||
);
|
||||
let size = req
|
||||
.uri()
|
||||
.query()
|
||||
.and_then(|q| q.split('&').find_map(|kv| kv.strip_prefix("s=")))
|
||||
.and_then(|v| v.parse::<u32>().ok())
|
||||
.unwrap_or(256);
|
||||
let err = |code: u16| Response::builder().status(code).header("Access-Control-Allow-Origin", "*").body(Vec::new()).unwrap();
|
||||
let err = |code: u16| {
|
||||
Response::builder()
|
||||
.status(code)
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.body(Vec::new())
|
||||
.unwrap()
|
||||
};
|
||||
let Some(id) = id else { return err(400) };
|
||||
return match eng.thumb(job, id, size) {
|
||||
Ok(b) => Response::builder()
|
||||
@@ -212,8 +259,17 @@ fn serve_file(eng: &Engine, req: &tauri::http::Request<Vec<u8>>) -> tauri::http:
|
||||
}
|
||||
const CAP: u64 = 8 * 1024 * 1024;
|
||||
let mut it = req.uri().path().trim_start_matches("/file/").split('/');
|
||||
let (job, id) = (it.next().unwrap_or(""), it.next().and_then(|s| s.parse::<u64>().ok()));
|
||||
let err = |code: u16| Response::builder().status(code).header("Access-Control-Allow-Origin", "*").body(Vec::new()).unwrap();
|
||||
let (job, id) = (
|
||||
it.next().unwrap_or(""),
|
||||
it.next().and_then(|s| s.parse::<u64>().ok()),
|
||||
);
|
||||
let err = |code: u16| {
|
||||
Response::builder()
|
||||
.status(code)
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.body(Vec::new())
|
||||
.unwrap()
|
||||
};
|
||||
let Some(id) = id else { return err(400) };
|
||||
let range = req.headers().get("range").and_then(|v| v.to_str().ok());
|
||||
let slice = match eng.file_slice(job, id, range) {
|
||||
@@ -225,14 +281,24 @@ fn serve_file(eng: &Engine, req: &tauri::http::Request<Vec<u8>>) -> tauri::http:
|
||||
let want = slice.len();
|
||||
let mut headers = slice.headers();
|
||||
let partial = slice.partial || want > CAP;
|
||||
let Ok(data) = slice.read_all(CAP) else { return err(500) };
|
||||
let Ok(data) = slice.read_all(CAP) else {
|
||||
return err(500);
|
||||
};
|
||||
let mut b = Response::builder().status(if partial { 206 } else { 200 });
|
||||
headers.retain(|(k, _)| *k != "Content-Range");
|
||||
for (k, v) in &headers {
|
||||
b = b.header(*k, v);
|
||||
}
|
||||
if partial && !data.is_empty() {
|
||||
b = b.header("Content-Range", format!("bytes {}-{}/{}", start, start + data.len() as u64 - 1, total));
|
||||
b = b.header(
|
||||
"Content-Range",
|
||||
format!(
|
||||
"bytes {}-{}/{}",
|
||||
start,
|
||||
start + data.len() as u64 - 1,
|
||||
total
|
||||
),
|
||||
);
|
||||
}
|
||||
b.body(data).unwrap_or_else(|_| err(500))
|
||||
}
|
||||
@@ -249,12 +315,15 @@ fn main() {
|
||||
std::thread::spawn(move || responder.respond(serve_file(&eng, &request)));
|
||||
})
|
||||
.setup(|app| {
|
||||
// Quando abrimos elevados a pedido do app normal, ele fecha o app antigo.
|
||||
// When launched elevated by the unprivileged instance, terminate the old process.
|
||||
#[cfg(unix)]
|
||||
if let Some(pid) = std::env::var("GUARA_REPLACE_PID").ok().and_then(|p| p.parse::<i32>().ok()) {
|
||||
if let Some(pid) = std::env::var("GUARA_REPLACE_PID")
|
||||
.ok()
|
||||
.and_then(|p| p.parse::<i32>().ok())
|
||||
{
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1200));
|
||||
// SAFETY: só envia SIGTERM ao processo anterior do próprio usuário.
|
||||
// SAFETY: only sends SIGTERM to the previous process owned by the invoking user.
|
||||
unsafe {
|
||||
if libc::geteuid() == 0 {
|
||||
libc::kill(pid, libc::SIGTERM);
|
||||
@@ -263,9 +332,9 @@ fn main() {
|
||||
});
|
||||
}
|
||||
|
||||
// Ler discos brutos exige root: pede elevação assim que o app abre,
|
||||
// sem esperar o usuário procurar um botão. Se a autorização falhar,
|
||||
// a janela continua aberta e a interface mostra o aviso.
|
||||
// Reading raw disks requires root: request elevation as soon as the app launches,
|
||||
// without waiting for the user to look for a button. If authorization fails,
|
||||
// the window stays open and the UI displays a warning.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
if unsafe { libc::geteuid() != 0 } && std::env::var("GUARA_REPLACE_PID").is_err() {
|
||||
let handle = app.handle().clone();
|
||||
@@ -275,7 +344,13 @@ fn main() {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![rpc, is_elevated, relaunch_elevated, update_check, update_install])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
rpc,
|
||||
is_elevated,
|
||||
relaunch_elevated,
|
||||
update_check,
|
||||
update_install
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("erro ao iniciar o Guará");
|
||||
}
|
||||
Reference in new issue
Block a user