forked from pisilinux-rs/yali-rs
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:
+132
-29
@@ -11,6 +11,7 @@
|
|||||||
//! UI'nin hemen güncellenmesi sağlanır.
|
//! UI'nin hemen güncellenmesi sağlanır.
|
||||||
|
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
@@ -140,6 +141,43 @@ impl Job for CopyFilesJob {
|
|||||||
fn name(&self) -> &str { "job_copy_files" }
|
fn name(&self) -> &str { "job_copy_files" }
|
||||||
|
|
||||||
async fn run(&self, ui: &UiSender) -> Result<(), String> {
|
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));
|
ui.log(format!("rsync {} → {}", self.source, self.target));
|
||||||
|
|
||||||
let is_root_source = self.source.trim_end_matches('/') == "";
|
let is_root_source = self.source.trim_end_matches('/') == "";
|
||||||
@@ -204,53 +242,113 @@ impl Job for CopyFilesJob {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut child = Command::new("rsync")
|
// rsync çıktısı pipe'a yönlendirilince tam tamponlu (fully buffered) olur,
|
||||||
.args(&args)
|
// 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())
|
.stdout(std::process::Stdio::piped())
|
||||||
.stderr(std::process::Stdio::piped())
|
.stderr(std::process::Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| format!("rsync çalıştırılamadı: {}", e))?;
|
.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()
|
let stderr = child.stderr.take()
|
||||||
.ok_or_else(|| "rsync stderr alınamadı".to_string())?;
|
.ok_or_else(|| "rsync stderr alınamadı".to_string())?;
|
||||||
|
|
||||||
|
// 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 reader = BufReader::new(stderr);
|
||||||
let mut lines = reader.lines();
|
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
|
while let Some(line) = lines.next_line().await
|
||||||
.map_err(|e| format!("rsync çıktısı okunamadı: {}", e))?
|
.map_err(|e| format!("rsync çıktısı okunamadı: {}", e))?
|
||||||
{
|
{
|
||||||
let trimmed = line.trim();
|
// Pipe'a yönlendirilince rsync satırbaşı (\r) içerebilir.
|
||||||
// rsync --info=progress2 format (newlines when piped):
|
// Önce \r karakterlerini temizle, sonra trim yap.
|
||||||
// 1,024 100% 512.00kB/s 0:00:00 (xfr#5, to-chk=99/105)
|
let cleaned: String = line.chars()
|
||||||
// Extract xfr# and to-chk for sub-progress
|
.filter(|&c| c != '\r')
|
||||||
if let Some(xfr_pos) = trimmed.find("xfr#") {
|
.collect();
|
||||||
let after_xfr = &trimmed[xfr_pos + 4..];
|
let trimmed = cleaned.trim().to_string();
|
||||||
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);
|
|
||||||
|
|
||||||
if let Some(to_chk_pos) = trimmed.find("to-chk=") {
|
if trimmed.is_empty() {
|
||||||
let after_to = &trimmed[to_chk_pos + 7..];
|
continue;
|
||||||
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);
|
|
||||||
|
|
||||||
let p = if total > 0 {
|
// ── 1) Progress satırı mı? ──────────────────────────
|
||||||
(xfr as f32) / (total as f32)
|
// rsync --info=progress2 format (pipe yoluyla):
|
||||||
} else {
|
// " 1,024 100% 512.00kB/s 0:00:00 (xfr#5, to-chk=99/105)"
|
||||||
0.0
|
// " 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));
|
ui.sub_progress(p.min(1.0));
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract filename: lines starting with a path (not a number/space)
|
// ── 2) Progress satırının ilk kısmı mı? (sayı + yüzde ile başlayan) ──
|
||||||
let first_char = trimmed.chars().next();
|
// rsync pipe çıktısı bazen iki satıra bölünebilir:
|
||||||
if let Some('/') = first_char {
|
// " 1,024 100% 512.00kB/s 0:00:00"
|
||||||
ui.current_file(trimmed.to_string());
|
// " (xfr#5, to-chk=99/105)"
|
||||||
} else if trimmed.starts_with("./") || trimmed.starts_with("..") {
|
// Bu durumda ilk satırda sadece sayı/yüzde/hız bilgisi vardır, atla.
|
||||||
ui.current_file(trimmed.to_string());
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 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))?;
|
.map_err(|e| format!("rsync beklenemedi: {}", e))?;
|
||||||
|
|
||||||
if !status.success() {
|
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(())
|
Ok(())
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ impl InstallerStep for ExecutionStep {
|
|||||||
loop {
|
loop {
|
||||||
match rx.try_recv() {
|
match rx.try_recv() {
|
||||||
Ok(InstallMessage::Log(line)) => {
|
Ok(InstallMessage::Log(line)) => {
|
||||||
|
if line.starts_with('▶') {
|
||||||
|
state.install_sub_progress = 0.0;
|
||||||
|
state.install_current_file = None;
|
||||||
|
}
|
||||||
state.install_log.push(line);
|
state.install_log.push(line);
|
||||||
}
|
}
|
||||||
Ok(InstallMessage::Progress(p)) => {
|
Ok(InstallMessage::Progress(p)) => {
|
||||||
@@ -268,8 +272,7 @@ impl InstallerStep for ExecutionStep {
|
|||||||
|
|
||||||
// İlerleme çubuğu
|
// İlerleme çubuğu
|
||||||
let running = !self.finished
|
let running = !self.finished
|
||||||
&& self.error_screen.is_none()
|
&& self.error_screen.is_none();
|
||||||
&& state.install_progress > 0.0;
|
|
||||||
|
|
||||||
egui::Frame::none()
|
egui::Frame::none()
|
||||||
.fill(theme::c_bg_widget())
|
.fill(theme::c_bg_widget())
|
||||||
@@ -313,7 +316,6 @@ impl InstallerStep for ExecutionStep {
|
|||||||
.desired_height(10.0),
|
.desired_height(10.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
if state.install_sub_progress > 0.0 {
|
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::ProgressBar::new(state.install_sub_progress)
|
egui::ProgressBar::new(state.install_sub_progress)
|
||||||
@@ -321,7 +323,6 @@ impl InstallerStep for ExecutionStep {
|
|||||||
.desired_height(6.0)
|
.desired_height(6.0)
|
||||||
.fill(theme::c_accent_dim()),
|
.fill(theme::c_accent_dim()),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Durum etiketi
|
// Durum etiketi
|
||||||
|
|||||||
Reference in New Issue
Block a user