coven_database/
file_write_device_only.rs1use super::FileWrite;
6use crate::{
7 file_hashes::FileHashes,
8 file_location::{FileLocation, Place},
9 file_table::Record,
10 sqlite::DatabaseConnection,
11 write_schema::WriteSchema,
12 DbError, Provenance,
13};
14use coven_foundation::id_source::{DeviceId, FileId};
15use coven_merge::RowId;
16use std::path::PathBuf;
17
18#[derive(Debug)]
23pub struct DeviceOnlyCopy {
24 pub file: FileId,
26 pub location: FileLocation,
28 pub path: PathBuf,
30 pub hashes: FileHashes,
32}
33
34pub(crate) fn permitted(
40 db: &DatabaseConnection,
41 schema: &WriteSchema,
42 device: DeviceId,
43 file: FileId,
44) -> Result<Record, DbError> {
45 let record = Record::read(db, file)?.ok_or(DbError::FileAbsent { file })?;
46 schema.namespace_of(&record.namespace, Provenance::UserProvided)?;
47 if !matches!(record.place, Place::Cloud(_)) {
48 return Err(DbError::NotInCloud { file });
49 }
50 crate::write_commit::check_membership(db, device, &record.audience)?;
51 let mut owners = crate::judge::Owners::new(db, schema, device, db.schema_version()?)?;
52 for (table, key) in db.query(
53 "SELECT DISTINCT table_name,key FROM _coven_file_references WHERE file=?1
54 ORDER BY table_name,key",
55 [file],
56 |r| Ok((r.get::<_, String>(0)?, r.get::<_, Vec<u8>>(1)?)),
57 )? {
58 let row = RowId { table, key, audience: record.audience.clone() };
59 if let Some(owner) = owners.foreign_owner(&row)? {
60 return Err(DbError::NotOwner {
61 key: crate::RowKey::decode(&row.key)?,
62 table: row.table,
63 owner,
64 });
65 }
66 }
67 Ok(record)
68}
69
70impl FileWrite<'_> {
71 pub(crate) fn make_prepared_device_only(
75 &self,
76 prepared: crate::PreparedDeviceOnly,
77 ) -> Result<(), DbError> {
78 super::held_here(self.files.copying, &prepared.paths())?;
79 let (copies, paths) = prepared.take();
80 self.files.held_copies.borrow_mut().extend(paths);
81 self.make_device_only(copies)
82 }
83
84 fn make_device_only(&self, copies: Vec<DeviceOnlyCopy>) -> Result<(), DbError> {
89 let db = self.database;
90 for copy in copies {
91 let record = permitted(db, self.files.schema, self.files.device, copy.file)?;
92 if record.place != Place::Cloud(copy.location.clone()) {
93 return Err(DbError::NotInCloud { file: copy.file });
94 }
95 crate::file_table::device_only(db, copy.file, self.files.device)?;
96 self.files.local.borrow_mut().push(copy);
97 }
98 Ok(())
99 }
100
101 #[expect(
107 clippy::map_err_ignore,
108 reason = "a stored value that won't convert or decode is DamagedDatabase"
109 )]
110 pub(super) fn keep_device_only(&self) -> Result<(), DbError> {
111 let db = self.database;
112 for copy in self.files.local.take() {
113 let path = crate::database::file_database::copies::text(©.path)?;
114 let (size, modified_at) = db
115 .query(
116 "SELECT size,modified_at FROM _coven_copies WHERE path=?1 AND state='placed'",
117 [path],
118 |r| Ok((r.get::<_, i64>(0)?, r.get::<_, Vec<u8>>(1)?)),
119 )?
120 .pop()
121 .ok_or(DbError::Invariant {
122 what: "a copy is placed before its file switches to it",
123 })?;
124 let original = crate::UserFile {
125 path: copy.path.clone(),
126 size: u64::try_from(size).map_err(|_| DbError::DamagedDatabase)?,
127 modified_at: crate::user_file::decode_time(&modified_at)?,
128 };
129 crate::user_file::forget(db, copy.file)?;
130 crate::user_file::record(db, copy.file, &original, ©.hashes)?;
131 crate::database::file_database::copies::keep(db, path)?;
132 }
133 Ok(())
134 }
135}