use std::{
collections::HashMap,
error::Error,
fs::{create_dir_all, read_dir, read_to_string, write},
path::Path,
};
#[derive(Debug)]
struct Metadata {
fields: HashMap<String, String>,
}
impl Metadata {
fn new() -> Self {
Metadata {
fields: HashMap::new(),
}
}
fn from_file(path: &str) -> Result<Self, Box<dyn Error>> {
let content = read_to_string(path)?;
let mut metadata = Metadata::new();
for line in content.lines() {
if line.trim().is_empty() {
continue; }
if let Some(colon_pos) = line.find(':') {
let before_colon = &line[..colon_pos];
let value = line[colon_pos + 1..].trim();
let field_name = if let Some(last_bracket_pos) = before_colon.rfind(']') {
before_colon[last_bracket_pos + 1..].trim()
} else {
before_colon.trim()
};
if !field_name.is_empty() && !value.is_empty() {
metadata
.fields
.insert(field_name.to_string(), value.to_string());
}
}
}
Ok(metadata)
}
fn compare_with(&self, other: &Metadata) -> Vec<String> {
let mut differences = Vec::new();
for (key, value) in &self.fields {
if let Some(other_value) = other.fields.get(key) {
if value != other_value {
differences.push(format!(
"Field '{}' differs:\n Rust: {}\n C#: {}",
key, value, other_value
));
}
} else {
differences.push(format!(
"Field '{}' exists in Rust but not in C#: {}",
key, value
));
}
}
for (key, value) in &other.fields {
if !self.fields.contains_key(key) {
differences.push(format!(
"Field '{}' exists in C# but not in Rust: {}",
key, value
));
}
}
differences
}
}
fn main() -> Result<(), Box<dyn Error>> {
println!("Starting metadata comparison process...");
create_dir_all("metadata_test/reports")?;
let rust_mp3_files = get_metadata_files("metadata_test/metadata/mp3")?;
let rust_flac_files = get_metadata_files("metadata_test/metadata/flac")?;
let csharp_mp3_files = get_metadata_files("metadata_test/metadata/C#-songs/mp3")?;
let csharp_flac_files = get_metadata_files("metadata_test/metadata/C#-songs/flac")?;
let mut csharp_files = csharp_mp3_files;
csharp_files.extend(csharp_flac_files);
let mut total_differences = 0;
for rust_file in &rust_mp3_files {
if let Some(csharp_file) = find_matching_csharp_file(rust_file, &csharp_files) {
println!("Comparing {} with {}", rust_file, csharp_file);
let rust_metadata =
Metadata::from_file(&format!("metadata_test/metadata/mp3/{}", rust_file))?;
let csharp_metadata = Metadata::from_file(&format!(
"metadata_test/metadata/C#-songs/mp3/{}",
csharp_file
))?;
let differences = rust_metadata.compare_with(&csharp_metadata);
if !differences.is_empty() {
let comparison_name = rust_file.trim_end_matches(".txt").replace(" - ", "_");
let report_filename = format!(
"metadata_test/reports/{}_vs_{}.txt",
comparison_name,
csharp_file.trim_end_matches(".txt")
);
let report_content = format!(
"Differences between {} and {}:\n{}\n",
rust_file,
csharp_file,
differences.join("\n")
);
write(&report_filename, &report_content)?;
total_differences += differences.len();
println!(" Differences saved to: {}", report_filename);
} else {
println!(
" No differences found between {} and {}",
rust_file, csharp_file
);
}
} else {
println!(" No matching C# file found for: {}", rust_file);
}
}
for rust_file in &rust_flac_files {
if let Some(csharp_file) = find_matching_csharp_file(rust_file, &csharp_files) {
println!("Comparing {} with {}", rust_file, csharp_file);
let rust_metadata =
Metadata::from_file(&format!("metadata_test/metadata/flac/{}", rust_file))?;
let csharp_metadata = Metadata::from_file(&format!(
"metadata_test/metadata/C#-songs/flac/{}",
csharp_file
))?;
let differences = rust_metadata.compare_with(&csharp_metadata);
if !differences.is_empty() {
let comparison_name = rust_file.trim_end_matches(".txt").replace(" - ", "_");
let report_filename = format!(
"metadata_test/reports/{}_vs_{}.txt",
comparison_name,
csharp_file.trim_end_matches(".txt")
);
let report_content = format!(
"Differences between {} and {}:\n{}\n",
rust_file,
csharp_file,
differences.join("\n")
);
write(&report_filename, &report_content)?;
total_differences += differences.len();
println!(" Differences saved to: {}", report_filename);
} else {
println!(
" No differences found between {} and {}",
rust_file, csharp_file
);
}
} else {
println!(" No matching C# file found for: {}", rust_file);
}
}
println!("\nComparison completed. Individual reports saved to: metadata_test/reports/");
println!(
"Total differences found across all comparisons: {}",
total_differences
);
Ok(())
}
fn get_metadata_files(dir: &str) -> Result<Vec<String>, Box<dyn Error>> {
let mut files = Vec::new();
if Path::new(dir).exists() {
for entry in read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file()
&& path.extension().is_some_and(|ext| ext == "txt")
&& let Some(filename) = path.file_name().and_then(|name| name.to_str())
{
files.push(filename.to_string());
}
}
}
Ok(files)
}
fn find_matching_csharp_file(rust_file: &str, csharp_files: &[String]) -> Option<String> {
let rust_parts: Vec<&str> = rust_file.trim_end_matches(".txt").split(" - ").collect();
if rust_parts.len() >= 2 {
let rust_artist = rust_parts[0];
let rust_track = rust_parts[1];
let normalized_rust_artist = normalize_filename(rust_artist);
let normalized_rust_track = normalize_filename(rust_track);
for csharp_file in csharp_files {
let csharp_name = csharp_file.trim_end_matches(".txt");
let csharp_parts: Vec<&str> = csharp_name.splitn(2, " - ").collect();
if !csharp_parts.is_empty() {
let csharp_artist = csharp_parts[0];
let csharp_track = if csharp_parts.len() > 1 {
csharp_parts[1]
} else {
"" };
let normalized_csharp_artist = normalize_filename(csharp_artist);
let normalized_csharp_track = normalize_filename(csharp_track);
if normalized_csharp_artist.contains(&normalized_rust_artist)
|| normalized_rust_artist.contains(&normalized_csharp_artist)
{
if normalized_csharp_track.contains(&normalized_rust_track)
|| normalized_rust_track.contains(&normalized_csharp_track)
{
return Some(csharp_file.clone());
}
}
}
}
}
None
}
fn normalize_filename(name: &str) -> String {
name.to_lowercase()
.replace(":", "_") .replace(" : ", "_") .replace(" _ ", "_") .replace(" ", " ") .replace("(", "")
.replace(")", "")
.replace(",", "")
.replace(".", "")
.replace("'", "")
.replace("!", "")
.replace("?", "")
.replace("-", " ")
.replace(" ", " ") .trim()
.to_string()
}