1use crate::LocalLb;
2use crate::model::errors::LbResult;
3use crate::model::tree_like::TreeLike;
4use crate::service::events::Actor;
5use serde::{Deserialize, Serialize};
6use std::cmp::{self, Ordering};
7use std::collections::HashMap;
8use uuid::Uuid;
9
10impl LocalLb {
11 #[instrument(level = "debug", skip(self), err(Debug))]
12 pub async fn suggested_docs(&self, settings: RankingWeights) -> LbResult<Vec<Uuid>> {
13 let db = self.ro_tx().await;
14 let db = db.db();
15
16 let mut scores = db.doc_events.get().iter().get_activity_metrics();
17 self.normalize(&mut scores);
18
19 scores.sort_unstable_by_key(|b| cmp::Reverse(b.score(settings)));
20
21 scores.truncate(10);
22
23 let mut result = Vec::new();
24 let mut tree = (&db.base_metadata).to_staged(&db.local_metadata).to_lazy();
25 for score in scores {
26 if tree.maybe_find(&score.id).is_none() {
27 continue;
28 }
29
30 if tree.calculate_deleted(&score.id)? {
31 continue;
32 }
33 if tree.in_pending_share(&score.id)? {
34 continue;
35 }
36 result.push(score.id);
37 }
38
39 Ok(result)
40 }
41
42 #[instrument(level = "debug", skip(self), err(Debug))]
43 pub async fn clear_suggested(&self) -> LbResult<()> {
44 let mut tx = self.begin_tx().await;
45 let db = tx.db();
46 db.doc_events.clear()?;
47 self.events.meta_changed(Actor::User(None));
48 Ok(())
49 }
50
51 #[instrument(level = "debug", skip(self), err(Debug))]
52 pub async fn clear_suggested_id(&self, id: Uuid) -> LbResult<()> {
53 let mut tx = self.begin_tx().await;
54 let db = tx.db();
55
56 let mut entries = db.doc_events.get().to_vec();
57 db.doc_events.clear()?;
58 entries.retain(|e| e.id() != id);
59 for entry in entries {
60 db.doc_events.push(entry)?;
61 }
62
63 Ok(())
64 }
65
66 pub(crate) async fn add_doc_event(&self, event: DocEvent) -> LbResult<()> {
67 let mut tx = self.begin_tx().await;
68 let db = tx.db();
69
70 let max_stored_events = 1000;
71 let events = &db.doc_events;
72
73 if events.get().len() > max_stored_events {
74 db.doc_events.remove(0)?;
75 }
76 db.doc_events.push(event)?;
77 Ok(())
78 }
79
80 pub(crate) fn normalize(&self, docs: &mut [DocActivityMetrics]) {
81 let read_count_range = StatisticValueRange {
82 max: docs.iter().map(|f| f.read_count).max().unwrap_or_default(),
83 min: docs.iter().map(|f| f.read_count).min().unwrap_or_default(),
84 };
85
86 let write_count_range = StatisticValueRange {
87 max: docs.iter().map(|f| f.write_count).max().unwrap_or_default(),
88 min: docs.iter().map(|f| f.write_count).min().unwrap_or_default(),
89 };
90
91 let last_read_range = StatisticValueRange {
92 max: docs
93 .iter()
94 .map(|f| f.last_read_timestamp)
95 .max()
96 .unwrap_or_default(),
97 min: docs
98 .iter()
99 .map(|f| f.last_read_timestamp)
100 .min()
101 .unwrap_or_default(),
102 };
103 let last_write_range = StatisticValueRange {
104 max: docs
105 .iter()
106 .map(|f| f.last_write_timestamp)
107 .max()
108 .unwrap_or_default(),
109 min: docs
110 .iter()
111 .map(|f| f.last_write_timestamp)
112 .min()
113 .unwrap_or_default(),
114 };
115
116 docs.iter_mut().for_each(|f| {
117 f.read_count.normalize(read_count_range);
118 f.write_count.normalize(write_count_range);
119 f.last_read_timestamp.normalize(last_read_range);
120 f.last_write_timestamp.normalize(last_write_range);
121 });
122 }
123
124 pub fn app_foregrounded(&self) {
126 let bg_lb = self.clone();
127 tokio::spawn(async move {
128 *bg_lb.user_last_seen.write().await = web_time::Instant::now();
129 bg_lb.user_wake.notify_one();
130 });
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Hash, Copy)]
135pub enum DocEvent {
136 Read(Uuid, i64),
137 Write(Uuid, i64),
138}
139impl DocEvent {
140 pub fn timestamp(&self) -> i64 {
141 match *self {
142 DocEvent::Read(_, timestamp) => timestamp,
143 DocEvent::Write(_, timestamp) => timestamp,
144 }
145 }
146 pub fn id(&self) -> Uuid {
147 match *self {
148 DocEvent::Read(id, _) => id,
149 DocEvent::Write(id, _) => id,
150 }
151 }
152}
153
154#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
155pub struct RankingWeights {
156 pub temporality: i64,
158 pub io: i64,
160}
161
162impl Default for RankingWeights {
163 fn default() -> Self {
164 Self { temporality: 60, io: 40 }
165 }
166}
167#[derive(Default, Copy, Clone, PartialEq)]
168pub struct StatisticValue {
169 pub raw: i64,
170 pub normalized: Option<f64>,
171}
172
173impl Ord for StatisticValue {
174 fn cmp(&self, other: &Self) -> Ordering {
175 (self.raw).cmp(&other.raw)
176 }
177}
178
179impl PartialOrd for StatisticValue {
180 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
181 Some(self.cmp(other))
182 }
183}
184
185impl Eq for StatisticValue {}
186
187#[derive(Clone, Copy)]
188pub struct StatisticValueRange {
189 pub max: StatisticValue,
190 pub min: StatisticValue,
191}
192impl StatisticValue {
193 pub fn normalize(&mut self, range: StatisticValueRange) {
194 let mut range_distance = range.max.raw - range.min.raw;
195 if range_distance == 0 {
196 range_distance = 1
197 };
198 let normalized = (self.raw - range.min.raw) as f64 / range_distance as f64;
199 self.normalized = Some(normalized);
200 }
201}
202#[derive(Default, Copy, Clone, PartialEq)]
205pub struct DocActivityMetrics {
206 pub id: Uuid,
207 pub last_read_timestamp: StatisticValue,
209 pub last_write_timestamp: StatisticValue,
211 pub read_count: StatisticValue,
213 pub write_count: StatisticValue,
215}
216
217impl DocActivityMetrics {
218 pub fn score(&self, weights: RankingWeights) -> i64 {
219 let timestamp_weight = weights.temporality;
220 let io_count_weight = weights.io;
221
222 let temporality_score = (self.last_read_timestamp.normalized.unwrap_or_default()
223 + self.last_write_timestamp.normalized.unwrap_or_default())
224 * timestamp_weight as f64;
225
226 let io_score = (self.read_count.normalized.unwrap_or_default()
227 + self.write_count.normalized.unwrap_or_default())
228 * io_count_weight as f64;
229
230 (io_score + temporality_score).ceil() as i64
231 }
232}
233pub trait Stats {
234 fn get_activity_metrics(self) -> Vec<DocActivityMetrics>;
235}
236impl<'a, T> Stats for T
237where
238 T: Iterator<Item = &'a DocEvent>,
239{
240 fn get_activity_metrics(self) -> Vec<DocActivityMetrics> {
241 let mut result = Vec::new();
242
243 let mut set = HashMap::new();
244 for event in self {
245 match set.get_mut(&event.id()) {
246 None => {
247 set.insert(event.id(), vec![event]);
248 }
249 Some(events) => {
250 events.push(event);
251 }
252 }
253 }
254
255 for (id, events) in set {
256 let read_events = events.iter().filter(|e| matches!(e, DocEvent::Read(_, _)));
257
258 let last_read = read_events
259 .clone()
260 .max_by(|x, y| x.timestamp().cmp(&y.timestamp()));
261
262 let last_read = match last_read {
263 None => 0,
264 Some(x) => x.timestamp(),
265 };
266
267 let write_events = events.iter().filter(|e| matches!(e, DocEvent::Write(_, _)));
268
269 let last_write = write_events
270 .clone()
271 .max_by(|x, y| x.timestamp().cmp(&y.timestamp()));
272 let last_write = match last_write {
273 None => 0,
274 Some(x) => x.timestamp(),
275 };
276
277 let metrics = DocActivityMetrics {
278 id,
279 last_read_timestamp: StatisticValue { raw: last_read, normalized: None },
280 last_write_timestamp: StatisticValue { raw: last_write, normalized: None },
281 read_count: StatisticValue { raw: read_events.count() as i64, normalized: None },
282 write_count: StatisticValue { raw: write_events.count() as i64, normalized: None },
283 };
284 result.push(metrics);
285 }
286
287 result
288 }
289}