darkfi/geode/
chunked_storage.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::{
20    hash::{Hash, Hasher},
21    path::{Path, PathBuf},
22};
23
24use crate::geode::{file_sequence::FileSequence, MAX_CHUNK_SIZE};
25
26#[derive(Clone, Debug, Eq)]
27pub struct Chunk {
28    pub hash: blake3::Hash,
29    pub available: bool,
30    pub size: usize,
31}
32
33impl Hash for Chunk {
34    fn hash<H: Hasher>(&self, state: &mut H) {
35        self.hash.hash(state);
36    }
37}
38
39impl PartialEq for Chunk {
40    fn eq(&self, other: &Self) -> bool {
41        self.hash == other.hash
42    }
43}
44
45/// `ChunkedStorage` is a representation of a file or directory we're trying to
46/// retrieve from `Geode`.
47#[derive(Clone, Debug)]
48pub struct ChunkedStorage {
49    /// Vector of chunk hashes and a bool which is `true` if the chunk is
50    /// available locally.
51    chunks: Vec<Chunk>,
52    /// FileSequence containing the list of file paths and file sizes, it has
53    /// a single item if this is not a directory but a single file.
54    fileseq: FileSequence,
55    /// Set to `true` if this ChunkedStorage is the representation of a
56    /// directory.
57    is_dir: bool,
58}
59
60impl ChunkedStorage {
61    pub fn new(hashes: &[blake3::Hash], files: &[(PathBuf, u64)], is_dir: bool) -> Self {
62        Self {
63            chunks: hashes
64                .iter()
65                .map(|x| Chunk { hash: *x, available: false, size: MAX_CHUNK_SIZE })
66                .collect(),
67            fileseq: FileSequence::new(files, is_dir),
68            is_dir,
69        }
70    }
71
72    /// Check whether we have all the chunks available locally.
73    pub fn is_complete(&self) -> bool {
74        !self.chunks.iter().any(|c| !c.available)
75    }
76
77    /// Return an iterator over the chunks and their availability.
78    pub fn iter(&self) -> core::slice::Iter<'_, Chunk> {
79        self.chunks.iter()
80    }
81
82    /// Return an mutable iterator over the chunks and their availability.
83    pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, Chunk> {
84        self.chunks.iter_mut()
85    }
86
87    /// Return the number of chunks.
88    pub fn len(&self) -> usize {
89        self.chunks.len()
90    }
91
92    /// Return `true` if the chunked file contains no chunk.
93    pub fn is_empty(&self) -> bool {
94        self.chunks.is_empty()
95    }
96
97    /// Return the number of chunks available locally.
98    pub fn local_chunks(&self) -> usize {
99        self.chunks.iter().filter(|c| c.available).count()
100    }
101
102    /// Return `chunks`.
103    pub fn get_chunks(&self) -> &Vec<Chunk> {
104        &self.chunks
105    }
106
107    /// Return a mutable chunk from `chunks`.
108    pub fn get_chunk_mut(&mut self, index: usize) -> &mut Chunk {
109        &mut self.chunks[index]
110    }
111
112    pub fn get_chunk_index(&self, hash: &blake3::Hash) -> Option<usize> {
113        for (i, chunk) in self.chunks.iter().enumerate() {
114            if chunk.hash == *hash {
115                return Some(i)
116            }
117        }
118        None
119    }
120
121    /// Return the list of files from the `reader`.
122    pub fn get_files(&self) -> &Vec<(PathBuf, u64)> {
123        self.fileseq.get_files()
124    }
125
126    /// Return `fileseq`.
127    pub fn get_fileseq(&self) -> &FileSequence {
128        &self.fileseq
129    }
130
131    /// Return a mutable `fileseq`.
132    pub fn get_fileseq_mut(&mut self) -> &mut FileSequence {
133        &mut self.fileseq
134    }
135
136    /// Return `is_dir`.
137    pub fn is_dir(&self) -> bool {
138        self.is_dir
139    }
140
141    /// Return all chunks that contain parts of `file`.
142    pub fn get_chunks_of_file(&self, file: &Path) -> Vec<blake3::Hash> {
143        let files = self.fileseq.get_files();
144        let file_index = files.iter().position(|(f, _)| f == file);
145        if file_index.is_none() {
146            return vec![];
147        }
148        let file_index = file_index.unwrap();
149
150        let start_pos = self.fileseq.get_file_position(file_index);
151
152        let end_pos = start_pos + files[file_index].1;
153
154        let start_index = (start_pos as f64 / MAX_CHUNK_SIZE as f64).floor();
155        let end_index = (end_pos as f64 / MAX_CHUNK_SIZE as f64).floor();
156
157        let chunk_indexes: Vec<usize> = (start_index as usize..=end_index as usize).collect();
158
159        chunk_indexes
160            .iter()
161            .filter_map(|&index| self.chunks.get(index))
162            .map(|c| &c.hash)
163            .cloned()
164            .collect()
165    }
166}