BFN2VHZS7VCBUHQ4S3CQ3LFQV2V4M6VANNAF32XMRFQVWRGYSZ6AC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC KLR5FRIBS6UOH3S3XAOE22TJACVSVOY7TOLW22DIWNGY27S6WZRAC IQDCHWCP47LL46EXQLQGHQPGFYIHQLMQBHA57RWJCIOX5UEUIQAQC SWWE2R6MVBX5CNM6X3WLXZTSRTU53PBJL7WJSFVF77XBPXDX4COAC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC UB2ITZJSDADVINSQEZ3HA6PVGA7OA6JYFG5GMSO7Y7LOXJC4FI7AC S2NVIFXRFER4SRA37WCT5XTXHDHAL5WIGGKY4A4XOTPLTKTZSRGQC YBJRDOTCX3ZRDB5EVXJBR55FX3CADCSIGMYWNYVC2PD5W3GXR3DQC KM5PSZ4A2FJOPHJA6RC7LHZAUXLQDZDQC2DVSE5YUORLFIPZO74QC 2VUX5BTDKHX3TJ677NW34H5WLSWH35C3PU46C7MXCN5O7PAZVXNQC A5YBC77VWH2LXCZJOPZORQJI5ZYABSCHJWVX5HVNWPM5RABXESLQC D7A7MSIHJS3IAOLEPK52M4CZLDPLO7JB3Y62XACT2AM6UUCPQ6BAC 4WO3ZJM2RNYZCBPS7FGYAEBELYD57OSS7LEUYCWGZBCAY272SNQQC PTFDJ567XGGF26TE7KVQT7WPZIWV737DBO24VFIPEWPVAVKEKADQC AMPZ2BXK4IGUZO3OPBRSJ6Z4GI5K4PRFMLUGTR6AP4FKKRWQG7LQC V55EAIWQXWER2HWKZHPJBV7DDJMSPSPWSO3FSSAYODJHVDBHUN6QC NRCUG4R2NIM2ANIETSUZ7WZDXFOOCMJ73ROP5MDYJA4RUT4PYA4QC Y5ATDI2HRWTTYJAVUR7SVWQVB4ZKKDZF3UVE4JJQFZ7RX7H7VPJQC YBLPPHZN7OSTXDQP6EKWYWK4FR4JRTLAUN5KDBBHF66C5TW74KXAC B4RMW5AEGAJX5CFC4RFPI6Y3NBSDM7GZKNBPPTTICRZSDZSYNXHQC MJDGPSHGF62FTVWZBE7MFNJTUQD42OBVJEOSVPBT553UFJLTEMXQC ZVI4AWERNOTDJ3765HJXRBZT57XPNKVONQ6TGOGNPOL2VN42KMJQC QMAUTRB6R5R7ABWT2JIDEA7LMILZOS3PGPZIF3YUFKRVLW6HGKTQC TTKR4Q76RBWCVB74UAYLVIOJIZQDSCSAXKPN7GM7WKFOA3TL47CQC OQ6HSAWHIRTAIIWMDGCTIOK47JDY7QVVAHLRDA2R5TTJKNSBFCWQC NWJD6VM6POMYKQTTPP3X6LVCWU3FHLDRIHMCSC2PPUT7JWNY42LAC AHWWRC73FXLSUDAJBU5UU76MZETHD3DSGJ7OLZPFEHXBDJ733QNAC JE44NYHM4QORCRKOF33QM42EDT7SBCPTULWGT6IVDL3D5LUHQXLAC ONRCENKTUB4JJMPXNAQQYEWDYD54TAGOLWH742GF4EH3KTHV7YLQC 4ELJZGRJNL6FXB33QTYDNPY57JA3WZPUXKLQRTGSLDM7W65PD3YQC HC7ROIBC66IBYFED4ZZM7RXGSNC2CCBWBI36RKM2G5FD5DKVEYMQC FR52XEMWD22VH3GKSARXJUJXOGO7ZSQEHWPXFRWHLGRAJU3WRKCAC CALXOZXANFZ64NBZBTR2KYTZ6ZLLCJXNFAEALBB2EYAVDVJJ6X6AC L6KSEFQIWICZJ6HJUFKLZQDEH6X2QMFM4Z7ZZUGMLDMFF7EHRXWAC use tokio_stream::StreamExt;// Invariant: Must be non-zeroconst SRC_FILES_CACHE_CAPACITY: usize = 1024 * 1024 * 1024;
let (src_file_load_tx, src_file_load_rx) = watch::channel((FileId {path: "".to_string(),file_kind: FileKind::Untracked,},0,));let src_file_load_rx = WatchStream::from_changes(src_file_load_rx);let repo_path_clone = repo_path.clone();let src_file_load_task = Task::run(src_file_load_rx.map(move |(id, cache_counter): (FileId, usize)| {(repo_path_clone.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();
src_file_load_tx: watch::Sender<(FileId, usize)>,diffs_cache: DiffsCache,diffs_state: HashMap<FileId, diff::State>,}#[derive(Debug)]struct DiffsCache {/// Used to prevent race-conditions between file loading and clearing cachecounter: usize,inner: DiffsCacheInner,
files: file::State,diffs_state: HashMap<file::Id, diff::State>,
enum FileDiff {Loading,Loaded(diff::File),}#[derive(Debug, Clone, Hash, PartialEq, Eq)]struct FileId {path: String,file_kind: FileKind,}#[derive(Debug)]struct DiffsCacheWeight;impl WeightScale<FileId, FileDiff> for DiffsCacheWeight {fn weight(&self, key: &FileId, value: &FileDiff) -> usize {let key_weight = key.path.len();let val_weight = match value {FileDiff::Loading => 0,FileDiff::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)}},};key_weight + val_weight}}#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]enum FileKind {Untracked,Changed,}#[derive(Debug)]
LoadedSrcFile {/// The cache counter value when the file was requestedcache_counter: usize,id: FileId,data: Vec<u8>,encoding: Option<pijul::Encoding>,},
File(file::Msg),
}Task::none()}Message::LoadedSrcFile {cache_counter,id,data,encoding,} => {if state.diffs_cache.counter == cache_counter {let file_content = match encoding {Some(encoding) => {let decoded = encoding.decode(&data);diff::FileContent::Decoded(decoded)}None => diff::FileContent::UnknownEncoding,};match id.file_kind {FileKind::Untracked => {let file_diff: diff::File =diff::init_file(file_content, None);diffs_cache_put(&mut state.diffs_cache,id,FileDiff::Loaded(file_diff),);}FileKind::Changed => {if let Some(repo) = state.repo.as_ref() {let changed_file = repo.changed_files.get(&id.path);let file_diff: diff::File =diff::init_file(file_content, changed_file);diffs_cache_put(&mut state.diffs_cache,id,FileDiff::Loaded(file_diff),);}}}
load_src_file_if_not_cached(&mut state.diffs_cache,&state.src_file_load_tx,FileId {
file::load_src_file_if_not_cached(&mut state.files,file::Id {
}async fn load_src_file(repo_path: PathBuf,id: FileId,cache_counter: usize,) -> Message {let mut path = repo_path;path.push(&id.path);if let Ok(data) = tokio::fs::read(&path).await {let encoding = pijul::change::get_encoding(&data);Message::LoadedSrcFile {id,data,encoding,cache_counter,}} else {Message::NoOp}
fn load_src_file_if_not_cached(cache: &mut DiffsCache,load_tx: &watch::Sender<(FileId, usize)>,id: FileId,) {if !cache.inner.contains(&id) {diffs_cache_put(cache, id.clone(), FileDiff::Loading);load_tx.send((id, cache.counter)).unwrap();}}fn diffs_cache_put(cache: &mut DiffsCache, key: FileId, value: FileDiff) {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 hold {}. Resizing cache to to {kv_weight} fit it.", key.path);cache.inner.resize(NonZero::new(kv_weight).unwrap());let res = cache.inner.put_with_weight(key, value);assert!(res.is_ok());}}fn diffs_cache_clear(cache: &mut DiffsCache) {cache.inner.clear();cache.counter = cache.counter.wrapping_add(1);}
//! Provides a way to load files from disk and to cache them.use crate::diff;use libflorescence::prelude::*;use libflorescence::repo;use std::mem;use std::num::NonZero;use std::path::PathBuf;use clru::{CLruCache, WeightScale};use iced::Task;use tokio::sync::watch;use tokio_stream::wrappers::WatchStream;use tokio_stream::StreamExt;// Invariant: Must be non-zeroconst SRC_FILES_CACHE_CAPACITY: usize = 1024 * 1024 * 1024;#[derive(Debug)]pub struct State {pub file_load_tx: watch::Sender<(Id, usize)>,pub diffs_cache: DiffsCache,}#[derive(Debug, Clone)]pub enum Msg {LoadedSrcFile {/// The cache counter value when the file was requestedcache_counter: usize,id: Id,data: Vec<u8>,encoding: Option<pijul::Encoding>,},NoOp,}#[derive(Debug)]pub struct DiffsCache {/// Used to prevent race-conditions between file loading and clearing cachepub counter: usize,pub inner: DiffsCacheInner,}pub type DiffsCacheInner =CLruCache<Id, Diff, std::hash::RandomState, DiffsCacheWeight>;#[derive(Debug)]pub enum Diff {Loading,Loaded(diff::File),}#[derive(Debug, Clone, Hash, PartialEq, Eq)]pub struct Id {pub path: String,pub file_kind: Kind,}#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]pub enum Kind {Untracked,Changed,}#[derive(Debug)]pub struct DiffsCacheWeight;pub fn init(repo_path: PathBuf) -> (State, Task<Msg>) {let (src_file_load_tx, src_file_load_rx) = watch::channel((Id {path: "".to_string(),file_kind: Kind::Untracked,},0,));let src_file_load_rx = WatchStream::from_changes(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)}pub fn update(state: &mut State, repo: Option<&repo::State>, msg: Msg) {match msg {Msg::LoadedSrcFile {cache_counter,id,data,encoding,} => {if state.diffs_cache.counter == cache_counter {let file_content = match encoding {Some(encoding) => {let decoded = encoding.decode(&data);diff::FileContent::Decoded(decoded)}None => diff::FileContent::UnknownEncoding,};match id.file_kind {Kind::Untracked => {let file_diff: diff::File =diff::init_file(file_content, None);diffs_cache_put(&mut state.diffs_cache,id,Diff::Loaded(file_diff),);}Kind::Changed => {if let Some(repo) = repo {let changed_file = repo.changed_files.get(&id.path);let file_diff: diff::File =diff::init_file(file_content, changed_file);diffs_cache_put(&mut state.diffs_cache,id,Diff::Loaded(file_diff),);}}}}}Msg::NoOp => {}}}pub async fn load_src_file(repo_path: PathBuf,id: Id,cache_counter: usize,) -> Msg {let mut path = repo_path;path.push(&id.path);if let Ok(data) = tokio::fs::read(&path).await {let encoding = pijul::change::get_encoding(&data);Msg::LoadedSrcFile {id,data,encoding,cache_counter,}} else {Msg::NoOp}}pub fn load_src_file_if_not_cached(state: &mut State, id: Id) {if !state.diffs_cache.inner.contains(&id) {diffs_cache_put(&mut state.diffs_cache, id.clone(), Diff::Loading);state.file_load_tx.send((id, state.diffs_cache.counter)).unwrap();}}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: Id, 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 hold {}. Resizing cache to to {kv_weight} fit it.", key.path);cache.inner.resize(NonZero::new(kv_weight).unwrap());let res = cache.inner.put_with_weight(key, value);assert!(res.is_ok());}}impl WeightScale<Id, Diff> for DiffsCacheWeight {fn weight(&self, key: &Id, value: &Diff) -> usize {let key_weight = key.path.len();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)}},};key_weight + val_weight}}
let (selection, task) =if !repo.untracked_files.is_empty() {let ix = 0;let selection = Some(untracked_file_selection(repo,ix,diffs_cache,src_file_load_tx,));(selection, Task::none())} else if !repo.changed_files.is_empty() {let ix = 0;let selection = Some(changed_file_selection(repo,ix,diffs_cache,src_file_load_tx,));(selection, Task::none())} else if !repo.log.is_empty() {let ix = repo.log.len() - 1;let (selection, task) = log_selection(repo, ix);(Some(selection), task)} else {(None, Task::none())};
let (selection, task) = if !repo.untracked_files.is_empty(){let ix = 0;let selection =Some(untracked_file_selection(repo, ix, files));(selection, Task::none())} else if !repo.changed_files.is_empty() {let ix = 0;let selection =Some(changed_file_selection(repo, ix, files));(selection, Task::none())} else if !repo.log.is_empty() {let ix = repo.log.len() - 1;let (selection, task) = log_selection(repo, ix);(Some(selection), task)} else {(None, Task::none())};