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 local_path(&self, path: &Path) -> Option<PathBuf> {
735 self.inner.local_path(path)
736 }
737
738 fn set_index_label(&self, label: &str) {
739 self.label.set(label);
740 self.inner.set_index_label(label);
741 }
742}
743
744#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
747#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
748impl<D: super::DirectoryWriter> super::DirectoryWriter for SliceCachingDirectory<D> {
749 async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
750 {
752 let mut caches = self.caches.write();
753 if let Some(file_cache) = caches.remove(path) {
754 let mut current = self.current_bytes.write();
755 *current = current.saturating_sub(file_cache.total_bytes);
756 }
757 }
758 {
760 let mut file_sizes = self.file_sizes.write();
761 file_sizes.remove(path);
762 }
763 self.inner.write(path, data).await
765 }
766
767 async fn delete(&self, path: &Path) -> io::Result<()> {
768 {
770 let mut caches = self.caches.write();
771 if let Some(file_cache) = caches.remove(path) {
772 let mut current = self.current_bytes.write();
773 *current = current.saturating_sub(file_cache.total_bytes);
774 }
775 }
776 {
778 let mut file_sizes = self.file_sizes.write();
779 file_sizes.remove(path);
780 }
781 self.inner.delete(path).await
783 }
784
785 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
786 {
788 let mut caches = self.caches.write();
789 if let Some(file_cache) = caches.remove(from) {
790 caches.insert(to.to_path_buf(), file_cache);
791 }
792 }
793 {
795 let mut file_sizes = self.file_sizes.write();
796 if let Some(size) = file_sizes.remove(from) {
797 file_sizes.insert(to.to_path_buf(), size);
798 }
799 }
800 self.inner.rename(from, to).await
802 }
803
804 async fn link(&self, from: &Path, to: &Path) -> io::Result<()> {
805 self.inner.link(from, to).await
808 }
809
810 async fn sync(&self) -> io::Result<()> {
811 self.inner.sync().await
812 }
813
814 async fn streaming_writer(&self, path: &Path) -> io::Result<Box<dyn super::StreamingWriter>> {
815 {
817 let mut caches = self.caches.write();
818 if let Some(file_cache) = caches.remove(path) {
819 let mut current = self.current_bytes.write();
820 *current = current.saturating_sub(file_cache.total_bytes);
821 }
822 }
823 {
824 let mut file_sizes = self.file_sizes.write();
825 file_sizes.remove(path);
826 }
827 self.inner.streaming_writer(path).await
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834 use crate::directories::{DirectoryWriter, RamDirectory};
835
836 #[tokio::test]
837 async fn test_slice_cache_basic() {
838 let ram = RamDirectory::new();
839 ram.write(Path::new("test.bin"), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
840 .await
841 .unwrap();
842
843 let cached = SliceCachingDirectory::new(ram, 1024);
844
845 let data = cached
847 .read_range(Path::new("test.bin"), 2..5)
848 .await
849 .unwrap();
850 assert_eq!(data.as_slice(), &[2, 3, 4]);
851
852 let data = cached
854 .read_range(Path::new("test.bin"), 2..5)
855 .await
856 .unwrap();
857 assert_eq!(data.as_slice(), &[2, 3, 4]);
858
859 let stats = cached.stats();
860 assert_eq!(stats.total_slices, 1);
861 assert_eq!(stats.total_bytes, 3);
862 }
863
864 #[tokio::test]
865 async fn test_slice_cache_overlap_merge() {
866 let ram = RamDirectory::new();
867 ram.write(Path::new("test.bin"), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
868 .await
869 .unwrap();
870
871 let cached = SliceCachingDirectory::new(ram, 1024);
872
873 cached
875 .read_range(Path::new("test.bin"), 2..5)
876 .await
877 .unwrap();
878
879 cached
881 .read_range(Path::new("test.bin"), 4..7)
882 .await
883 .unwrap();
884
885 let stats = cached.stats();
886 assert_eq!(stats.total_slices, 1);
888 assert_eq!(stats.total_bytes, 5); let data = cached
892 .read_range(Path::new("test.bin"), 3..6)
893 .await
894 .unwrap();
895 assert_eq!(data.as_slice(), &[3, 4, 5]);
896 }
897
898 #[tokio::test]
899 async fn test_slice_cache_eviction() {
900 let ram = RamDirectory::new();
901 ram.write(Path::new("test.bin"), &[0; 100]).await.unwrap();
902
903 let cached = SliceCachingDirectory::new(ram, 50);
905
906 cached
908 .read_range(Path::new("test.bin"), 0..30)
909 .await
910 .unwrap();
911
912 cached
914 .read_range(Path::new("test.bin"), 50..80)
915 .await
916 .unwrap();
917
918 let stats = cached.stats();
919 assert!(stats.total_bytes <= 50);
920 }
921
922 #[tokio::test]
923 async fn test_slice_cache_serialize_deserialize() {
924 let ram = RamDirectory::new();
925 ram.write(Path::new("file1.bin"), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
926 .await
927 .unwrap();
928 ram.write(Path::new("file2.bin"), &[10, 11, 12, 13, 14, 15])
929 .await
930 .unwrap();
931
932 let cached = SliceCachingDirectory::new(ram.clone(), 1024);
933
934 cached
936 .read_range(Path::new("file1.bin"), 2..6)
937 .await
938 .unwrap();
939 cached
940 .read_range(Path::new("file2.bin"), 1..4)
941 .await
942 .unwrap();
943
944 let stats = cached.stats();
945 assert_eq!(stats.files_cached, 2);
946 assert_eq!(stats.total_bytes, 7); let serialized = cached.serialize();
950 assert!(!serialized.is_empty());
951
952 let cached2 = SliceCachingDirectory::new(ram.clone(), 1024);
954 assert!(cached2.is_empty());
955
956 cached2.deserialize(&serialized).unwrap();
957
958 let stats2 = cached2.stats();
959 assert_eq!(stats2.files_cached, 2);
960 assert_eq!(stats2.total_bytes, 7);
961
962 let data = cached2
964 .read_range(Path::new("file1.bin"), 2..6)
965 .await
966 .unwrap();
967 assert_eq!(data.as_slice(), &[2, 3, 4, 5]);
968
969 let data = cached2
970 .read_range(Path::new("file2.bin"), 1..4)
971 .await
972 .unwrap();
973 assert_eq!(data.as_slice(), &[11, 12, 13]);
974 }
975
976 #[tokio::test]
977 async fn test_slice_cache_serialize_empty() {
978 let ram = RamDirectory::new();
979 let cached = SliceCachingDirectory::new(ram, 1024);
980
981 let serialized = cached.serialize();
983 assert!(!serialized.is_empty()); let cached2 = SliceCachingDirectory::new(RamDirectory::new(), 1024);
987 cached2.deserialize(&serialized).unwrap();
988 assert!(cached2.is_empty());
989 }
990}