coven_database/
sync_facts.rs1use crate::sqlite::DatabaseConnection;
16use crate::DbError;
17use std::collections::{BTreeMap, BTreeSet};
18use std::time::SystemTime;
19
20#[derive(Clone, Debug, PartialEq)]
22pub struct SyncFacts<P> {
23 pub row: crate::SyncStatusRow,
25 pub retry: crate::RetryState,
28 pub unsent: bool,
30 pub uploads: Vec<UploadFacts<P>>,
37 pub queued: bool,
40 pub downloading: bool,
42 pub records: Vec<P>,
44 pub paused: bool,
46 pub unmetered_only: bool,
48 pub metered: bool,
51}
52
53#[derive(Clone, Debug, PartialEq)]
55pub struct UploadFacts<P> {
56 pub sending: bool,
58 pub stored: bool,
60 pub bound: bool,
62 pub retry_at: Option<SystemTime>,
64 pub record: Option<P>,
66}
67
68pub(crate) const UPLOADS: &str = "SELECT u.id,u.stored,u.holds IS NOT NULL,r.not_before,e.id
74 FROM _coven_file_uploads AS u
75 LEFT JOIN _coven_retries AS r ON r.upload=u.id
76 LEFT JOIN _coven_pending AS e
77 ON e.work='upload' AND e.device=u.device AND e.object=u.object
78 WHERE u.id IN (
79 SELECT f.id FROM _coven_pending AS p
80 JOIN _coven_file_uploads AS f ON f.device=p.device AND f.object=p.object
81 WHERE p.work='upload'
82 UNION
83 SELECT MIN(position) FROM coven_sending
84 UNION
85 SELECT * FROM (
86 SELECT f.id FROM _coven_file_uploads AS f
87 WHERE NOT f.stored
88 AND f.id NOT IN (SELECT position FROM coven_sending)
89 AND EXISTS(SELECT 1 FROM _coven_upload_files AS l WHERE l.upload=f.id)
90 AND NOT EXISTS(SELECT 1 FROM _coven_pending AS p
91 WHERE p.work='upload' AND p.device=f.device
92 AND p.object=f.object)
93 ORDER BY f.id LIMIT 1))
94 AND EXISTS(SELECT 1 FROM _coven_upload_files AS l WHERE l.upload=u.id)
95 ORDER BY u.id";
96
97pub(crate) fn read<P: crate::PendingValue + Clone>(
99 db: &DatabaseConnection,
100) -> Result<SyncFacts<P>, DbError> {
101 let records = crate::pending::positioned::<P>(db)?;
102 let by_position: BTreeMap<i64, &P> =
103 records.iter().map(|(position, record)| (*position, record)).collect();
104 let sending: BTreeSet<i64> =
105 db.query("SELECT position FROM coven_sending", [], |row| row.get(0))?.into_iter().collect();
106 let uploads = db
107 .query(UPLOADS, [], |row| {
108 Ok((
109 row.get::<_, i64>(0)?,
110 row.get::<_, bool>(1)?,
111 row.get::<_, bool>(2)?,
112 row.get::<_, Option<Vec<u8>>>(3)?,
113 row.get::<_, Option<i64>>(4)?,
114 ))
115 })?
116 .into_iter()
117 .map(|(id, stored, bound, retry_at, position)| {
118 let record = position
119 .map(|position| {
120 by_position.get(&position).map(|record| (*record).clone()).ok_or(
121 DbError::Invariant {
122 what: "an upload's record is among the pending records",
123 },
124 )
125 })
126 .transpose()?;
127 Ok(UploadFacts {
128 sending: sending.contains(&id),
129 stored,
130 bound,
131 retry_at: retry_at.as_deref().map(crate::user_file::decode_time).transpose()?,
132 record,
133 })
134 })
135 .collect::<Result<_, DbError>>()?;
136 let (metered, unmetered_only) =
137 db.query_row("SELECT metered,unmetered_only FROM coven_network", [], |row| {
138 Ok((row.get::<_, bool>(0)?, row.get::<_, bool>(1)?))
139 })?;
140 Ok(SyncFacts {
141 row: crate::internal_schema::sync_status::read(db)?,
142 retry: crate::internal_schema::retry_effect::read(db)?,
143 unsent: db.query_row("SELECT EXISTS(SELECT 1 FROM _coven_uploads)", [], |r| r.get(0))?,
144 uploads,
145 queued: db.query_row(
146 "SELECT EXISTS(SELECT 1 FROM _coven_file_uploads AS u
147 WHERE EXISTS(SELECT 1 FROM _coven_upload_files AS l WHERE l.upload=u.id))",
148 [],
149 |r| r.get(0),
150 )?,
151 downloading: db
152 .query_row("SELECT EXISTS(SELECT 1 FROM coven_downloading)", [], |r| r.get(0))?,
153 records: records.into_iter().map(|(_, record)| record).collect(),
154 paused: crate::file_queue::paused(db)?,
155 unmetered_only,
156 metered,
157 })
158}
159
160#[cfg(test)]
161#[path = "sync_facts_tests.rs"]
162mod tests;