Skip to content

Commit 1295e91

Browse files
committed
extracting cache struct
1 parent 16a064e commit 1295e91

File tree

6 files changed

+172
-127
lines changed

6 files changed

+172
-127
lines changed

src/cache/mod.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use crate::project::Error;
2+
use error_stack::{Result, ResultExt};
3+
use std::{
4+
collections::HashMap,
5+
fs::{self, File, OpenOptions},
6+
io::{BufReader, BufWriter},
7+
path::{Path, PathBuf},
8+
sync::Mutex,
9+
};
10+
11+
pub trait Cache {
12+
fn get_file_owner(&self, path: &Path) -> Result<Option<FileOwnerCacheEntry>, Error>;
13+
fn write_file_owner(&self, path: &Path, owner: Option<String>);
14+
}
15+
16+
#[derive(Debug)]
17+
pub struct GlobalCache<'a> {
18+
base_path: &'a PathBuf,
19+
cache_directory: &'a String,
20+
file_owner_cache: Option<Box<Mutex<HashMap<PathBuf, FileOwnerCacheEntry>>>>,
21+
}
22+
23+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
24+
pub struct FileOwnerCacheEntry {
25+
timestamp: u64,
26+
pub owner: Option<String>,
27+
}
28+
29+
const DEFAULT_CACHE_CAPACITY: usize = 10000;
30+
31+
impl<'a> GlobalCache<'a> {
32+
pub fn new(base_path: &'a PathBuf, cache_directory: &'a String) -> Self {
33+
Self {
34+
base_path,
35+
cache_directory,
36+
file_owner_cache: None,
37+
}
38+
}
39+
40+
pub fn persist_cache(&self) -> Result<(), Error> {
41+
let cache_path = self.get_cache_path();
42+
let file = OpenOptions::new()
43+
.write(true)
44+
.create(true)
45+
.truncate(true)
46+
.open(cache_path)
47+
.change_context(Error::Io)?;
48+
49+
let writer = BufWriter::new(file);
50+
let cache = self.file_owner_cache.as_ref().unwrap().lock().map_err(|_| Error::Io)?;
51+
serde_json::to_writer(writer, &*cache).change_context(Error::SerdeJson)
52+
}
53+
54+
pub fn load_cache(&mut self) -> Result<(), Error> {
55+
let cache_path = self.get_cache_path();
56+
if !cache_path.exists() {
57+
self.file_owner_cache = Some(Box::new(Mutex::new(HashMap::with_capacity(DEFAULT_CACHE_CAPACITY))));
58+
return Ok(());
59+
}
60+
61+
let file = File::open(cache_path).change_context(Error::Io)?;
62+
let reader = BufReader::new(file);
63+
let json = serde_json::from_reader(reader);
64+
self.file_owner_cache = match json {
65+
Ok(cache) => Some(Box::new(Mutex::new(cache))),
66+
_ => Some(Box::new(Mutex::new(HashMap::with_capacity(DEFAULT_CACHE_CAPACITY)))),
67+
};
68+
Ok(())
69+
}
70+
71+
pub fn get_file_owner(&self, path: &Path) -> Result<Option<FileOwnerCacheEntry>, Error> {
72+
if let Ok(cache) = self.file_owner_cache.as_ref().unwrap().lock() {
73+
if let Some(cached_entry) = cache.get(path) {
74+
let timestamp = Self::get_file_timestamp(path)?;
75+
if cached_entry.timestamp == timestamp {
76+
return Ok(Some(cached_entry.clone()));
77+
}
78+
}
79+
}
80+
Ok(None)
81+
}
82+
83+
pub fn write_file_owner(&self, path: &Path, owner: Option<String>) {
84+
if let Ok(mut cache) = self.file_owner_cache.as_ref().unwrap().lock() {
85+
if let Ok(timestamp) = Self::get_file_timestamp(path) {
86+
cache.insert(path.to_path_buf(), FileOwnerCacheEntry { timestamp, owner });
87+
}
88+
}
89+
}
90+
91+
fn get_cache_path(&self) -> PathBuf {
92+
let cache_dir = self.base_path.join(PathBuf::from(&self.cache_directory));
93+
fs::create_dir_all(&cache_dir).unwrap();
94+
95+
cache_dir.join("project-file-cache.json")
96+
}
97+
98+
pub fn delete_cache(&self) -> Result<(), Error> {
99+
let cache_path = self.get_cache_path();
100+
dbg!("deleting", &cache_path);
101+
fs::remove_file(cache_path).change_context(Error::Io)
102+
}
103+
104+
fn get_file_timestamp(path: &Path) -> Result<u64, Error> {
105+
let metadata = fs::metadata(path).change_context(Error::Io)?;
106+
metadata
107+
.modified()
108+
.change_context(Error::Io)?
109+
.duration_since(std::time::UNIX_EPOCH)
110+
.change_context(Error::Io)
111+
.map(|duration| duration.as_secs())
112+
}
113+
}

src/cli.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use clap::{Parser, Subcommand};
22
use codeowners::{
3+
cache::GlobalCache,
4+
config::Config,
35
ownership::{FileOwner, Ownership},
46
project_builder::ProjectBuilder,
57
};
@@ -98,12 +100,23 @@ pub fn cli() -> Result<(), Error> {
98100
.change_context(Error::Io)
99101
.attach_printable(format!("Can't open config file: {}", config_path.to_string_lossy()))?;
100102

101-
let config = serde_yaml::from_reader(config_file).change_context(Error::Io)?;
103+
let config: Config = serde_yaml::from_reader(config_file).change_context(Error::Io)?;
102104

103-
let mut project_builder = ProjectBuilder::new(&config, project_root, codeowners_file_path.clone(), !args.no_cache);
105+
let mut global_cache = GlobalCache::new(&project_root, &config.cache_directory);
106+
global_cache.load_cache().change_context(Error::Io)?;
107+
108+
let mut project_builder = ProjectBuilder::new(
109+
&config,
110+
project_root.clone(),
111+
codeowners_file_path.clone(),
112+
!args.no_cache,
113+
&global_cache,
114+
);
104115
let project = project_builder.build().change_context(Error::Io)?;
105116
let ownership = Ownership::build(project);
106117

118+
global_cache.persist_cache().change_context(Error::Io)?;
119+
107120
match args.command {
108121
Command::Validate => ownership.validate().change_context(Error::ValidationFailed)?,
109122
Command::Generate => {
@@ -140,7 +153,7 @@ pub fn cli() -> Result<(), Error> {
140153
Err(err) => println!("{}", err),
141154
},
142155
Command::DeleteCache => {
143-
project_builder.delete_cache().change_context(Error::Io)?;
156+
global_cache.delete_cache().change_context(Error::Io)?;
144157
}
145158
}
146159

src/common_test.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub mod tests {
1010

1111
use tempfile::tempdir;
1212

13-
use crate::{ownership::Ownership, project_builder::ProjectBuilder};
13+
use crate::{cache::GlobalCache, config::Config, ownership::Ownership, project_builder::ProjectBuilder};
1414

1515
macro_rules! ownership {
1616
($($test_files:expr),+) => {{
@@ -107,17 +107,30 @@ pub mod tests {
107107
}
108108

109109
let config_file = File::open(test_config.temp_dir_path.join(test_config.relative_code_ownership_config_yml_path))?;
110-
let config = serde_yaml::from_reader(config_file)?;
110+
let config: Config = serde_yaml::from_reader(config_file)?;
111111

112112
let codeowners_file_path = &test_config.temp_dir_path.join(".github/CODEOWNERS");
113-
let mut builder = ProjectBuilder::new(&config, test_config.temp_dir_path.clone(), codeowners_file_path.clone(), false);
113+
let global_cache = GlobalCache::new(&test_config.temp_dir_path, &config.cache_directory);
114+
let mut builder = ProjectBuilder::new(
115+
&config,
116+
test_config.temp_dir_path.clone(),
117+
codeowners_file_path.clone(),
118+
false,
119+
&global_cache,
120+
);
114121
let project = builder.build()?;
115122
let ownership = Ownership::build(project);
116123
if test_config.generate_codeowners {
117124
std::fs::write(codeowners_file_path, ownership.generate_file())?;
118125
}
119126
// rebuild project to ensure new codeowners file is read
120-
let mut builder = ProjectBuilder::new(&config, test_config.temp_dir_path.clone(), codeowners_file_path.clone(), false);
127+
let mut builder = ProjectBuilder::new(
128+
&config,
129+
test_config.temp_dir_path.clone(),
130+
codeowners_file_path.clone(),
131+
false,
132+
&global_cache,
133+
);
121134
let project = builder.build()?;
122135
Ok(Ownership::build(project))
123136
}

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
pub mod cache;
12
pub(crate) mod common_test;
2-
pub(crate) mod config;
3+
pub mod config;
34
pub mod ownership;
45
pub(crate) mod project;
56
pub mod project_builder;

src/project_builder.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator};
1010
use tracing::instrument;
1111

1212
use crate::{
13+
cache::GlobalCache,
1314
config::Config,
1415
project::{deserializers, DirectoryCodeownersFile, Error, Package, PackageType, Project, ProjectFile, Team, VendoredGem},
1516
project_file_builder::ProjectFileBuilder,
@@ -38,8 +39,14 @@ pub struct ProjectBuilder<'a> {
3839
const INITIAL_VECTOR_CAPACITY: usize = 1000;
3940

4041
impl<'a> ProjectBuilder<'a> {
41-
pub fn new(config: &'a Config, base_path: PathBuf, codeowners_file_path: PathBuf, use_cache: bool) -> Self {
42-
let project_file_builder = ProjectFileBuilder::new(config, base_path.clone(), use_cache);
42+
pub fn new(
43+
config: &'a Config,
44+
base_path: PathBuf,
45+
codeowners_file_path: PathBuf,
46+
use_cache: bool,
47+
global_cache: &'a GlobalCache,
48+
) -> Self {
49+
let project_file_builder = ProjectFileBuilder::new(use_cache, global_cache);
4350
Self {
4451
project_file_builder,
4552
config,
@@ -59,13 +66,7 @@ impl<'a> ProjectBuilder<'a> {
5966
let entry = entry.change_context(Error::Io)?;
6067
entry_types.push(self.build_entry_type(entry)?);
6168
}
62-
let project = self.build_project_from_entry_types(entry_types);
63-
self.project_file_builder.possibly_save_cache()?;
64-
project
65-
}
66-
67-
pub fn delete_cache(&mut self) -> Result<(), Error> {
68-
self.project_file_builder.delete_cache()
69+
self.build_project_from_entry_types(entry_types)
6970
}
7071

7172
fn build_entry_type(&mut self, entry: ignore::DirEntry) -> Result<EntryType, Error> {

0 commit comments

Comments
 (0)