1use async_trait::async_trait;
7use parking_lot::RwLock;
8use std::collections::BTreeMap;
9use std::io::{self, Read, Write};
10use std::ops::Range;
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13
14use super::{Directory, FileHandle, OwnedBytes, RangeReadFn};
15
16pub const SLICE_CACHE_EXTENSION: &str = "slicecache";
18
19const SLICE_CACHE_MAGIC: &[u8; 8] = b"HRMSCACH";
21
22const SLICE_CACHE_VERSION: u32 = 2;
25
26#[derive(Debug, Clone)]
28struct CachedSlice {
29 range: Range<u64>,
31 data: Arc<Vec<u8>>,
33 access_count: u64,
35}
36
37struct FileSliceCache {
39 slices: BTreeMap<u64, CachedSlice>,
41 total_bytes: usize,
43}
44
45impl FileSliceCache {
46 fn new() -> Self {
47 Self {
48 slices: BTreeMap::new(),
49 total_bytes: 0,
50 }
51 }
52
53 fn serialize(&self) -> Vec<u8> {
55 let mut buf = Vec::new();
56 buf.extend_from_slice(&(self.slices.len() as u32).to_le_bytes());
58 for slice in self.slices.values() {
59 buf.extend_from_slice(&slice.range.start.to_le_bytes());
61 buf.extend_from_slice(&slice.range.end.to_le_bytes());
62 buf.extend_from_slice(&(slice.data.len() as u32).to_le_bytes());
64 buf.extend_from_slice(&slice.data);
65 }
66 buf
67 }
68
69 fn deserialize(data: &[u8], access_counter: u64) -> io::Result<(Self, usize)> {
71 let mut pos = 0;
72 if data.len() < 4 {
73 return Err(io::Error::new(
74 io::ErrorKind::InvalidData,
75 "truncated slice cache",
76 ));
77 }
78 let num_slices = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
79 pos += 4;
80
81 let mut cache = FileSliceCache::new();
82 for _ in 0..num_slices {
83 if pos + 20 > data.len() {
84 return Err(io::Error::new(
85 io::ErrorKind::InvalidData,
86 "truncated slice entry",
87 ));
88 }
89 let range_start = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
90 pos += 8;
91 let range_end = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
92 pos += 8;
93 let data_len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
94 pos += 4;
95
96 if pos + data_len > data.len() {
97 return Err(io::Error::new(
98 io::ErrorKind::InvalidData,
99 "truncated slice data",
100 ));
101 }
102 let slice_data = data[pos..pos + data_len].to_vec();
103 pos += data_len;
104
105 cache.total_bytes += slice_data.len();
106 cache.slices.insert(
107 range_start,
108 CachedSlice {
109 range: range_start..range_end,
110 data: Arc::new(slice_data),
111 access_count: access_counter,
112 },
113 );
114 }
115 Ok((cache, pos))
116 }
117
118 #[allow(dead_code)]
120 fn iter_slices(&self) -> impl Iterator<Item = (&u64, &CachedSlice)> {
121 self.slices.iter()
122 }
123
124 fn try_read(&mut self, range: Range<u64>, access_counter: &mut u64) -> Option<Vec<u8>> {
126 let start = range.start;
128 let end = range.end;
129
130 let mut found_key = None;
132 for (&slice_start, slice) in self.slices.range(..=start).rev() {
133 if slice_start <= start && slice.range.end >= end {
134 found_key = Some((
135 slice_start,
136 (start - slice_start) as usize,
137 (end - start) as usize,
138 ));
139 break;
140 }
141 }
142
143 if let Some((key, offset, len)) = found_key {
144 *access_counter += 1;
146 if let Some(s) = self.slices.get_mut(&key) {
147 s.access_count = *access_counter;
148 return Some(s.data[offset..offset + len].to_vec());
149 }
150 }
151
152 None
153 }
154
155 fn insert(&mut self, range: Range<u64>, data: Vec<u8>, access_counter: u64) -> isize {
158 let start = range.start;
159 let end = range.end;
160 let data_len = data.len();
161
162 let mut to_remove = Vec::new();
164 let mut merged_start = start;
165 let mut merged_end = end;
166 let mut merged_data: Option<Vec<u8>> = None;
167 let mut bytes_removed: usize = 0;
168
169 for (&slice_start, slice) in &self.slices {
170 if slice_start < end && slice.range.end > start {
172 to_remove.push(slice_start);
173
174 merged_start = merged_start.min(slice_start);
176 merged_end = merged_end.max(slice.range.end);
177 }
178 }
179
180 if !to_remove.is_empty() {
182 let merged_len = (merged_end - merged_start) as usize;
183 let mut new_data = vec![0u8; merged_len];
184
185 for &slice_start in &to_remove {
187 if let Some(slice) = self.slices.get(&slice_start) {
188 let offset = (slice_start - merged_start) as usize;
189 new_data[offset..offset + slice.data.len()].copy_from_slice(&slice.data);
190 bytes_removed += slice.data.len();
191 self.total_bytes -= slice.data.len();
192 }
193 }
194
195 let offset = (start - merged_start) as usize;
197 new_data[offset..offset + data_len].copy_from_slice(&data);
198
199 for slice_start in to_remove {
201 self.slices.remove(&slice_start);
202 }
203
204 merged_data = Some(new_data);
205 }
206
207 let (final_start, final_data) = if let Some(md) = merged_data {
209 (merged_start, md)
210 } else {
211 (start, data)
212 };
213
214 let bytes_added = final_data.len();
215 self.total_bytes += bytes_added;
216
217 self.slices.insert(
218 final_start,
219 CachedSlice {
220 range: final_start..final_start + bytes_added as u64,
221 data: Arc::new(final_data),
222 access_count: access_counter,
223 },
224 );
225
226 bytes_added as isize - bytes_removed as isize
228 }
229
230 fn evict_lru(&mut self, bytes_to_free: usize) -> usize {
232 let mut freed = 0;
233
234 while freed < bytes_to_free && !self.slices.is_empty() {
235 let lru_key = self
237 .slices
238 .iter()
239 .min_by_key(|(_, s)| s.access_count)
240 .map(|(&k, _)| k);
241
242 if let Some(key) = lru_key {
243 if let Some(slice) = self.slices.remove(&key) {
244 freed += slice.data.len();
245 self.total_bytes -= slice.data.len();
246 }
247 } else {
248 break;
249 }
250 }
251
252 freed
253 }
254}
255
256pub struct SliceCachingDirectory<D: Directory> {
264 inner: Arc<D>,
265 caches: Arc<RwLock<std::collections::HashMap<PathBuf, FileSliceCache>>>,
267 file_sizes: Arc<RwLock<std::collections::HashMap<PathBuf, u64>>>,
269 max_bytes: usize,
271 current_bytes: Arc<RwLock<usize>>,
273 access_counter: Arc<RwLock<u64>>,
275 label: super::IndexLabel,
277}
278
279impl<D: Directory> SliceCachingDirectory<D> {
280 pub fn new(inner: D, max_bytes: usize) -> Self {
282 Self {
283 inner: Arc::new(inner),
284 caches: Arc::new(RwLock::new(std::collections::HashMap::new())),
285 file_sizes: Arc::new(RwLock::new(std::collections::HashMap::new())),
286 max_bytes,
287 current_bytes: Arc::new(RwLock::new(0)),
288 access_counter: Arc::new(RwLock::new(0)),
289 label: super::IndexLabel::default(),
290 }
291 }
292
293 pub fn inner(&self) -> &D {
295 &self.inner
296 }
297
298 fn try_cache_read(&self, path: &Path, range: Range<u64>) -> Option<Vec<u8>> {
300 let mut caches = self.caches.write();
301 let mut counter = self.access_counter.write();
302
303 if let Some(file_cache) = caches.get_mut(path) {
304 file_cache.try_read(range, &mut counter)
305 } else {
306 None
307 }
308 }
309
310 fn cache_insert(&self, path: &Path, range: Range<u64>, data: Vec<u8>) {
312 let data_len = data.len();
313
314 {
316 let current = *self.current_bytes.read();
317 if current + data_len > self.max_bytes {
318 self.evict_to_fit(data_len);
319 }
320 }
321
322 let mut caches = self.caches.write();
323 let counter = *self.access_counter.read();
324
325 let file_cache = caches
326 .entry(path.to_path_buf())
327 .or_insert_with(FileSliceCache::new);
328
329 let net_change = file_cache.insert(range, data, counter);
330 let mut current = self.current_bytes.write();
331 if net_change >= 0 {
332 *current += net_change as usize;
333 } else {
334 *current = current.saturating_sub((-net_change) as usize);
335 }
336 }
337
338 fn evict_to_fit(&self, needed: usize) {
340 let mut caches = self.caches.write();
341 let mut current = self.current_bytes.write();
342
343 let target = if *current + needed > self.max_bytes {
344 (*current + needed) - self.max_bytes
345 } else {
346 return;
347 };
348
349 let mut freed = 0;
350
351 while freed < target {
353 let oldest_file = caches
355 .iter()
356 .filter(|(_, fc)| !fc.slices.is_empty())
357 .min_by_key(|(_, fc)| {
358 fc.slices
359 .values()
360 .map(|s| s.access_count)
361 .min()
362 .unwrap_or(u64::MAX)
363 })
364 .map(|(p, _)| p.clone());
365
366 if let Some(path) = oldest_file {
367 if let Some(file_cache) = caches.get_mut(&path) {
368 freed += file_cache.evict_lru(target - freed);
369 }
370 } else {
371 break;
372 }
373 }
374
375 *current = current.saturating_sub(freed);
376 }
377
378 pub fn stats(&self) -> SliceCacheStats {
380 let caches = self.caches.read();
381 let mut total_slices = 0;
382 let mut files_cached = 0;
383
384 for fc in caches.values() {
385 if !fc.slices.is_empty() {
386 files_cached += 1;
387 total_slices += fc.slices.len();
388 }
389 }
390
391 SliceCacheStats {
392 total_bytes: *self.current_bytes.read(),
393 max_bytes: self.max_bytes,
394 total_slices,
395 files_cached,
396 }
397 }
398
399 pub fn serialize(&self) -> Vec<u8> {
415 let caches = self.caches.read();
416 let file_sizes = self.file_sizes.read();
417 let mut buf = Vec::new();
418
419 buf.extend_from_slice(SLICE_CACHE_MAGIC);
421 buf.extend_from_slice(&SLICE_CACHE_VERSION.to_le_bytes());
422
423 let non_empty: Vec<_> = caches
425 .iter()
426 .filter(|(_, fc)| !fc.slices.is_empty())
427 .collect();
428 buf.extend_from_slice(&(non_empty.len() as u32).to_le_bytes());
429
430 for (path, file_cache) in non_empty {
431 let path_str = path.to_string_lossy();
433 let path_bytes = path_str.as_bytes();
434 buf.extend_from_slice(&(path_bytes.len() as u32).to_le_bytes());
435 buf.extend_from_slice(path_bytes);
436
437 let cache_data = file_cache.serialize();
439 buf.extend_from_slice(&cache_data);
440 }
441
442 buf.extend_from_slice(&(file_sizes.len() as u32).to_le_bytes());
444 for (path, &size) in file_sizes.iter() {
445 let path_str = path.to_string_lossy();
446 let path_bytes = path_str.as_bytes();
447 buf.extend_from_slice(&(path_bytes.len() as u32).to_le_bytes());
448 buf.extend_from_slice(path_bytes);
449 buf.extend_from_slice(&size.to_le_bytes());
450 }
451
452 buf
453 }
454
455 pub fn deserialize(&self, data: &[u8]) -> io::Result<()> {
460 let mut pos = 0;
461
462 if data.len() < 16 {
464 return Err(io::Error::new(
465 io::ErrorKind::InvalidData,
466 "slice cache too short",
467 ));
468 }
469 if &data[pos..pos + 8] != SLICE_CACHE_MAGIC {
470 return Err(io::Error::new(
471 io::ErrorKind::InvalidData,
472 "invalid slice cache magic",
473 ));
474 }
475 pos += 8;
476
477 let version = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap());
479 pos += 4;
480 if version != 2 {
481 return Err(io::Error::new(
482 io::ErrorKind::InvalidData,
483 format!("unsupported slice cache version: {} (expected 2)", version),
484 ));
485 }
486
487 let num_files = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
489 pos += 4;
490
491 let mut caches = self.caches.write();
492 let mut current_bytes = self.current_bytes.write();
493 let counter = *self.access_counter.read();
494
495 for _ in 0..num_files {
496 if pos + 4 > data.len() {
498 return Err(io::Error::new(
499 io::ErrorKind::InvalidData,
500 "truncated path length",
501 ));
502 }
503 let path_len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
504 pos += 4;
505
506 if pos + path_len > data.len() {
508 return Err(io::Error::new(io::ErrorKind::InvalidData, "truncated path"));
509 }
510 let path_str = std::str::from_utf8(&data[pos..pos + path_len])
511 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
512 let path = PathBuf::from(path_str);
513 pos += path_len;
514
515 let (file_cache, consumed) = FileSliceCache::deserialize(&data[pos..], counter)?;
517 pos += consumed;
518
519 *current_bytes += file_cache.total_bytes;
521 caches.insert(path, file_cache);
522 }
523
524 if pos + 4 <= data.len() {
526 let num_sizes = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
527 pos += 4;
528
529 let mut file_sizes = self.file_sizes.write();
530 for _ in 0..num_sizes {
531 if pos + 4 > data.len() {
532 break;
533 }
534 let path_len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
535 pos += 4;
536
537 if pos + path_len > data.len() {
538 break;
539 }
540 let path_str = match std::str::from_utf8(&data[pos..pos + path_len]) {
541 Ok(s) => s,
542 Err(_) => break,
543 };
544 let path = PathBuf::from(path_str);
545 pos += path_len;
546
547 if pos + 8 > data.len() {
548 break;
549 }
550 let size = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
551 pos += 8;
552
553 file_sizes.insert(path, size);
554 }
555 }
556
557 Ok(())
558 }
559
560 pub fn serialize_to_writer<W: Write>(&self, mut writer: W) -> io::Result<()> {
562 let data = self.serialize();
563 writer.write_all(&data)
564 }
565
566 pub fn deserialize_from_reader<R: Read>(&self, mut reader: R) -> io::Result<()> {
568 let mut data = Vec::new();
569 reader.read_to_end(&mut data)?;
570 self.deserialize(&data)
571 }
572
573 pub fn is_empty(&self) -> bool {
575 *self.current_bytes.read() == 0
576 }
577
578 pub fn clear(&self) {
580 let mut caches = self.caches.write();
581 let mut current_bytes = self.current_bytes.write();
582 caches.clear();
583 *current_bytes = 0;
584 }
585}
586
587#[derive(Debug, Clone)]
589pub struct SliceCacheStats {
590 pub total_bytes: usize,
591 pub max_bytes: usize,
592 pub total_slices: usize,
593 pub files_cached: usize,
594}
595
596#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
597#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
598impl<D: Directory> Directory for SliceCachingDirectory<D> {
599 async fn exists(&self, path: &Path) -> io::Result<bool> {
600 self.inner.exists(path).await
601 }
602
603 async fn file_size(&self, path: &Path) -> io::Result<u64> {
604 {
606 let file_sizes = self.file_sizes.read();
607 if let Some(&size) = file_sizes.get(path) {
608 return Ok(size);
609 }
610 }
611
612 let size = self.inner.file_size(path).await?;
614 {
615 let mut file_sizes = self.file_sizes.write();
616 file_sizes.insert(path.to_path_buf(), size);
617 }
618 Ok(size)
619 }
620
621 async fn open_read(&self, path: &Path) -> io::Result<FileHandle> {
622 let file_size = self.file_size(path).await?;
624 let full_range = 0..file_size;
625
626 if let Some(data) = self.try_cache_read(path, full_range.clone()) {
628 return Ok(FileHandle::from_bytes(OwnedBytes::new(data)));
629 }
630
631 let handle = self.inner.open_read(path).await?;
633 let bytes = handle.read_bytes().await?;
634
635 self.cache_insert(path, full_range, bytes.as_slice().to_vec());
637
638 Ok(FileHandle::from_bytes(bytes))
639 }
640
641 async fn read_range(&self, path: &Path, range: Range<u64>) -> io::Result<OwnedBytes> {
642 if let Some(data) = self.try_cache_read(path, range.clone()) {
644 return Ok(OwnedBytes::new(data));
645 }
646
647 let data = self.inner.read_range(path, range.clone()).await?;
649
650 self.cache_insert(path, range, data.as_slice().to_vec());
652
653 Ok(data)
654 }
655
656 async fn list_files(&self, prefix: &Path) -> io::Result<Vec<PathBuf>> {
657 self.inner.list_files(prefix).await
658 }
659
660 async fn open_lazy(&self, path: &Path) -> io::Result<FileHandle> {
661 let file_size = self.file_size(path).await?;
663
664 let path_buf = path.to_path_buf();
666 let caches = Arc::clone(&self.caches);
667 let current_bytes = Arc::clone(&self.current_bytes);
668 let access_counter = Arc::clone(&self.access_counter);
669 let max_bytes = self.max_bytes;
670 let inner = Arc::clone(&self.inner);
671
672 let read_fn: RangeReadFn = Arc::new(move |range: Range<u64>| {
673 let path = path_buf.clone();
674 let caches = Arc::clone(&caches);
675 let current_bytes = Arc::clone(¤t_bytes);
676 let access_counter = Arc::clone(&access_counter);
677 let inner = Arc::clone(&inner);
678
679 Box::pin(async move {
680 {
682 let mut caches_guard = caches.write();
683 let mut counter = access_counter.write();
684 if let Some(file_cache) = caches_guard.get_mut(&path)
685 && let Some(data) = file_cache.try_read(range.clone(), &mut counter)
686 {
687 return Ok(OwnedBytes::new(data));
688 }
689 }
690
691 log::trace!("Cache MISS: {:?} [{}-{}]", path, range.start, range.end);
692
693 let data = inner.read_range(&path, range.clone()).await?;
695
696 let data_len = data.as_slice().len();
698 {
699 let current = *current_bytes.read();
701 if current + data_len > max_bytes {
702 } else {
705 let mut caches_guard = caches.write();
706 let counter = *access_counter.read();
707
708 let file_cache = caches_guard
709 .entry(path.clone())
710 .or_insert_with(FileSliceCache::new);
711
712 let net_change =
713 file_cache.insert(range, data.as_slice().to_vec(), counter);
714 let mut current = current_bytes.write();
715 if net_change >= 0 {
716 *current += net_change as usize;
717 } else {
718 *current = current.saturating_sub((-net_change) as usize);
719 }
720 }
721 }
722
723 Ok(data)
724 })
725 });
726
727 Ok(FileHandle::lazy_labeled(
728 file_size,
729 read_fn,
730 self.label.get(),
731 ))
732 }
733
734 fn set_index_label(&self, label: &str) {
735 self.label.set(label);
736 self.inner.set_index_label(label);
737 }
738}
739
740#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
743#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
744impl<D: super::DirectoryWriter> super::DirectoryWriter for SliceCachingDirectory<D> {
745 async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
746 {
748 let mut caches = self.caches.write();
749 if let Some(file_cache) = caches.remove(path) {
750 let mut current = self.current_bytes.write();
751 *current = current.saturating_sub(file_cache.total_bytes);
752 }
753 }
754 {
756 let mut file_sizes = self.file_sizes.write();
757 file_sizes.remove(path);
758 }
759 self.inner.write(path, data).await
761 }
762
763 async fn delete(&self, path: &Path) -> io::Result<()> {
764 {
766 let mut caches = self.caches.write();
767 if let Some(file_cache) = caches.remove(path) {
768 let mut current = self.current_bytes.write();
769 *current = current.saturating_sub(file_cache.total_bytes);
770 }
771 }
772 {
774 let mut file_sizes = self.file_sizes.write();
775 file_sizes.remove(path);
776 }
777 self.inner.delete(path).await
779 }
780
781 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
782 {
784 let mut caches = self.caches.write();
785 if let Some(file_cache) = caches.remove(from) {
786 caches.insert(to.to_path_buf(), file_cache);
787 }
788 }
789 {
791 let mut file_sizes = self.file_sizes.write();
792 if let Some(size) = file_sizes.remove(from) {
793 file_sizes.insert(to.to_path_buf(), size);
794 }
795 }
796 self.inner.rename(from, to).await
798 }
799
800 async fn sync(&self) -> io::Result<()> {
801 self.inner.sync().await
802 }
803
804 async fn streaming_writer(&self, path: &Path) -> io::Result<Box<dyn super::StreamingWriter>> {
805 {
807 let mut caches = self.caches.write();
808 if let Some(file_cache) = caches.remove(path) {
809 let mut current = self.current_bytes.write();
810 *current = current.saturating_sub(file_cache.total_bytes);
811 }
812 }
813 {
814 let mut file_sizes = self.file_sizes.write();
815 file_sizes.remove(path);
816 }
817 self.inner.streaming_writer(path).await
818 }
819}
820
821#[cfg(test)]
822mod tests {
823 use super::*;
824 use crate::directories::{DirectoryWriter, RamDirectory};
825
826 #[tokio::test]
827 async fn test_slice_cache_basic() {
828 let ram = RamDirectory::new();
829 ram.write(Path::new("test.bin"), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
830 .await
831 .unwrap();
832
833 let cached = SliceCachingDirectory::new(ram, 1024);
834
835 let data = cached
837 .read_range(Path::new("test.bin"), 2..5)
838 .await
839 .unwrap();
840 assert_eq!(data.as_slice(), &[2, 3, 4]);
841
842 let data = cached
844 .read_range(Path::new("test.bin"), 2..5)
845 .await
846 .unwrap();
847 assert_eq!(data.as_slice(), &[2, 3, 4]);
848
849 let stats = cached.stats();
850 assert_eq!(stats.total_slices, 1);
851 assert_eq!(stats.total_bytes, 3);
852 }
853
854 #[tokio::test]
855 async fn test_slice_cache_overlap_merge() {
856 let ram = RamDirectory::new();
857 ram.write(Path::new("test.bin"), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
858 .await
859 .unwrap();
860
861 let cached = SliceCachingDirectory::new(ram, 1024);
862
863 cached
865 .read_range(Path::new("test.bin"), 2..5)
866 .await
867 .unwrap();
868
869 cached
871 .read_range(Path::new("test.bin"), 4..7)
872 .await
873 .unwrap();
874
875 let stats = cached.stats();
876 assert_eq!(stats.total_slices, 1);
878 assert_eq!(stats.total_bytes, 5); let data = cached
882 .read_range(Path::new("test.bin"), 3..6)
883 .await
884 .unwrap();
885 assert_eq!(data.as_slice(), &[3, 4, 5]);
886 }
887
888 #[tokio::test]
889 async fn test_slice_cache_eviction() {
890 let ram = RamDirectory::new();
891 ram.write(Path::new("test.bin"), &[0; 100]).await.unwrap();
892
893 let cached = SliceCachingDirectory::new(ram, 50);
895
896 cached
898 .read_range(Path::new("test.bin"), 0..30)
899 .await
900 .unwrap();
901
902 cached
904 .read_range(Path::new("test.bin"), 50..80)
905 .await
906 .unwrap();
907
908 let stats = cached.stats();
909 assert!(stats.total_bytes <= 50);
910 }
911
912 #[tokio::test]
913 async fn test_slice_cache_serialize_deserialize() {
914 let ram = RamDirectory::new();
915 ram.write(Path::new("file1.bin"), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
916 .await
917 .unwrap();
918 ram.write(Path::new("file2.bin"), &[10, 11, 12, 13, 14, 15])
919 .await
920 .unwrap();
921
922 let cached = SliceCachingDirectory::new(ram.clone(), 1024);
923
924 cached
926 .read_range(Path::new("file1.bin"), 2..6)
927 .await
928 .unwrap();
929 cached
930 .read_range(Path::new("file2.bin"), 1..4)
931 .await
932 .unwrap();
933
934 let stats = cached.stats();
935 assert_eq!(stats.files_cached, 2);
936 assert_eq!(stats.total_bytes, 7); let serialized = cached.serialize();
940 assert!(!serialized.is_empty());
941
942 let cached2 = SliceCachingDirectory::new(ram.clone(), 1024);
944 assert!(cached2.is_empty());
945
946 cached2.deserialize(&serialized).unwrap();
947
948 let stats2 = cached2.stats();
949 assert_eq!(stats2.files_cached, 2);
950 assert_eq!(stats2.total_bytes, 7);
951
952 let data = cached2
954 .read_range(Path::new("file1.bin"), 2..6)
955 .await
956 .unwrap();
957 assert_eq!(data.as_slice(), &[2, 3, 4, 5]);
958
959 let data = cached2
960 .read_range(Path::new("file2.bin"), 1..4)
961 .await
962 .unwrap();
963 assert_eq!(data.as_slice(), &[11, 12, 13]);
964 }
965
966 #[tokio::test]
967 async fn test_slice_cache_serialize_empty() {
968 let ram = RamDirectory::new();
969 let cached = SliceCachingDirectory::new(ram, 1024);
970
971 let serialized = cached.serialize();
973 assert!(!serialized.is_empty()); let cached2 = SliceCachingDirectory::new(RamDirectory::new(), 1024);
977 cached2.deserialize(&serialized).unwrap();
978 assert!(cached2.is_empty());
979 }
980}