1use std::collections::HashSet;
2use std::io::BufRead;
3use std::path::PathBuf;
4
5use crate::Hashes;
6use crate::config::{Compression, Config};
7use anyhow::Result;
8use jiff::Timestamp;
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Action {
15 Add {
16 file: AuditFile,
17 compression: Compression,
18 },
19 Init {
20 settings: Config,
21 project_path: PathBuf,
22 },
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct AuditFile {
27 pub path: PathBuf,
28 pub hashes: Hashes,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct AuditEntry {
33 pub operation_id: String,
34 pub timestamp: i64,
35 pub user: String,
36 pub action: Action,
37}
38
39impl AuditEntry {
40 pub fn new_add(operation_id: Uuid, file: AuditFile, compression: Compression) -> Self {
41 let timestamp = Timestamp::now().as_second();
42 let user = whoami::username().unwrap_or_else(|_| "unknown".to_string());
43
44 Self {
45 operation_id: operation_id.to_string(),
46 timestamp,
47 user,
48 action: Action::Add { file, compression },
49 }
50 }
51
52 pub fn new_init(operation_id: Uuid, config: Config, project_path: PathBuf) -> Self {
53 let timestamp = Timestamp::now().as_second();
54 let user = whoami::username().unwrap_or_else(|_| "unknown".to_string());
55 let project_path = project_path.canonicalize().unwrap_or(project_path);
56
57 Self {
58 operation_id: operation_id.to_string(),
59 timestamp,
60 user,
61 action: Action::Init {
62 settings: config,
63 project_path,
64 },
65 }
66 }
67}
68
69pub fn parse_audit_log(
70 reader: impl BufRead,
71 only_files: &HashSet<PathBuf>,
72) -> Result<Vec<AuditEntry>> {
73 reader
74 .lines()
75 .map(|line| line.map_err(anyhow::Error::from))
76 .filter_map(|line| match line {
77 Ok(l) if l.trim().is_empty() => None,
78 other => Some(other),
79 })
80 .map(|line| Ok(serde_json::from_str::<AuditEntry>(&line?)?))
81 .filter(|entry| match entry {
82 Ok(e) => {
83 if only_files.is_empty() {
84 true
85 } else {
86 match &e.action {
87 Action::Add { file, .. } => only_files.contains(&file.path),
88 Action::Init { .. } => false,
89 }
90 }
91 }
92 Err(_) => true, })
94 .collect()
95}