1use 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, 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}