Please provide the file changes or a description of the modifications you made so I can generate the commit message for you.

This commit is contained in:
Erkan IŞIK
2026-06-28 09:58:20 +03:00
parent 733548da56
commit 7621507acd
2 changed files with 145 additions and 41 deletions
+133 -30
View File
@@ -11,6 +11,7 @@
//! UI'nin hemen güncellenmesi sağlanır.
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use tokio::io::{AsyncBufReadExt, BufReader};
@@ -140,6 +141,43 @@ impl Job for CopyFilesJob {
fn name(&self) -> &str { "job_copy_files" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
ui.log(format!("[DEBUG] CopyFilesJob başladı"));
ui.log(format!("[DEBUG] Kaynak: '{}'", self.source));
ui.log(format!("[DEBUG] Hedef: '{}'", self.target));
// Kaynak dizini kontrol et
let source_path = std::path::Path::new(&self.source);
let source_exists = source_path.exists();
let source_readable = source_exists && source_path.is_dir();
ui.log(format!("[DEBUG] Kaynak mevcut: {}, dizin: {}", source_exists, source_readable));
if !source_exists {
ui.log(format!("[DEBUG] Kaynak dizin içeriği aranıyor..."));
// Kaynağın bir üst dizinini dene
let parent = source_path.parent();
if let Some(p) = parent {
if p.exists() {
if let Ok(entries) = std::fs::read_dir(p) {
for entry in entries.flatten().take(20) {
ui.log(format!("[DEBUG] /{:?}", entry.path()));
}
}
}
} else {
ui.log("[DEBUG] Kaynağın parent'ı yok (kök dizin olabilir)");
}
} else if source_readable {
// İlk 10 girdiyi listele
if let Ok(entries) = std::fs::read_dir(source_path) {
let mut count = 0;
for entry in entries.flatten().take(10) {
ui.log(format!("[DEBUG] {:?}", entry.path()));
count += 1;
}
ui.log(format!("[DEBUG] (ilk {} girdi listelendi)", count));
}
}
ui.log(format!("rsync {}{}", self.source, self.target));
let is_root_source = self.source.trim_end_matches('/') == "";
@@ -204,53 +242,113 @@ impl Job for CopyFilesJob {
return Ok(());
}
let mut child = Command::new("rsync")
.args(&args)
// rsync çıktısı pipe'a yönlendirilince tam tamponlu (fully buffered) olur,
// bu da progress çıktısının gerçek zamanlı gelmemesine yol açar.
// stdbuf -eL ile stderr'i satır tamponlu (line-buffered) yapıyoruz.
let (program, final_args) = if Path::new("/usr/bin/stdbuf").exists() {
let mut a = vec!["-eL".to_string(), "rsync".to_string()];
a.extend(args);
("stdbuf", a)
} else {
("rsync", args)
};
let mut child = Command::new(program)
.args(&final_args)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|e| format!("rsync çalıştırılamadı: {}", e))?;
let stdout = child.stdout.take()
.ok_or_else(|| "rsync stdout alınamadı".to_string())?;
let stderr = child.stderr.take()
.ok_or_else(|| "rsync stderr alınamadı".to_string())?;
let reader = BufReader::new(stderr);
// Stderr'i arka planda okuyarak tamponun dolmasını ve rsync'in kilitlenmesini engelliyoruz.
let stderr_err_accumulator = std::sync::Arc::new(tokio::sync::Mutex::new(String::new()));
let stderr_accumulator_clone = stderr_err_accumulator.clone();
tokio::spawn(async move {
let reader = BufReader::new(stderr);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
let mut accum = stderr_accumulator_clone.lock().await;
accum.push_str(&line);
accum.push('\n');
}
});
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await
.map_err(|e| format!("rsync çıktısı okunamadı: {}", e))?
{
let trimmed = line.trim();
// rsync --info=progress2 format (newlines when piped):
// 1,024 100% 512.00kB/s 0:00:00 (xfr#5, to-chk=99/105)
// Extract xfr# and to-chk for sub-progress
if let Some(xfr_pos) = trimmed.find("xfr#") {
let after_xfr = &trimmed[xfr_pos + 4..];
let xfr_end = after_xfr.find(|c: char| !c.is_ascii_digit()).unwrap_or(after_xfr.len());
let xfr: u64 = after_xfr[..xfr_end].parse().unwrap_or(0);
// Pipe'a yönlendirilince rsync satırbaşı (\r) içerebilir.
// Önce \r karakterlerini temizle, sonra trim yap.
let cleaned: String = line.chars()
.filter(|&c| c != '\r')
.collect();
let trimmed = cleaned.trim().to_string();
if let Some(to_chk_pos) = trimmed.find("to-chk=") {
let after_to = &trimmed[to_chk_pos + 7..];
let slash_pos = after_to.find('/').unwrap_or(after_to.len());
let total_end = after_to[slash_pos + 1..]
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(after_to[slash_pos + 1..].len());
let total: u64 = after_to[slash_pos + 1..][..total_end].parse().unwrap_or(1);
if trimmed.is_empty() {
continue;
}
let p = if total > 0 {
(xfr as f32) / (total as f32)
} else {
0.0
};
// ── 1) Progress satırı mı? ──────────────────────────
// rsync --info=progress2 format (pipe yoluyla):
// " 1,024 100% 512.00kB/s 0:00:00 (xfr#5, to-chk=99/105)"
// " 3.456M 67% 5.12MB/s 0:00:01 (xfr#17, to-chk=83/105)"
// (xfr# ve to-chk= varsa progress satırıdır)
if trimmed.contains("xfr#") && trimmed.contains("to-chk=") {
// xfr# değerini çıkar
let xfr = if let Some(pos) = trimmed.find("xfr#") {
let after = &trimmed[pos + 4..];
let end = after.find(|c: char| !c.is_ascii_digit()).unwrap_or(after.len());
after[..end].parse::<u64>().unwrap_or(0)
} else { 0 };
// to-chk= içinden total değerini çıkar
let total = if let Some(pos) = trimmed.find("to-chk=") {
let after = &trimmed[pos + 7..];
let slash_pos = after.find('/');
if let Some(sp) = slash_pos {
let after_slash = &after[sp + 1..];
let end = after_slash.find(|c: char| !c.is_ascii_digit()).unwrap_or(after_slash.len());
after_slash[..end].parse::<u64>().unwrap_or(1)
} else { 1 }
} else { 1 };
if total > 0 {
let p = (xfr as f32) / (total as f32);
ui.sub_progress(p.min(1.0));
}
continue;
}
// ── 2) Progress satırının ilk kısmı mı? (sayı + yüzde ile başlayan) ──
// rsync pipe çıktısı bazen iki satıra bölünebilir:
// " 1,024 100% 512.00kB/s 0:00:00"
// " (xfr#5, to-chk=99/105)"
// Bu durumda ilk satırda sadece sayı/yüzde/hız bilgisi vardır, atla.
{
let first = trimmed.chars().next().unwrap_or(' ');
if first.is_ascii_digit() || first == ' ' {
// Sayı veya boşlukla başlayıp xfr içermiyorsa progress'in devamıdır
continue;
}
}
// Extract filename: lines starting with a path (not a number/space)
let first_char = trimmed.chars().next();
if let Some('/') = first_char {
ui.current_file(trimmed.to_string());
} else if trimmed.starts_with("./") || trimmed.starts_with("..") {
ui.current_file(trimmed.to_string());
// ── 3) Gerçek dosya adı ──────────────────────────────
// rsync --out-format kullanılmadığında dosya adları da stderr'e
// göreceli yol olarak yazılır: "etc/passwd", "usr/share/doc/..."
// Bunlar genellikle '.' '/' veya harf ile başlar, sayı ile başlamaz.
// progress satırı olmadığına göre bu bir dosya adıdır.
if trimmed.len() > 3 && !trimmed.starts_with('[') && !trimmed.contains("sending incremental")
&& !trimmed.contains("building file list") && !trimmed.contains("done")
&& !trimmed.contains("sent ") && !trimmed.contains("total size")
{
ui.current_file(trimmed);
}
}
@@ -258,7 +356,12 @@ impl Job for CopyFilesJob {
.map_err(|e| format!("rsync beklenemedi: {}", e))?;
if !status.success() {
return Err(format!("rsync çıkış kodu: {:?}", status.code()));
let stderr_content = stderr_err_accumulator.lock().await.clone();
return Err(format!(
"rsync çıkış kodu: {:?}. Hata: {}",
status.code(),
stderr_content.trim()
));
}
Ok(())
+12 -11
View File
@@ -178,6 +178,10 @@ impl InstallerStep for ExecutionStep {
loop {
match rx.try_recv() {
Ok(InstallMessage::Log(line)) => {
if line.starts_with('▶') {
state.install_sub_progress = 0.0;
state.install_current_file = None;
}
state.install_log.push(line);
}
Ok(InstallMessage::Progress(p)) => {
@@ -268,8 +272,7 @@ impl InstallerStep for ExecutionStep {
// İlerleme çubuğu
let running = !self.finished
&& self.error_screen.is_none()
&& state.install_progress > 0.0;
&& self.error_screen.is_none();
egui::Frame::none()
.fill(theme::c_bg_widget())
@@ -313,15 +316,13 @@ impl InstallerStep for ExecutionStep {
.desired_height(10.0),
);
if state.install_sub_progress > 0.0 {
ui.add_space(4.0);
ui.add(
egui::ProgressBar::new(state.install_sub_progress)
.animate(running)
.desired_height(6.0)
.fill(theme::c_accent_dim()),
);
}
ui.add_space(4.0);
ui.add(
egui::ProgressBar::new(state.install_sub_progress)
.animate(running)
.desired_height(6.0)
.fill(theme::c_accent_dim()),
);
});
// Durum etiketi