use libflorescence::encoding;
use libflorescence::encoding::Encoding;
#[doc(inline)]
#[allow(unused_imports)]
pub use libflorescence::file::{
id_hash, id_parts_hash, log_id_parts_hash, Diff, Id, IdHash, Kind, Path,
};
use crate::diff;
use iced_utils::Task;
use libflorescence::prelude::*;
use libflorescence::repo;
use std::borrow::Cow;
use std::collections::HashSet;
use std::mem;
use std::num::NonZero;
use std::path::PathBuf;
use clru::{CLruCache, WeightScale};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
use tokio_stream::StreamExt;
const SRC_FILES_CACHE_CAPACITY: usize = 1024 * 1024 * 1024;
#[derive(Debug)]
pub struct State {
pub file_load_tx: mpsc::UnboundedSender<(Id, usize)>,
pub diffs_cache: DiffsCache,
}
#[derive(Debug, Clone)]
pub enum Msg {
LoadedSrcFile {
cache_counter: usize,
id: Id,
data: Vec<u8>,
encoding: Option<Encoding>,
},
SrcFileDoesntExist {
id: Id,
},
NoOp,
}
#[derive(Debug)]
pub struct DiffsCache {
pub counter: usize,
pub inner: DiffsCacheInner,
}
pub type DiffsCacheInner =
CLruCache<IdHash, Diff, std::hash::RandomState, DiffsCacheWeight>;
#[derive(Debug)]
pub struct DiffsCacheWeight;
pub fn init(repo_path: PathBuf) -> (State, Task<Msg>) {
let (src_file_load_tx, src_file_load_rx) = mpsc::unbounded_channel();
let src_file_load_rx = UnboundedReceiverStream::from(src_file_load_rx);
let src_file_load_task = Task::run(
src_file_load_rx
.map(move |(id, cache_counter): (Id, usize)| {
(repo_path.clone(), id, cache_counter)
})
.then(|(repo_path, id, cache_counter)| async move {
load_src_file(repo_path, id, cache_counter).await
}),
|msg| msg,
);
let src_files_cache_capacity =
NonZero::new(SRC_FILES_CACHE_CAPACITY).unwrap();
let diffs_cache = DiffsCache {
counter: 0,
inner: DiffsCacheInner::with_scale(
src_files_cache_capacity,
DiffsCacheWeight,
),
};
let state = State {
file_load_tx: src_file_load_tx,
diffs_cache,
};
(state, src_file_load_task)
}
#[derive(Debug)]
pub struct Loaded {
pub file_id: IdHash,
pub unchanged_sections: HashSet<usize>,
}
pub fn update(
state: &mut State,
repo: &repo::State,
msg: Msg,
) -> Option<Loaded> {
match msg {
Msg::LoadedSrcFile {
cache_counter,
id,
data,
encoding,
} => {
if state.diffs_cache.counter == cache_counter {
let file_content = match encoding {
Some(Encoding::Text(encoding)) => {
let decoded = encoding.decode(&data);
diff::FileContent::Decoded(decoded)
}
Some(Encoding::Image) => diff::FileContent::Image(data),
Some(
Encoding::Other
| Encoding::Audio
| Encoding::Video
| Encoding::Font,
)
| None => diff::FileContent::UnknownEncoding,
};
let id_hash = id_hash(&id);
match id.file_kind {
Kind::Untracked => {
let file_diff: diff::File =
diff::init_file(file_content, None);
let unchanged_sections =
diff::unchanged_sections(&file_diff);
diffs_cache_put(
&mut state.diffs_cache,
id_hash,
Diff::Loaded(file_diff),
);
return Some(Loaded {
file_id: id_hash,
unchanged_sections,
});
}
Kind::Changed => {
let changed_file = repo.changed_files.get(&id.path);
let file_diff: diff::File =
diff::init_file(file_content, changed_file);
let skip_sections =
diff::unchanged_sections(&file_diff);
diffs_cache_put(
&mut state.diffs_cache,
id_hash,
Diff::Loaded(file_diff),
);
return Some(Loaded {
file_id: id_hash,
unchanged_sections: skip_sections,
});
}
}
} else {
state
.file_load_tx
.send((id, state.diffs_cache.counter))
.unwrap();
}
}
Msg::NoOp => {}
Msg::SrcFileDoesntExist { id } => {
let id_hash = id_hash(&id);
return Some(Loaded {
file_id: id_hash,
unchanged_sections: HashSet::new(),
});
}
}
None
}
pub async fn load_src_file(
repo_path: PathBuf,
id: Id,
cache_counter: usize,
) -> Msg {
let mut path = repo_path;
path.push(&id.path.raw);
if let Ok(metadata) = tokio::fs::metadata(&path).await
&& metadata.is_dir()
{
Msg::LoadedSrcFile {
id,
data: vec![],
encoding: None,
cache_counter,
}
} else if let Ok(data) = tokio::fs::read(&path).await {
let encoding = encoding::detect(&data);
Msg::LoadedSrcFile {
id,
data,
encoding: Some(encoding),
cache_counter,
}
} else {
Msg::NoOp
}
}
pub fn try_get_src_file(state: &State, id_hash: IdHash) -> Option<&diff::File> {
state
.diffs_cache
.inner
.peek(&id_hash)
.and_then(|diff| match diff {
Diff::Loading => None,
Diff::Loaded(file) => Some(file),
})
}
pub fn load_src_file_if_not_cached(state: &mut State, id: Id) {
let id_hash = id_hash(&id);
if !state.diffs_cache.inner.contains(&id_hash) {
diffs_cache_put(&mut state.diffs_cache, id_hash, Diff::Loading);
state
.file_load_tx
.send((id, state.diffs_cache.counter))
.unwrap();
}
}
pub fn src_file_doesnt_exist(
state: &mut State,
id: Id,
changed_file: &repo::ChangedFile,
) -> Task<Msg> {
let id_hash = id_hash(&id);
if !state.diffs_cache.inner.contains(&id_hash) {
let content = diff::FileContent::Decoded(Cow::from(""));
let file = diff::init_file(content, Some(changed_file));
diffs_cache_put(&mut state.diffs_cache, id_hash, Diff::Loaded(file));
return Task::done(Msg::SrcFileDoesntExist { id });
}
Task::none()
}
pub fn diffs_cache_clear(cache: &mut DiffsCache) {
cache.inner.clear();
cache.counter = cache.counter.wrapping_add(1);
}
fn diffs_cache_put(cache: &mut DiffsCache, key: IdHash, value: Diff) {
if let Err((key, value)) = cache.inner.put_with_weight(key, value) {
let kv_weight = DiffsCacheWeight.weight(&key, &value);
info!("Source file cache is too small to fit new key. Resizing cache to {kv_weight} fit it.");
cache.inner.resize(NonZero::new(kv_weight).unwrap());
let res = cache.inner.put_with_weight(key, value);
assert!(res.is_ok());
}
}
impl WeightScale<IdHash, Diff> for DiffsCacheWeight {
fn weight(&self, _key: &IdHash, value: &Diff) -> usize {
const KEY_WEIGHT: usize = mem::size_of::<IdHash>();
let val_weight = match value {
Diff::Loading => 0,
Diff::Loaded(file) => match file {
diff::File::Decoded(diff::DecodedFile {
combined:
diff::Combined {
sections,
max_line_num: _,
},
diffs_without_contents,
}) => {
mem::size_of_val(sections)
+ mem::size_of::<usize>()
+ mem::size_of_val(diffs_without_contents)
}
diff::File::Undecodable(diff::UndecodableFile {
diffs_with_contents,
diffs_without_contents,
}) => {
mem::size_of_val(diffs_with_contents)
+ mem::size_of_val(diffs_without_contents)
}
diff::File::Image(bytes) => bytes.len(),
},
};
KEY_WEIGHT + val_weight
}
}