loggit/logger/file_handler/
file_manager.rs1use std::{
2 fs::File,
3 io::{self, BufReader},
4};
5
6use chrono::Timelike;
7use thiserror::Error;
8use zip::{result::ZipError, write::SimpleFileOptions, CompressionMethod, ZipWriter};
9
10use crate::{
11 helper::{self, WriteToFileError},
12 logger::archivation,
13 Config,
14};
15
16use super::{
17 file_formatter::{FileFormatter, FileFormatterTryFromStringError},
18 file_name::{FileName, FileNameFromFileFormatterError},
19};
20
21#[derive(Clone, Debug)]
22pub(crate) struct FileManager {
23 file_format: FileFormatter,
24 file_name: FileName,
25 file_constraints: FileConstraints,
26 curr_file: std::sync::Arc<std::fs::File>,
27}
28
29#[derive(Error, Debug)]
30pub enum FileManagerFromStringError {
31 #[error("string parsing for the file format error: {0}")]
32 FileFormatParsingError(FileFormatterTryFromStringError),
33 #[error("format parsing for the file name error: {0}")]
34 FileNameParsingError(FileNameFromFileFormatterError),
35 #[error("io error {0}")]
36 IoError(std::io::Error)
37}
38
39#[derive(Error, Debug)]
40pub(crate) enum CompressFileError {
41 #[error("error with verifying archivation folder {0}")]
42 UnableToCreateArchivationFolder(std::io::Error),
43 #[error("unable to create a zip file: {0}")]
44 UnableToCreateZipFile(std::io::Error),
45 #[error("unable to open file to compress: {0}")]
46 UnableToOpenFileToCompress(std::io::Error),
47 #[error("unable to start zip archiving: {0}")]
48 UnableToStartZipArchiving(ZipError),
49 #[error("unable to copy the contents of the file: {0}")]
50 UnableToCopyContents(std::io::Error),
51 #[error("unable to write to archive")]
52 UnableToWriteToArchive,
53 #[error("unable to finish archivation: {0}")]
54 UnableToFinishArchivation(ZipError),
55 #[error("unable to get compression settings")]
56 UnableToGetCompressionSettings,
57 #[error("inaccessible archivation directory: {0}")]
58 InaccessibleArchivationDirectory(std::io::Error),
59}
60
61#[derive(Error, Debug)]
62pub(crate) enum VerifyConstraintsError {
63 #[error("unable to verify file existence {0}")]
64 UnableToVerifyFileExistence(std::io::Error),
65 #[error("unable to create file {0} {1}")]
66 UnableToCreateFile(String, std::io::Error),
67 #[error("unable to open file: {0} {1}")]
68 UnableToOpenFile(String, std::io::Error),
69 #[error("unable to get file metadata: {0} {1}")]
70 UnableToGetFileMetadata(String, std::io::Error),
71 #[error("unable to delete old log files: {0} {1}")]
72 UnableToDeleteOldLogFile(String, std::io::Error),
73 #[error("uable to compress file")]
74 UnableToCompressFile,
75 #[error("unable to create a new file: {0}")]
76 UnableToCreateNewFile(CreateNewFileError),
77}
78pub(crate) enum VerifyConstraintsRes {
79 ConstraintsPassed,
80 NewFileCreated,
81}
82#[derive(Debug, Error)]
83pub(crate) enum WriteLogError {
84 #[error("unable to write to the file: {0}")]
85 UnableToWriteToFile(WriteToFileError),
86}
87#[derive(Debug, Error)]
88pub(crate) enum CreateNewFileError {
89 #[error("unable to verify that the file exists: {0}")]
90 UnableToVerifyFileExistence(std::io::Error),
91 #[error("IO error occured: {0}")]
92 UnableToCreateFileIO(std::io::Error),
93 #[error("unable to get the file name: {0}")]
94 UnableToGetFileName(FileNameFromFileFormatterError),
95}
96
97impl FileManager {
98 pub(crate) fn init_from_string(
99 format: &str,
100 config: Config,
101 ) -> Result<FileManager, FileManagerFromStringError> {
102 let f_format = match FileFormatter::try_from_string(format) {
103 Ok(f) => f,
104 Err(e) => {
105 return Err(FileManagerFromStringError::FileFormatParsingError(e));
106 }
107 };
108 let f_name = match FileName::from_file_formatter(f_format.clone(), config.level) {
109 Ok(f) => f,
110 Err(e) => {
111 return Err(FileManagerFromStringError::FileNameParsingError(e));
112 }
113 };
114 let full_file_name: String = f_name.clone().into();
115
116 let mut file = match std::fs::OpenOptions::new()
117 .append(true)
118 .create(true)
119 .open(full_file_name)
120 {
121 Ok(f) => f,
122 Err(e) => {
123 return Err(FileManagerFromStringError::IoError(e));
124 }
125 };
126
127 Ok(FileManager {
128 file_format: f_format,
129 file_name: f_name,
130 file_constraints: Default::default(),
131 curr_file: std::sync::Arc::new(file)
132 })
133 }
134 pub(crate) fn get_file_name(&self) -> String {
136 self.file_name.get_full_file_name()
137 }
138 pub(crate) fn remove_rotations(&mut self) {
139 self.file_constraints.rotation = Vec::new();
140 }
141 pub(crate) fn add_rotation(&mut self, string: &str) -> bool {
142 let rot_type = match RotationType::try_from_string(string) {
143 Some(r) => r,
144 None => {
145 return false;
146 }
147 };
148 let rot = Rotation::init_from_rotation_type(rot_type);
149 self.file_constraints.rotation.push(rot);
150 true
151 }
152 pub(crate) fn set_compression(&mut self, string: &str) -> bool {
153 match CompressionType::try_from_string(string) {
154 Some(r) => {
155 self.file_constraints.compression = Some(r);
156 true
157 }
158 None => false,
159 }
160 }
161 fn set_curr_file(&mut self, curr_file: std::fs::File){
162 self.curr_file = std::sync::Arc::new(curr_file);
163 }
164 pub(crate) fn remove_compression(&mut self) {
165 self.file_constraints.compression = None;
166 }
167
168 pub(crate) fn create_new_file(&mut self, config: &Config) -> Result<(), CreateNewFileError> {
169 loop {
170 match std::path::Path::new(&self.file_name.get_full_file_name()).exists() {
171 false => {
172 let new_f_name =
173 match FileName::from_file_formatter(self.file_format.clone(), config.level)
174 {
175 Ok(r) => r,
176 Err(e) => {
177 return Err(CreateNewFileError::UnableToGetFileName(e));
178 }
179 };
180 self.file_name = new_f_name;
181 let f_name_str = self.file_name.get_full_file_name();
182 let file = match std::fs::OpenOptions::new()
183 .append(true)
184 .create(true)
185 .open(f_name_str)
186 {
187 Ok(f) => f,
188 Err(e) => {
189 return Err(CreateNewFileError::UnableToCreateFileIO(e));
190 }
191 };
192 self.set_curr_file(file);
193 return Ok(())
194 }
195 true => {
196 self.file_name.increase_num();
197 }
198 }
199 }
200 }
201
202 fn compress_zip(&self, path: &str) -> Result<(), CompressFileError> {
204 if let Err(e) = archivation::ensure_archive_dir() {
205 return Err(CompressFileError::UnableToCreateArchivationFolder(e));
206 }
207 let zip_file_path = archivation::archive_dir().join(format!("{}.zip", path));
208 let zip_file = std::fs::File::create(&zip_file_path)
209 .map_err(CompressFileError::UnableToCreateZipFile)?;
210 let mut zip = ZipWriter::new(zip_file);
211 let options = SimpleFileOptions::default().compression_method(CompressionMethod::DEFLATE);
212
213 let file =
214 std::fs::File::open(path).map_err(CompressFileError::UnableToOpenFileToCompress)?;
215 let mut reader = BufReader::new(file);
216
217 let entry_name = std::path::Path::new(path).file_name().unwrap_or_default().to_string_lossy();
218 zip.start_file(entry_name, options)
219 .map_err(CompressFileError::UnableToStartZipArchiving)?;
220 std::io::copy(&mut reader, &mut zip).map_err(CompressFileError::UnableToCopyContents)?;
221 zip.finish()
222 .map_err(CompressFileError::UnableToFinishArchivation)?;
223 Ok(())
224
225 }
227 pub(crate) fn compress_file(&self, path: &str) -> Result<(), CompressFileError> {
230 if let Err(e) = archivation::ensure_archive_dir() {
231 return Err(CompressFileError::InaccessibleArchivationDirectory(e));
232 }
233 if let Some(compr_t) = &self.file_constraints.compression {
234 match compr_t {
235 CompressionType::Zip => self.compress_zip(path),
236 }
237 } else {
238 Err(CompressFileError::UnableToGetCompressionSettings)
239 }
240 }
241 pub(crate) fn verify_constraints(
244 &mut self,
245 config: &Config,
246 ) -> Result<VerifyConstraintsRes, VerifyConstraintsError> {
247 let curr_file_name = self.file_name.get_full_file_name();
248 let file = self.curr_file.clone();
249 let f_size = match file.metadata() {
250 Err(e) => {
251 return Err(VerifyConstraintsError::UnableToGetFileMetadata(
252 curr_file_name.clone(),
253 e,
254 ));
255 }
256 Ok(data) => data.len(),
257 };
258 let mut last_idx: i32 = -1;
259 let mut idx: usize = 0;
264 let mut res: Result<VerifyConstraintsRes, VerifyConstraintsError> =
265 Ok(VerifyConstraintsRes::ConstraintsPassed);
266 loop {
267 if (idx) >= (self.file_constraints.rotation.len()) && last_idx == -1 {
268 break;
276 }
277 if (idx as i32) == last_idx {
278 break;
282 }
283 if idx >= (self.file_constraints.rotation.len()) && last_idx != -1 {
284 idx = 0;
293 }
294 if last_idx == 0 {
295 break;
298 }
299
300 let rot = self.file_constraints.rotation[idx];
303 match rot.rotation_type {
304 RotationType::Period(_) | RotationType::Time(_, _) => {
305 let unix_now = chrono::Utc::now().timestamp()
306 .max(0) as u64;
308 if unix_now > rot.next_rotation || last_idx != -1 {
309 let new_rot = Rotation::init_from_rotation_type(rot.rotation_type);
313 self.file_constraints.rotation[idx] = new_rot;
314 if last_idx == -1 {
315 match self.create_new_file(config) {
316 Ok(_) => {}
317 Err(e) => {
318 return Err(VerifyConstraintsError::UnableToCreateNewFile(e));
319 }
320 }
321 if self.compress_file(&curr_file_name).is_ok() {
322 if let Err(e) = FileManager::delete_file(&curr_file_name) {
323 res = Err(VerifyConstraintsError::UnableToDeleteOldLogFile(
324 curr_file_name.clone(),
325 e,
326 ));
327 } else {
328 res = Ok(VerifyConstraintsRes::NewFileCreated)
329 }
330 } else {
331 res = Err(VerifyConstraintsError::UnableToCompressFile)
332 }
333 last_idx = idx as i32;
334 }
335 }
336 }
337 RotationType::Size(_) => {
338 if f_size > rot.next_rotation || last_idx != -1 {
339 let new_rot = Rotation::init_from_rotation_type(rot.rotation_type);
340 self.file_constraints.rotation[idx] = new_rot;
341 if last_idx == -1 {
342 match self.create_new_file(config) {
343 Ok(_) => {}
344 Err(e) => {
345 return Err(VerifyConstraintsError::UnableToCreateNewFile(e));
346 }
347 }
348 if self.compress_file(&curr_file_name).is_ok() {
349 if let Err(e) = FileManager::delete_file(&curr_file_name) {
350 res = Err(VerifyConstraintsError::UnableToDeleteOldLogFile(
351 curr_file_name.clone(),
352 e,
353 ));
354 } else {
355 res = Ok(VerifyConstraintsRes::NewFileCreated)
356 }
357 } else {
358 res = Err(VerifyConstraintsError::UnableToCompressFile)
359 }
360 last_idx = idx as i32;
361 }
362 }
363 }
364 }
365 idx += 1;
367 }
368 res
369 }
370 pub(crate) fn delete_file(path: &str) -> io::Result<()> {
371 std::fs::remove_file(path)
372 }
373
374 pub(crate) fn write_log(
375 &mut self,
376 mess: &str,
377 config: Config,
378 ) -> Result<VerifyConstraintsRes, WriteLogError> {
379 let mut ok_res = Ok(VerifyConstraintsRes::ConstraintsPassed);
380 match self.verify_constraints(&config) {
381 Ok(r) => ok_res = Ok(r),
382 Err(e) => {
383 eprintln!("An error occured while verifying constraints: {}", e);
384 eprintln!("Trying to write to an old file");
385 ok_res = Err(e)
386 }
387 }
388
389 let arc_file = self.curr_file.clone();
390 let mut file = (*arc_file).try_clone().map_err(|e| WriteLogError::UnableToWriteToFile(WriteToFileError::UnexpectedError(e)))?;
391
392 let res = helper::write_to_file(&mut file, mess)
393 .map(|_| ok_res.unwrap())
394 .map_err(WriteLogError::UnableToWriteToFile);
395
396 self.set_curr_file(file);
397 res
398 }
399}
400
401#[derive(Debug, Clone, PartialEq, Eq, Copy)]
402pub(crate) enum RotationType {
403 Period(u64), Time(u8, u8), Size(u64), }
407
408impl RotationType {
409 pub(crate) fn try_from_string(text: &str) -> Option<RotationType> {
410 if text.contains(":") {
411 let sp: Vec<&str> = text.split(":").collect();
413 if sp.len() != 2 {
414 return None;
415 }
416 let h: u8 = match sp[0].parse() {
417 Ok(n) => n,
418 Err(_) => return None,
419 };
420 let m: u8 = match sp[1].parse() {
421 Ok(n) => n,
422 Err(_) => return None,
423 };
424 if !(0..=23).contains(&h) {
425 return None;
426 }
427 if !(0..=59).contains(&m) {
428 return None;
429 }
430 Some(RotationType::Time(h, m))
431 } else if text.ends_with(" KB")
432 || text.ends_with(" MB")
433 || text.ends_with(" GB")
434 || text.ends_with(" TB")
435 {
436 let multiply_factor;
438 if text.ends_with(" KB") {
439 multiply_factor = 1024;
440 } else if text.ends_with(" MB") {
441 multiply_factor = 1024 * 1024;
442 } else if text.ends_with(" GB") {
443 multiply_factor = 1024 * 1024 * 1024;
444 } else if text.ends_with(" TB") {
445 multiply_factor = 1024 * 1024 * 1024 * 1024;
446 } else {
447 multiply_factor = 1;
448 }
449
450 let t_len = text.len();
451 let text = &text[0..(t_len - 3)];
452 let num: u64 = match text.parse() {
453 Ok(n) => n,
454 Err(_) => {
455 return None;
456 }
457 };
458 Some(RotationType::Size(num * multiply_factor))
459 } else if text.ends_with(" hour")
460 || text.ends_with(" day")
461 || text.ends_with(" week")
462 || text.ends_with(" month")
463 || text.ends_with(" year")
464 {
465 let multiply_factor: u64;
467 let finish_txt: &str = {
468 if text.ends_with(" hour") {
469 multiply_factor = 60 * 60;
470 " hour"
471 } else if text.ends_with(" day") {
472 multiply_factor = 60 * 60 * 24;
473 " day"
474 } else if text.ends_with(" week") {
475 multiply_factor = 60 * 60 * 24 * 7;
476 " week"
477 } else if text.ends_with(" month") {
478 multiply_factor = 60 * 60 * 24 * 30;
479 " month"
480 } else {
481 multiply_factor = 60 * 60 * 24 * 365;
482 " year"
483 }
484 };
485 let fin_len = finish_txt.len();
486 let str_len = text.len();
487 let text_to_parse = &text[0..(str_len - fin_len)];
488 let num: u64 = match text_to_parse.parse() {
489 Ok(n) => n,
490 Err(_) => {
491 return None;
492 }
493 };
494 Some(RotationType::Period(num * multiply_factor))
495 } else {
496 None
497 }
498 }
499}
500
501#[derive(Clone, Copy, Debug)]
502pub(crate) struct Rotation {
503 rotation_type: RotationType,
504 next_rotation: u64,
505}
506impl Rotation {
507 pub(crate) fn init_from_rotation_type(rot_type: RotationType) -> Rotation {
508 match rot_type {
509 RotationType::Period(p) => {
510 let unix_time: u64 = chrono::Utc::now().timestamp().try_into().unwrap_or(0);
511 let next_to_rotate = unix_time + p;
512 Rotation {
513 rotation_type: rot_type,
514 next_rotation: next_to_rotate,
515 }
516 }
517 RotationType::Time(h, m) => {
518 let h = h as u64;
519 let m = m as u64;
520 let now = chrono::Local::now();
521 let curr_h: u64 = now.hour().into();
522 let curr_m: u64 = now.minute().into();
523 if curr_h < h || (curr_h == h && curr_m < m) {
524 let unix: u64 = now.timestamp().max(0) as u64;
526 let secs_curr = ((curr_h) * 60 * 60) + (curr_m * 60);
527 let secs_desirable = (h * 60 * 60) + (m * 60);
528 let diff = secs_desirable - secs_curr;
529 Rotation {
530 rotation_type: rot_type,
531 next_rotation: unix + diff,
532 }
533 } else {
534 let unix: u64 = now.timestamp().max(0) as u64;
536 let secs_till_tomorrow =
537 (24 * 60 * 60) - ((curr_h * 60 * 60) + (curr_m * 60));
538 let secs_desirable = ((h * 60 * 60) + (m * 60));
539 Rotation {
540 rotation_type: rot_type,
541 next_rotation: unix + secs_till_tomorrow + secs_desirable,
542 }
543 }
544 }
545 RotationType::Size(s) => Rotation {
546 rotation_type: rot_type,
547 next_rotation: s,
548 },
549 }
550 }
551}
552
553#[derive(Clone, Debug)]
554pub(crate) enum CompressionType {
555 Zip,
556}
557
558impl CompressionType {
559 pub(crate) fn try_from_string(text: &str) -> Option<CompressionType> {
560 if text == "zip" {
561 Some(CompressionType::Zip)
562 } else {
563 None
564 }
565 }
566}
567
568#[derive(Clone, Default, Debug)]
569pub(crate) struct FileConstraints {
570 compression: Option<CompressionType>,
571 rotation: Vec<Rotation>,
572}