Skip to main content

plot/lib/job/
step1.rs

1use std::{fs, path::Path};
2
3use anyhow::{Context, Result};
4use chrono::DateTime;
5
6use crate::lib::{job, logic};
7// ============================================================================
8// TRAIT POMOCNICZY (Unifikacja typów węzłów)
9// ============================================================================
10
11/// Pozwala na bezproblemową konwersję specjalistycznych węzłów na węzeł uniwersalny.
12pub trait AsScannedNode {
13	fn as_scanned_node(&self) -> logic::ScannedNode;
14}
15
16impl AsScannedNode for logic::ScannedFileNode {
17	fn as_scanned_node(&self) -> logic::ScannedNode {
18		logic::ScannedNode {
19			name: self.name.clone(),
20			path: self.path.clone(),
21			node: logic::NodeIs::File,
22			tier: self.tier,
23			id_self: self.id_self,         // ⚡ PRZEPISANIE ID
24			id_path: self.id_path.clone(), // ⚡ PRZEPISANIE ŚCIEŻKI
25			dir_has_subdirs: None,
26			dir_has_files_binary: None,
27			dir_has_files_text: None,
28			dir_has_symlinks: None,
29			file_is_binary: Some(self.is_binary),
30			file_is_empty: Some(self.is_empty),
31		}
32	}
33}
34
35impl AsScannedNode for logic::ScannedDirNode {
36	fn as_scanned_node(&self) -> logic::ScannedNode {
37		logic::ScannedNode {
38			name: self.name.clone(),
39			path: self.path.clone(),
40			node: logic::NodeIs::Dir,
41			tier: self.tier,
42			id_self: self.id_self,         // ⚡ PRZEPISANIE ID
43			id_path: self.id_path.clone(), // ⚡ PRZEPISANIE ŚCIEŻKI
44			dir_has_subdirs: Some(self.has_subdirs),
45			dir_has_files_binary: Some(self.has_files_binary),
46			dir_has_files_text: Some(self.has_files_text),
47			dir_has_symlinks: Some(self.has_symlinks),
48			file_is_binary: None,
49			file_is_empty: None,
50		}
51	}
52}
53
54// ============================================================================
55// GATHER & INSPECT (Faza Hydracji)
56// ============================================================================
57
58/// Przechodzi po całej partycji, konwertuje generyczne węzły i uderza do dysku po metadane.
59fn piece_gather<L>(j: &job::ValidPreparedJobConfig, p: &logic::Partition<L>) -> Vec<job::ValidResultMainRow>
60where
61	L: logic::MatchLabel,
62	L::Node: AsScannedNode, //  Wymagamy, aby węzeł w partycji potrafił stać się ScannedNode
63{
64	p.nodes
65		.iter()
66		.filter_map(|n| {
67			let unified_node = n.as_scanned_node();
68			data_inspect(&j, unified_node).ok() // Ignorujemy pliki, które np. usunięto w międzyczasie
69		})
70		.collect()
71}
72
73/// Zderza zunifikowany węzeł z fizycznym dyskiem i buduje ostateczny wiersz.
74fn data_inspect(j: &job::ValidPreparedJobConfig, scanned_node: logic::ScannedNode) -> Result<job::ValidResultMainRow> {
75	// 1. Ustalenie fizycznej ścieżki absolutnej
76	// ⁉️ poco ponownie ustalamy
77	let clean_rel = scanned_node.path.str.strip_prefix("./").unwrap_or(&scanned_node.path.str);
78	let absolute_path = j.work.execution_dir.join(clean_rel);
79
80	// 2. Pobranie metadanych systemu plików
81	let metadata = fs::metadata(&absolute_path).context("Nie można odczytać metadanych")?;
82
83	// 3. Ekstrakcja danych
84	let dt_modified = DateTime::from(metadata.modified()?);
85
86	let name_with_ext = absolute_path.file_name().unwrap_or_default().to_string_lossy().to_string();
87
88	let size_real = if metadata.is_dir() { get_dir_size(&absolute_path) } else { metadata.len() };
89
90	Ok(job::ValidResultMainRow { dt_modified, name_with_ext, size_real, node: scanned_node })
91}
92
93/// Oblicza faktyczny (fizyczny) rozmiar katalogu sumując wszystkie pliki wewnątrz.
94fn get_dir_size(path: &Path) -> u64 {
95	walkdir::WalkDir::new(path)
96		.into_iter()
97		.filter_map(|e| e.ok())
98		.filter(|e| e.file_type().is_file())
99		.filter_map(|e| e.metadata().ok())
100		.map(|m| m.len())
101		.sum()
102}
103
104//============================================================================
105//job::ValidTablePartConfig
106
107pub fn data_gather(j: &job::ValidPreparedJobConfig, b: &logic::PartitionScanned) -> job::ValidResultMainTab {
108	let mut final_rows = Vec::new();
109	let mut t_max = 0;
110	let mut n_max = 0;
111	let mut p_max = 0;
112
113	// Leniwe pobieranie danych - uderzamy do dysku tylko po wybrane pule
114	if j.part.md {
115		final_rows.extend(piece_gather(&j, &b.m_d));
116		t_max = t_max.max(b.m_d.tier_max);
117		n_max = n_max.max(b.m_d.name_len_max);
118		p_max = p_max.max(b.m_d.path_len_max);
119	}
120	if j.part.mf {
121		final_rows.extend(piece_gather(&j, &b.m_f));
122		t_max = t_max.max(b.m_f.tier_max);
123		n_max = n_max.max(b.m_f.name_len_max);
124		p_max = p_max.max(b.m_f.path_len_max);
125	}
126	if j.part.xd {
127		final_rows.extend(piece_gather(&j, &b.x_d));
128		t_max = t_max.max(b.x_d.tier_max);
129		n_max = n_max.max(b.x_d.name_len_max);
130		p_max = p_max.max(b.x_d.path_len_max);
131	}
132	if j.part.xf {
133		final_rows.extend(piece_gather(&j, &b.x_f));
134		t_max = t_max.max(b.x_f.tier_max);
135		n_max = n_max.max(b.x_f.name_len_max);
136		p_max = p_max.max(b.x_f.path_len_max);
137	}
138
139	// Sortujemy złączony wynik, aby przywrócić spójną strukturę drzewa po ścieżkach
140	final_rows.sort_unstable_by(|row_a, row_b| row_a.node.path.str.cmp(&row_b.node.path.str));
141
142	job::ValidResultMainTab { rows: final_rows, tier_max: t_max, name_len_max: n_max, path_len_max: p_max }
143}
144//============================================================================
145/// [Step 1] Skaner - teraz przyjmuje konfiguratory zamiast surowych struktur Job
146pub fn engine_step1_scanner(j: &job::ValidPreparedJobConfig) -> job::ValidResultMainTab {
147	// 2. Bezpośrednie wywołanie skanera z logiki
148	let partition_scanned = logic::PartitionScanned::scan(&j);
149
150	// 3. Zbieranie danych (hydracja z dysku) - funkcja data_gather pozostaje bez zmian
151	data_gather(&j, &partition_scanned)
152}