monarch_conda/
pack_meta_history.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use std::path::Path;
10use std::path::PathBuf;
11
12use anyhow::Context;
13use anyhow::Result;
14use anyhow::ensure;
15use rattler_conda_types::package::FileMode;
16use serde::Deserialize;
17use serde::Serialize;
18use tokio::fs;
19
20#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
21pub struct Offset {
22    pub start: usize,
23    pub len: usize,
24    pub contents: Option<Vec<(usize, usize)>>,
25}
26
27#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
28pub struct OffsetRecord {
29    pub path: PathBuf,
30    pub mode: FileMode,
31    pub offsets: Vec<Offset>,
32}
33
34impl OffsetRecord {
35    fn to_str(&self) -> Result<String> {
36        Ok(serde_json::to_string(&(
37            &self.path,
38            self.mode,
39            &self
40                .offsets
41                .iter()
42                .map(|o| {
43                    (
44                        o.start,
45                        o.len,
46                        o.contents.as_ref().map(|c| {
47                            c.iter()
48                                .map(|(a, b)| (a, b, None::<()>))
49                                .collect::<Vec<_>>()
50                        }),
51                    )
52                })
53                .collect::<Vec<_>>(),
54        ))?)
55    }
56
57    fn from_str(str: &str) -> Result<Self> {
58        let (path, mode, offsets): (_, _, Vec<(usize, usize, Option<Vec<(usize, usize, ())>>)>) =
59            serde_json::from_str(str).with_context(|| format!("parsing: {}", str))?;
60        Ok(OffsetRecord {
61            path,
62            mode,
63            offsets: offsets
64                .into_iter()
65                .map(|(start, len, contents)| Offset {
66                    start,
67                    len,
68                    contents: contents.map(|c| c.into_iter().map(|(a, b, _)| (a, b)).collect()),
69                })
70                .collect(),
71        })
72    }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
76pub struct Offsets {
77    pub entries: Vec<OffsetRecord>,
78}
79
80impl Offsets {
81    pub fn from_contents(s: &str) -> Result<Self> {
82        let mut entries = Vec::new();
83        for line in s.lines() {
84            entries.push(OffsetRecord::from_str(line)?);
85        }
86        Ok(Offsets { entries })
87    }
88
89    pub async fn from_env(env: &Path) -> Result<Self> {
90        let path = env.join("pack-meta").join("offsets.jsonl");
91        let s = fs::read_to_string(&path).await?;
92        Self::from_contents(&s)
93    }
94
95    pub fn to_str(&self) -> Result<String> {
96        let mut str = String::new();
97        for entry in &self.entries {
98            str += &entry.to_str()?;
99            str += "\n";
100        }
101        Ok(str)
102    }
103}
104
105#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
106pub struct HistoryRecord {
107    pub timestamp: u64, // timestamps are truncated into seconds
108    pub prefix: PathBuf,
109    pub finished: bool,
110}
111
112impl HistoryRecord {
113    fn to_str(&self) -> Result<String> {
114        Ok(serde_json::to_string(&(
115            self.timestamp,
116            &self.prefix,
117            self.finished,
118        ))?)
119    }
120
121    fn from_str(line: &str) -> Result<Self> {
122        let (timestamp, prefix, finished) = serde_json::from_str(line)?;
123        Ok(HistoryRecord {
124            timestamp,
125            prefix,
126            finished,
127        })
128    }
129}
130
131#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
132pub struct History {
133    pub entries: Vec<HistoryRecord>,
134}
135
136impl History {
137    pub fn from_contents(s: &str) -> Result<Self> {
138        let mut entries = Vec::new();
139        for line in s.lines() {
140            entries.push(HistoryRecord::from_str(line)?);
141        }
142        Ok(History { entries })
143    }
144
145    pub async fn from_env(env: &Path) -> Result<Self> {
146        let path = env.join("pack-meta").join("history.jsonl");
147        let s = fs::read_to_string(&path).await?;
148        Self::from_contents(&s)
149    }
150
151    pub fn to_str(&self) -> Result<String> {
152        let mut str = String::new();
153        for entry in &self.entries {
154            str += &entry.to_str()?;
155            str += "\n";
156        }
157        Ok(str)
158    }
159
160    pub fn first(&self) -> Result<(&Path, u64)> {
161        let first = self.entries.first().context("missing history")?;
162        ensure!(first.finished);
163        Ok((&first.prefix, first.timestamp))
164    }
165
166    pub fn last_prefix_update(&self) -> Result<Option<(&Path, u64, u64)>> {
167        let last = self.entries.last().context("missing history")?;
168        ensure!(last.finished);
169        Ok(if let [.., record, _] = &self.entries[..] {
170            ensure!(!record.finished);
171            ensure!(record.prefix == last.prefix);
172            Some((&record.prefix, record.timestamp, last.timestamp))
173        } else {
174            None
175        })
176    }
177
178    pub fn last_prefix(&self) -> Result<&Path> {
179        if let Some((prefix, _, _)) = self.last_prefix_update()? {
180            return Ok(prefix);
181        }
182        let (prefix, _) = self.first()?;
183        Ok(prefix)
184    }
185
186    pub fn prefix_and_last_update_window(&self) -> Result<(&Path, Option<(u64, u64)>)> {
187        let src_first = self.first()?;
188        Ok(if let Some((prefix, s, e)) = self.last_prefix_update()? {
189            (prefix, Some((s, e)))
190        } else {
191            (src_first.0, None)
192        })
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use std::path::PathBuf;
199
200    use super::*;
201
202    #[test]
203    fn test_offset_record_parsing() {
204        let json = r#"["lib/pkgconfig/pthread-stubs.pc", "text", [[7, 67, null]]]"#;
205        let record: OffsetRecord = serde_json::from_str(json).unwrap();
206
207        assert_eq!(record.path, PathBuf::from("lib/pkgconfig/pthread-stubs.pc"));
208        assert_eq!(record.mode, FileMode::Text);
209        assert_eq!(record.offsets.len(), 1);
210        assert_eq!(record.offsets[0].start, 7);
211        assert_eq!(record.offsets[0].len, 67);
212        assert_eq!(record.offsets[0].contents, None);
213    }
214}