iroh_blobs/store/fs/
tables.rs

1//! Table definitions and accessors for the redb database.
2use std::collections::BTreeSet;
3
4use redb::{ReadableTable, TableDefinition, TableError};
5
6use super::{EntryState, PathOptions};
7use crate::{util::Tag, Hash, HashAndFormat};
8
9pub(super) const BLOBS_TABLE: TableDefinition<Hash, EntryState> = TableDefinition::new("blobs-0");
10
11pub(super) const TAGS_TABLE: TableDefinition<Tag, HashAndFormat> = TableDefinition::new("tags-0");
12
13pub(super) const INLINE_DATA_TABLE: TableDefinition<Hash, &[u8]> =
14    TableDefinition::new("inline-data-0");
15
16pub(super) const INLINE_OUTBOARD_TABLE: TableDefinition<Hash, &[u8]> =
17    TableDefinition::new("inline-outboard-0");
18
19/// A trait similar to [`redb::ReadableTable`] but for all tables that make up
20/// the blob store. This can be used in places where either a readonly or
21/// mutable table is needed.
22pub(super) trait ReadableTables {
23    fn blobs(&self) -> &impl ReadableTable<Hash, EntryState>;
24    fn tags(&self) -> &impl ReadableTable<Tag, HashAndFormat>;
25    fn inline_data(&self) -> &impl ReadableTable<Hash, &'static [u8]>;
26    fn inline_outboard(&self) -> &impl ReadableTable<Hash, &'static [u8]>;
27}
28
29/// A struct similar to [`redb::Table`] but for all tables that make up the
30/// blob store.
31pub(super) struct Tables<'a> {
32    pub blobs: redb::Table<'a, Hash, EntryState>,
33    pub tags: redb::Table<'a, Tag, HashAndFormat>,
34    pub inline_data: redb::Table<'a, Hash, &'static [u8]>,
35    pub inline_outboard: redb::Table<'a, Hash, &'static [u8]>,
36    pub delete_after_commit: &'a mut DeleteSet,
37}
38
39#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
40pub(super) enum BaoFilePart {
41    Outboard,
42    Data,
43    Sizes,
44}
45
46impl<'txn> Tables<'txn> {
47    pub fn new(
48        tx: &'txn redb::WriteTransaction,
49        delete_after_commit: &'txn mut DeleteSet,
50    ) -> std::result::Result<Self, TableError> {
51        Ok(Self {
52            blobs: tx.open_table(BLOBS_TABLE)?,
53            tags: tx.open_table(TAGS_TABLE)?,
54            inline_data: tx.open_table(INLINE_DATA_TABLE)?,
55            inline_outboard: tx.open_table(INLINE_OUTBOARD_TABLE)?,
56            delete_after_commit,
57        })
58    }
59}
60
61impl ReadableTables for Tables<'_> {
62    fn blobs(&self) -> &impl ReadableTable<Hash, EntryState> {
63        &self.blobs
64    }
65    fn tags(&self) -> &impl ReadableTable<Tag, HashAndFormat> {
66        &self.tags
67    }
68    fn inline_data(&self) -> &impl ReadableTable<Hash, &'static [u8]> {
69        &self.inline_data
70    }
71    fn inline_outboard(&self) -> &impl ReadableTable<Hash, &'static [u8]> {
72        &self.inline_outboard
73    }
74}
75
76/// A struct similar to [`redb::ReadOnlyTable`] but for all tables that make up
77/// the blob store.
78pub(super) struct ReadOnlyTables {
79    pub blobs: redb::ReadOnlyTable<Hash, EntryState>,
80    pub tags: redb::ReadOnlyTable<Tag, HashAndFormat>,
81    pub inline_data: redb::ReadOnlyTable<Hash, &'static [u8]>,
82    pub inline_outboard: redb::ReadOnlyTable<Hash, &'static [u8]>,
83}
84
85impl ReadOnlyTables {
86    pub fn new(tx: &redb::ReadTransaction) -> std::result::Result<Self, TableError> {
87        Ok(Self {
88            blobs: tx.open_table(BLOBS_TABLE)?,
89            tags: tx.open_table(TAGS_TABLE)?,
90            inline_data: tx.open_table(INLINE_DATA_TABLE)?,
91            inline_outboard: tx.open_table(INLINE_OUTBOARD_TABLE)?,
92        })
93    }
94}
95
96impl ReadableTables for ReadOnlyTables {
97    fn blobs(&self) -> &impl ReadableTable<Hash, EntryState> {
98        &self.blobs
99    }
100    fn tags(&self) -> &impl ReadableTable<Tag, HashAndFormat> {
101        &self.tags
102    }
103    fn inline_data(&self) -> &impl ReadableTable<Hash, &'static [u8]> {
104        &self.inline_data
105    }
106    fn inline_outboard(&self) -> &impl ReadableTable<Hash, &'static [u8]> {
107        &self.inline_outboard
108    }
109}
110
111/// Helper to keep track of files to delete after a transaction is committed.
112#[derive(Debug, Default)]
113pub(super) struct DeleteSet(BTreeSet<(Hash, BaoFilePart)>);
114
115impl DeleteSet {
116    /// Mark a file as to be deleted after the transaction is committed.
117    pub fn insert(&mut self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
118        for part in parts {
119            self.0.insert((hash, part));
120        }
121    }
122
123    /// Mark a file as to be kept after the transaction is committed.
124    ///
125    /// This will cancel any previous delete for the same file in the same transaction.
126    pub fn remove(&mut self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
127        for part in parts {
128            self.0.remove(&(hash, part));
129        }
130    }
131
132    /// Get the inner set of files to delete.
133    pub fn into_inner(self) -> BTreeSet<(Hash, BaoFilePart)> {
134        self.0
135    }
136
137    /// Apply the delete set and clear it.
138    ///
139    /// This will delete all files marked for deletion and then clear the set.
140    /// Errors will just be logged.
141    pub fn apply_and_clear(&mut self, options: &PathOptions) {
142        for (hash, to_delete) in &self.0 {
143            tracing::debug!("deleting {:?} for {hash}", to_delete);
144            let path = match to_delete {
145                BaoFilePart::Data => options.owned_data_path(hash),
146                BaoFilePart::Outboard => options.owned_outboard_path(hash),
147                BaoFilePart::Sizes => options.owned_sizes_path(hash),
148            };
149            if let Err(cause) = std::fs::remove_file(&path) {
150                // Ignore NotFound errors, if the file is already gone that's fine.
151                if cause.kind() != std::io::ErrorKind::NotFound {
152                    tracing::warn!(
153                        "failed to delete {:?} {}: {}",
154                        to_delete,
155                        path.display(),
156                        cause
157                    );
158                }
159            }
160        }
161        self.0.clear();
162    }
163}