1use bao_tree::{io::outboard::PreOrderMemOutboard, BaoTree, ChunkRanges};
3use bytes::Bytes;
4use derive_more::{Debug, Display, From, Into};
5use range_collections::range_set::RangeSetRange;
6use serde::{Deserialize, Serialize};
7use std::{borrow::Borrow, fmt, sync::Arc, time::SystemTime};
8
9use crate::{store::Store, BlobFormat, Hash, HashAndFormat, IROH_BLOCK_SIZE};
10
11pub mod io;
12mod mem_or_file;
13pub mod progress;
14pub use mem_or_file::MemOrFile;
15mod sparse_mem_file;
16pub use sparse_mem_file::SparseMemFile;
17
18#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, From, Into)]
20pub struct Tag(pub Bytes);
21
22#[cfg(feature = "redb")]
23mod redb_support {
24    use super::Tag;
25    use bytes::Bytes;
26    use redb::{Key as RedbKey, Value as RedbValue};
27
28    impl RedbValue for Tag {
29        type SelfType<'a> = Self;
30
31        type AsBytes<'a> = bytes::Bytes;
32
33        fn fixed_width() -> Option<usize> {
34            None
35        }
36
37        fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
38        where
39            Self: 'a,
40        {
41            Self(Bytes::copy_from_slice(data))
42        }
43
44        fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
45        where
46            Self: 'a,
47            Self: 'b,
48        {
49            value.0.clone()
50        }
51
52        fn type_name() -> redb::TypeName {
53            redb::TypeName::new("Tag")
54        }
55    }
56
57    impl RedbKey for Tag {
58        fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
59            data1.cmp(data2)
60        }
61    }
62}
63
64impl Borrow<[u8]> for Tag {
65    fn borrow(&self) -> &[u8] {
66        self.0.as_ref()
67    }
68}
69
70impl From<String> for Tag {
71    fn from(value: String) -> Self {
72        Self(Bytes::from(value))
73    }
74}
75
76impl From<&str> for Tag {
77    fn from(value: &str) -> Self {
78        Self(Bytes::from(value.to_owned()))
79    }
80}
81
82impl Display for Tag {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        let bytes = self.0.as_ref();
85        match std::str::from_utf8(bytes) {
86            Ok(s) => write!(f, "\"{}\"", s),
87            Err(_) => write!(f, "{}", hex::encode(bytes)),
88        }
89    }
90}
91
92struct DD<T: fmt::Display>(T);
93
94impl<T: fmt::Display> fmt::Debug for DD<T> {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        fmt::Display::fmt(&self.0, f)
97    }
98}
99
100impl Debug for Tag {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        f.debug_tuple("Tag").field(&DD(self)).finish()
103    }
104}
105
106impl Tag {
107    pub fn auto(time: SystemTime, exists: impl Fn(&[u8]) -> bool) -> Self {
109        let now = chrono::DateTime::<chrono::Utc>::from(time);
110        let mut i = 0;
111        loop {
112            let mut text = format!("auto-{}", now.format("%Y-%m-%dT%H:%M:%S%.3fZ"));
113            if i != 0 {
114                text.push_str(&format!("-{}", i));
115            }
116            if !exists(text.as_bytes()) {
117                return Self::from(text);
118            }
119            i += 1;
120        }
121    }
122}
123
124#[derive(Debug, Default)]
126pub struct TagSet {
127    auto: bool,
128    named: Vec<Tag>,
129}
130
131impl TagSet {
132    pub fn insert(&mut self, tag: SetTagOption) {
134        match tag {
135            SetTagOption::Auto => self.auto = true,
136            SetTagOption::Named(tag) => {
137                if !self.named.iter().any(|t| t == &tag) {
138                    self.named.push(tag)
139                }
140            }
141        }
142    }
143
144    pub fn into_tags(self) -> impl Iterator<Item = SetTagOption> {
146        self.auto
147            .then_some(SetTagOption::Auto)
148            .into_iter()
149            .chain(self.named.into_iter().map(SetTagOption::Named))
150    }
151
152    pub async fn apply<D: Store>(
154        self,
155        db: &D,
156        hash_and_format: HashAndFormat,
157    ) -> std::io::Result<()> {
158        let tags = self.into_tags();
159        for tag in tags {
160            match tag {
161                SetTagOption::Named(tag) => {
162                    db.set_tag(tag, Some(hash_and_format)).await?;
163                }
164                SetTagOption::Auto => {
165                    db.create_tag(hash_and_format).await?;
166                }
167            }
168        }
169        Ok(())
170    }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
175pub enum SetTagOption {
176    Auto,
178    Named(Tag),
180}
181
182pub trait LivenessTracker: std::fmt::Debug + Send + Sync + 'static {
191    fn on_clone(&self, inner: &HashAndFormat);
193    fn on_drop(&self, inner: &HashAndFormat);
195}
196
197#[derive(Debug)]
202pub struct TempTag {
203    inner: HashAndFormat,
205    liveness: Option<Arc<dyn LivenessTracker>>,
207}
208
209impl TempTag {
210    pub fn new(inner: HashAndFormat, liveness: Option<Arc<dyn LivenessTracker>>) -> Self {
218        if let Some(liveness) = liveness.as_ref() {
219            liveness.on_clone(&inner);
220        }
221        Self { inner, liveness }
222    }
223
224    pub fn inner(&self) -> &HashAndFormat {
226        &self.inner
227    }
228
229    pub fn hash(&self) -> &Hash {
231        &self.inner.hash
232    }
233
234    pub fn format(&self) -> BlobFormat {
236        self.inner.format
237    }
238
239    pub fn leak(mut self) {
241        self.liveness = None;
245    }
246}
247
248impl Clone for TempTag {
249    fn clone(&self) -> Self {
250        Self::new(self.inner, self.liveness.clone())
251    }
252}
253
254impl Drop for TempTag {
255    fn drop(&mut self) {
256        if let Some(liveness) = self.liveness.as_ref() {
257            liveness.on_drop(&self.inner);
258        }
259    }
260}
261
262pub fn total_bytes(ranges: ChunkRanges, size: u64) -> u64 {
266    ranges
267        .iter()
268        .map(|range| {
269            let (start, end) = match range {
270                RangeSetRange::Range(r) => {
271                    (r.start.to_bytes().min(size), r.end.to_bytes().min(size))
272                }
273                RangeSetRange::RangeFrom(range) => (range.start.to_bytes().min(size), size),
274            };
275            end.saturating_sub(start)
276        })
277        .reduce(u64::saturating_add)
278        .unwrap_or_default()
279}
280
281#[derive(Debug)]
283pub(crate) struct NonSend {
284    _marker: std::marker::PhantomData<std::rc::Rc<()>>,
285}
286
287impl NonSend {
288    #[allow(dead_code)]
290    pub const fn new() -> Self {
291        Self {
292            _marker: std::marker::PhantomData,
293        }
294    }
295}
296
297pub(crate) fn copy_limited_slice(bytes: &[u8], offset: u64, len: usize) -> Bytes {
299    bytes[limited_range(offset, len, bytes.len())]
300        .to_vec()
301        .into()
302}
303
304pub(crate) fn limited_range(offset: u64, len: usize, buf_len: usize) -> std::ops::Range<usize> {
305    if offset < buf_len as u64 {
306        let start = offset as usize;
307        let end = start.saturating_add(len).min(buf_len);
308        start..end
309    } else {
310        0..0
311    }
312}
313
314#[allow(dead_code)]
316pub(crate) fn get_limited_slice(bytes: &Bytes, offset: u64, len: usize) -> Bytes {
317    bytes.slice(limited_range(offset, len, bytes.len()))
318}
319
320#[allow(dead_code)]
322pub(crate) fn raw_outboard_size(size: u64) -> u64 {
323    BaoTree::new(size, IROH_BLOCK_SIZE).outboard_size()
324}
325
326pub(crate) fn raw_outboard(data: &[u8]) -> (Vec<u8>, Hash) {
328    let res = PreOrderMemOutboard::create(data, IROH_BLOCK_SIZE);
329    (res.data, res.root.into())
330}