ouisync/directory/
content.rs

1//! Directory content
2
3use super::entry_data::EntryData;
4use crate::{
5    blob::BlobId,
6    error::{Error, Result},
7    protocol::Bump,
8    version_vector::VersionVector,
9};
10use serde::Deserialize;
11use std::{
12    cmp::Ordering,
13    collections::{
14        btree_map::{self, Entry},
15        BTreeMap,
16    },
17};
18
19/// Version of the Directory serialization format.
20pub const VERSION: u64 = 2;
21
22#[derive(Clone, Debug)]
23pub(super) struct Content {
24    entries: v2::Entries,
25}
26
27impl Content {
28    pub fn empty() -> Self {
29        Self {
30            entries: BTreeMap::new(),
31        }
32    }
33
34    pub fn deserialize(mut input: &[u8]) -> Result<Self> {
35        let version = vint64::decode(&mut input).map_err(|_| Error::MalformedDirectory)?;
36        let entries = match version {
37            VERSION => deserialize_entries(input),
38            1 => Ok(v2::from_v1(deserialize_entries(input)?)),
39            0 => Ok(v2::from_v1(v1::from_v0(deserialize_entries(input)?))),
40            _ => Err(Error::StorageVersionMismatch),
41        };
42
43        Ok(Self { entries: entries? })
44    }
45
46    pub fn serialize(&self) -> Vec<u8> {
47        let mut output = Vec::new();
48        output.extend_from_slice(vint64::encode(VERSION).as_ref());
49        bincode::serialize_into(&mut output, &self.entries)
50            .expect("failed to serialize directory content");
51        output
52    }
53
54    pub fn iter(&self) -> btree_map::Iter<String, EntryData> {
55        self.entries.iter()
56    }
57
58    pub fn get_key_value(&self, name: &str) -> Option<(&String, &EntryData)> {
59        self.entries.get_key_value(name)
60    }
61
62    pub fn get_mut(&mut self, name: &str) -> Option<&mut EntryData> {
63        self.entries.get_mut(name)
64    }
65
66    /// Inserts an entry into this directory. Returns the difference between the new and the old
67    /// version vectors.
68    pub fn insert(
69        &mut self,
70        name: String,
71        new_data: EntryData,
72    ) -> Result<VersionVector, EntryExists> {
73        match self.entries.entry(name) {
74            Entry::Vacant(entry) => {
75                let diff = new_data.version_vector().clone();
76                entry.insert(new_data);
77                Ok(diff)
78            }
79            Entry::Occupied(mut entry) => {
80                check_replace(entry.get(), &new_data)?;
81                let diff = new_data
82                    .version_vector()
83                    .saturating_sub(entry.get().version_vector());
84                entry.insert(new_data);
85                Ok(diff)
86            }
87        }
88    }
89
90    /// Checks whether an entry can be inserted into this directory without actually inserting it.
91    /// If so, returns the blob_id of the existing entry (if any).
92    pub fn check_insert(
93        &self,
94        name: &str,
95        new_data: &EntryData,
96    ) -> Result<Option<BlobId>, EntryExists> {
97        if let Some(old_data) = self.entries.get(name) {
98            check_replace(old_data, new_data)
99        } else {
100            Ok(None)
101        }
102    }
103
104    /// Updates the version vector of entry at `name`. Returns the difference between the old and
105    /// the new version vectors.
106    pub fn bump(&mut self, name: &str, bump: Bump) -> Result<VersionVector> {
107        Ok(bump.apply(
108            self.entries
109                .get_mut(name)
110                .ok_or(Error::EntryNotFound)?
111                .version_vector_mut(),
112        ))
113    }
114
115    /// Initial version vector for a new entry to be inserted.
116    pub fn initial_version_vector(&self, name: &str) -> VersionVector {
117        if let Some(EntryData::Tombstone(entry)) = self.entries.get(name) {
118            entry.version_vector.clone()
119        } else {
120            VersionVector::new()
121        }
122    }
123}
124
125#[derive(Debug)]
126pub(crate) enum EntryExists {
127    /// The existing entry is more up-to-date and points to the same blob than the one being
128    /// inserted
129    Same,
130    /// The existing entry either points to a different blob or is concurrent
131    Different,
132}
133
134impl From<EntryExists> for Error {
135    fn from(_: EntryExists) -> Self {
136        Self::EntryExists
137    }
138}
139
140impl<'a> IntoIterator for &'a Content {
141    type Item = <Self::IntoIter as Iterator>::Item;
142    type IntoIter = btree_map::Iter<'a, String, EntryData>;
143
144    fn into_iter(self) -> Self::IntoIter {
145        self.iter()
146    }
147}
148
149fn deserialize_entries<'a, T: Deserialize<'a>>(input: &'a [u8]) -> Result<T, Error> {
150    bincode::deserialize(input).map_err(|_| Error::MalformedDirectory)
151}
152
153fn check_replace(old: &EntryData, new: &EntryData) -> Result<Option<BlobId>, EntryExists> {
154    // Replace entries only if the new version is more up to date than the old version.
155
156    match (
157        new.version_vector().partial_cmp(old.version_vector()),
158        new,
159        old,
160    ) {
161        (Some(Ordering::Greater), _, _) => Ok(old.blob_id().copied()),
162        (Some(Ordering::Equal | Ordering::Less), EntryData::File(new), EntryData::File(old))
163            if new.blob_id == old.blob_id =>
164        {
165            Err(EntryExists::Same)
166        }
167        (
168            Some(Ordering::Equal | Ordering::Less),
169            EntryData::Directory(new),
170            EntryData::Directory(old),
171        ) if new.blob_id == old.blob_id => Err(EntryExists::Same),
172        (
173            Some(Ordering::Equal | Ordering::Less),
174            EntryData::Tombstone(new),
175            EntryData::Tombstone(old),
176        ) if new.cause == old.cause => Err(EntryExists::Same),
177        (
178            Some(Ordering::Equal | Ordering::Less),
179            EntryData::File(_) | EntryData::Directory(_) | EntryData::Tombstone(_),
180            EntryData::File(_) | EntryData::Directory(_) | EntryData::Tombstone(_),
181        ) => Err(EntryExists::Different),
182        (None, _, _) => Err(EntryExists::Different),
183    }
184}
185
186mod v2 {
187    use super::{
188        super::entry_data::{EntryData, EntryTombstoneData, TombstoneCause},
189        v1,
190    };
191    use std::collections::BTreeMap;
192
193    pub(super) type Entries = BTreeMap<String, EntryData>;
194
195    pub(super) fn from_v1(v1: v1::Entries) -> Entries {
196        v1.into_iter()
197            .map(|(name, data)| {
198                let data = match data {
199                    v1::EntryData::File(data) => EntryData::File(data),
200                    v1::EntryData::Directory(data) => EntryData::Directory(data),
201                    v1::EntryData::Tombstone(v1::EntryTombstoneData { version_vector }) => {
202                        EntryData::Tombstone(EntryTombstoneData {
203                            cause: TombstoneCause::Removed,
204                            version_vector,
205                        })
206                    }
207                };
208
209                (name, data)
210            })
211            .collect()
212    }
213}
214
215mod v1 {
216    use super::v0;
217    use std::collections::BTreeMap;
218    pub(super) use v0::{EntryData, EntryTombstoneData};
219
220    pub(super) type Entries = BTreeMap<String, v0::EntryData>;
221
222    pub(super) fn from_v0(v0: v0::Entries) -> Entries {
223        use crate::conflict;
224
225        let mut v1 = BTreeMap::new();
226
227        for (name, versions) in v0 {
228            if versions.len() <= 1 {
229                // If there is only one version, insert it directly
230                if let Some(data) = versions.into_values().next() {
231                    v1.insert(name, data);
232                }
233            } else {
234                // If there is more than one version, create unique name for each of them and insert
235                // them as separate entries
236                for (author_id, data) in versions {
237                    v1.insert(conflict::create_unique_name(&name, &author_id), data);
238                }
239            }
240        }
241
242        v1
243    }
244}
245
246mod v0 {
247    use super::super::entry_data::{EntryDirectoryData, EntryFileData};
248    use crate::{crypto::sign::PublicKey, version_vector::VersionVector};
249    use serde::Deserialize;
250    use std::collections::BTreeMap;
251
252    pub(super) type Entries = BTreeMap<String, BTreeMap<PublicKey, EntryData>>;
253
254    #[derive(Deserialize)]
255    pub(super) enum EntryData {
256        File(EntryFileData),
257        Directory(EntryDirectoryData),
258        Tombstone(EntryTombstoneData),
259    }
260
261    #[derive(Deserialize)]
262    pub(super) struct EntryTombstoneData {
263        pub version_vector: VersionVector,
264    }
265}