lastfm_client/file_handler.rs
1use chrono::Local;
2use csv::Writer;
3use serde::Serialize;
4use std::collections::HashMap;
5use std::fs::{self, File, OpenOptions};
6use std::io::{Result, prelude::*};
7
8use crate::lastfm_handler::TrackPlayInfo;
9
10#[allow(dead_code)]
11pub enum FileFormat {
12 Json,
13 Csv,
14}
15
16pub struct FileHandler;
17
18impl FileHandler {
19 /// Save data to a file in the data directory.
20 ///
21 /// # Arguments
22 /// * `data` - Data to save
23 /// * `format` - File format to save as
24 /// * `filename_prefix` - Prefix for the filename
25 ///
26 /// # Errors
27 /// * `std::io::Error` - If the file cannot be opened or written to
28 /// * `serde_json::Error` - If the JSON cannot be serialized
29 ///
30 /// # Returns
31 /// * `Result<String>` - Filename of the saved file
32 pub fn save<T: Serialize>(
33 data: &[T],
34 format: &FileFormat,
35 filename_prefix: &str,
36 ) -> Result<String> {
37 // Create data directory if it doesn't exist
38 fs::create_dir_all("data")?;
39
40 // Generate timestamp
41 let timestamp = Local::now().format("%Y%m%d_%H%M%S");
42
43 // Create filename with timestamp
44 let filename = format!(
45 "data/{}_{}.{}",
46 filename_prefix,
47 timestamp,
48 match format {
49 FileFormat::Json => "json",
50 FileFormat::Csv => "csv",
51 }
52 );
53
54 match format {
55 FileFormat::Json => {
56 // Special case: if T is a HashMap with track info
57 if std::any::type_name::<T>()
58 == std::any::type_name::<HashMap<String, TrackPlayInfo>>()
59 && let Some(single_item) = data.first()
60 {
61 Self::save_single(single_item, &filename)?;
62 return Ok(filename);
63 }
64 Self::save_as_json(data, &filename)
65 }
66 FileFormat::Csv => Self::save_as_csv(data, &filename),
67 }?;
68
69 Ok(filename)
70 }
71
72 /// Save data to a JSON file.
73 ///
74 /// # Arguments
75 /// * `data` - Data to save
76 /// * `filename` - Filename to save as
77 #[allow(dead_code)]
78 fn save_as_json<T: Serialize>(data: &[T], filename: &str) -> Result<()> {
79 let json = serde_json::to_string_pretty(data)?;
80 let mut file = File::create(filename)?;
81
82 file.write_all(json.as_bytes())?;
83
84 Ok(())
85 }
86
87 /// Save data to a CSV file.
88 ///
89 /// # Arguments
90 /// * `data` - Data to save
91 /// * `filename` - Filename to save as
92 fn save_as_csv<T: Serialize>(data: &[T], filename: &str) -> Result<()> {
93 let mut writer = Writer::from_path(filename)?;
94
95 for item in data {
96 writer.serialize(item)?;
97 }
98
99 writer.flush()?;
100 Ok(())
101 }
102
103 /// Append data to an existing file.
104 ///
105 /// # Arguments
106 /// * `data` - Data to append
107 /// * `file_path` - Path to the file to append to
108 ///
109 /// # Returns
110 /// * `Result<String>` - Path of the updated file
111 ///
112 /// Append data to an existing file.
113 ///
114 /// # Arguments
115 /// * `data` - Data to append
116 /// * `file_path` - Path to the file to append to
117 ///
118 /// # Errors
119 /// * `std::io::Error` - If an I/O error occurs
120 ///
121 /// # Returns
122 /// * `Result<String>` - Path of the updated file
123 #[allow(dead_code)]
124 pub fn append<T: Serialize + for<'de> serde::Deserialize<'de> + Clone>(
125 data: &[T],
126 file_path: &str,
127 ) -> Result<String> {
128 // Determine file format from extension
129 let format = if std::path::Path::new(file_path)
130 .extension()
131 .is_some_and(|ext| ext.eq_ignore_ascii_case("json"))
132 {
133 FileFormat::Json
134 } else if std::path::Path::new(file_path)
135 .extension()
136 .is_some_and(|ext| ext.eq_ignore_ascii_case("csv"))
137 {
138 FileFormat::Csv
139 } else {
140 return Err(std::io::Error::new(
141 std::io::ErrorKind::InvalidInput,
142 "Unsupported file format",
143 ));
144 };
145
146 match format {
147 FileFormat::Json => {
148 // For JSON, we need to read the existing data, combine it, and write it back
149 let file = File::open(file_path)?;
150 let mut existing_data: Vec<T> = serde_json::from_reader(file)?;
151
152 existing_data.extend(data.iter().cloned());
153
154 Self::save_as_json(&existing_data, file_path)?;
155 }
156 FileFormat::Csv => {
157 // For CSV, we can simply append to the file
158 let mut writer =
159 Writer::from_writer(OpenOptions::new().append(true).open(file_path)?);
160
161 for item in data {
162 writer.serialize(item)?;
163 }
164 writer.flush()?;
165 }
166 }
167
168 Ok(file_path.to_string())
169 }
170
171 /// Save a single item to a JSON file
172 ///
173 /// # Errors
174 /// * `std::io::Error` - If there was an error reading or writing the file
175 /// * `serde_json::Error` - If there was an error serializing the data
176 ///
177 /// # Arguments
178 /// * `data` - Data to save
179 /// * `filename` - Filename to save as
180 pub fn save_single<T: Serialize>(data: &T, filename: &str) -> Result<()> {
181 let json = serde_json::to_string_pretty(data)?;
182 let mut file = File::create(filename)?;
183 file.write_all(json.as_bytes())?;
184 Ok(())
185 }
186}