Skip to main content

lfsx_server/
page.rs

1use crate::locks::Lock;
2
3// The lock list is answered from a directory of files, so there is no server-side
4// cursor to hand out and nothing to keep between requests. The id of the last
5// lock returned is enough: the list is ordered by id, so resuming means skipping
6// past that one. A lock released in between simply is not there any more, which
7// is the same answer a client would get by asking again.
8pub const DEFAULT: usize = 100;
9pub const MAX: usize = 1000;
10
11pub struct Page {
12    pub locks: Vec<Lock>,
13    pub next_cursor: String,
14}
15
16pub fn paginate(mut locks: Vec<Lock>, cursor: Option<&str>, limit: Option<usize>) -> Page {
17    locks.sort_by(|one, other| one.id.cmp(&other.id));
18
19    let from = match cursor {
20        Some(cursor) => locks
21            .iter()
22            .position(|lock| lock.id.as_str() > cursor)
23            .unwrap_or(locks.len()),
24        None => 0,
25    };
26
27    // A limit the client did not ask for is still a limit: without one, a studio
28    // that has locked an art directory receives every lock it holds in a single
29    // body, and a client honouring the field it sent believes it has seen the
30    // whole list.
31    let limit = limit.unwrap_or(DEFAULT).clamp(1, MAX);
32    let mut page: Vec<Lock> = locks.into_iter().skip(from).take(limit + 1).collect();
33
34    // One more was fetched than asked for: if it is there, there is another page,
35    // and the cursor is the last id actually returned.
36    let next_cursor = match page.len() > limit {
37        true => {
38            page.truncate(limit);
39            page.last().map(|lock| lock.id.clone()).unwrap_or_default()
40        }
41        false => String::new(),
42    };
43
44    Page {
45        locks: page,
46        next_cursor,
47    }
48}
49
50#[cfg(test)]
51mod tests;