Skip to main content

coven_database/
file_ref.rs

1//! A file as this device has its record: its id, size, namespace, audience
2//! and place (§17.2, E8). A file never changes its bytes, so a reference
3//! taken earlier still reads what it named.
4
5use crate::{
6    file_location::{FileLocation, Place},
7    file_table::Record,
8    sqlite::DatabaseConnection,
9    DbError,
10};
11use coven_crypto::ContentHash;
12use coven_foundation::id_source::{DeviceId, FileId};
13use coven_merge::Audience;
14
15#[path = "local_file.rs"]
16mod local_file;
17pub(crate) use local_file::{device_only_source, open_source, queued_source, LocalSource};
18pub use local_file::{LocalFileError, LocalFileStream};
19
20/// One file as its record stood when it was read.
21#[derive(Clone, Debug, PartialEq)]
22pub struct FileRef {
23    id: FileId,
24    audience: Audience,
25    namespace: String,
26    size: u64,
27    place: Stored,
28}
29
30#[derive(Clone, Debug, PartialEq)]
31enum Stored {
32    Cloud { location: FileLocation, hash: ContentHash },
33    DeviceOnly(DeviceId),
34}
35
36/// Where a file is (§17.2): in storage, or only on one device.
37#[derive(Clone, Copy, Debug, PartialEq)]
38pub enum FilePlace<'a> {
39    /// Stored at its location, with its content hash.
40    Cloud {
41        /// The stored object and its key.
42        location: &'a FileLocation,
43        /// The device that uploaded it.
44        uploader: DeviceId,
45        /// The SHA-256 of its bytes, which every download checks.
46        content_hash: &'a ContentHash,
47    },
48    /// Only on one device, which may be this one: while a file uploads, it
49    /// is on its uploader only.
50    DeviceOnly {
51        /// The device that has it.
52        device: DeviceId,
53    },
54}
55
56impl FileRef {
57    /// The file's id, which rows name.
58    pub fn id(&self) -> FileId {
59        self.id
60    }
61    /// Its size in bytes.
62    pub fn size(&self) -> u64 {
63        self.size
64    }
65    /// Its namespace, which says its kind and whose cache budget it counts
66    /// against.
67    pub fn namespace(&self) -> &str {
68        &self.namespace
69    }
70    /// Its audience: only that audience's rows reference it, and only its
71    /// members read it.
72    pub fn audience(&self) -> Audience {
73        self.audience.clone()
74    }
75    /// Where it is.
76    pub fn place(&self) -> FilePlace<'_> {
77        match &self.place {
78            Stored::Cloud { location, hash } => {
79                FilePlace::Cloud { location, uploader: location.uploader(), content_hash: hash }
80            }
81            Stored::DeviceOnly(device) => FilePlace::DeviceOnly { device: *device },
82        }
83    }
84
85    pub(crate) fn of(record: Record) -> Result<Self, DbError> {
86        let place = match (record.place, record.hash) {
87            (Place::Cloud(location), Some(hash)) => Stored::Cloud { location, hash },
88            (Place::DeviceOnly(device), None) => Stored::DeviceOnly(device),
89            _ => return Err(DbError::DamagedDatabase),
90        };
91        Ok(Self {
92            id: record.id,
93            audience: record.audience,
94            namespace: record.namespace,
95            size: record.size,
96            place,
97        })
98    }
99}
100
101/// The file this device has a record of under `file`.
102pub(crate) fn read(db: &DatabaseConnection, file: FileId) -> Result<FileRef, DbError> {
103    FileRef::of(Record::read(db, file)?.ok_or(DbError::FileAbsent { file })?)
104}
105
106#[cfg(test)]
107#[path = "file_ref_tests.rs"]
108mod tests;