1extern crate chrono;
2extern crate deb_architectures;
3#[macro_use]
4extern crate smart_default;
5
6mod entry;
7mod image_size;
8mod time;
9
10pub use self::entry::*;
11pub use self::image_size::*;
12
13use self::time::get_time;
14use chrono::{DateTime, Utc};
15use std::collections::BTreeMap;
16use std::path::Path;
17use std::str::FromStr;
18use std::{fs, io};
19
20#[derive(Debug, SmartDefault, Clone, PartialEq)]
22pub struct DistRelease {
23 pub architectures: Vec<String>,
24 pub codename: String,
25 pub components: Vec<String>,
26 #[default = "Utc::now()"]
27 pub date: DateTime<Utc>,
28 pub description: String,
29 pub label: String,
30 pub origin: String,
31 pub suite: String,
32 pub version: String,
33 pub sums: BTreeMap<String, EntryComponents>,
34}
35
36impl DistRelease {
37 pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
38 fs::read_to_string(path).and_then(|string| string.parse::<Self>())
39 }
40}
41
42impl FromStr for DistRelease {
43 type Err = io::Error;
44
45 fn from_str(input: &str) -> Result<Self, Self::Err> {
46 let mut iterator = input.lines();
47
48 let mut release = DistRelease::default();
49
50 #[derive(Copy, Clone)]
51 enum Variant {
52 Archs,
53 Codename,
54 Components,
55 Date,
56 Description,
57 Label,
58 Origin,
59 Suite,
60 Version,
61 }
62
63 let mut entries = vec![
64 ("Architectures:", Variant::Archs),
65 ("Codename:", Variant::Codename),
66 ("Components:", Variant::Components),
67 ("Date:", Variant::Date),
68 ("Description:", Variant::Description),
69 ("Label:", Variant::Label),
70 ("Origin:", Variant::Origin),
71 ("Suite:", Variant::Suite),
72 ("Version:", Variant::Version),
73 ];
74
75 let mut remove = None;
76
77 fn get_string(value: &str) -> String {
78 value.trim().to_owned()
79 }
80
81 fn get_vec(value: &str) -> Vec<String> {
82 value.split_whitespace().map(String::from).collect()
83 }
84
85 let mut sum = None;
86
87 while !entries.is_empty() {
88 let line = iterator.next().unwrap();
89
90 for (id, &(ref key, variant)) in entries.iter().enumerate() {
91 if line.starts_with(key) {
92 remove = Some(id);
93
94 let value = &line[key.len()..];
95
96 match variant {
97 Variant::Archs => release.architectures = get_vec(value),
98 Variant::Codename => release.codename = get_string(value),
99 Variant::Components => release.components = get_vec(value),
100 Variant::Date => release.date = get_time(value)?,
101 Variant::Description => release.description = get_string(value),
102 Variant::Label => release.label = get_string(value),
103 Variant::Origin => release.origin = get_string(value),
104 Variant::Suite => release.suite = get_string(value),
105 Variant::Version => release.version = get_string(value),
106 }
107 }
108 }
109
110 if let Some(pos) = remove.take() {
111 entries.remove(pos);
112 } else if line.ends_with(':') {
113 sum = Some(line.trim()[..line.len() - 1].to_owned());
114 break
115 } else {
116 return Err(io::Error::new(
117 io::ErrorKind::InvalidData,
118 format!("unknown key in release file: {}", line),
119 ));
120 }
121 }
122
123 let mut active_hash = sum.unwrap_or_default();
124 let mut active_components = EntryComponents::default();
125
126 for line in iterator {
127 if line.starts_with(' ') {
128 match line.parse::<ReleaseEntry>() {
129 Ok(mut entry) => {
130 let mut path = String::new();
131 ::std::mem::swap(&mut path, &mut entry.path);
132 let base = match path.find('.') {
133 Some(pos) => &path[..pos],
134 None => &path,
135 };
136
137 match path.find('/') {
138 Some(pos) => {
139 let component = &path[..pos];
140 entry.path = path[pos + 1..].to_owned();
142
143 active_components
144 .components
145 .entry(component.into())
146 .and_modify(|e| {
147 e.entry(base.into())
148 .and_modify(|e| e.push(entry.to_owned()))
149 .or_insert_with(|| vec![entry.to_owned()]);
150 })
151 .or_insert_with(|| {
152 let mut map = BTreeMap::new();
153 map.insert(base.into(), vec![entry.to_owned()]);
154 map
155 });
156 }
157 None => {
158 entry.path = path.clone();
159 active_components
160 .base
161 .entry(base.into())
162 .and_modify(|e| e.push(entry.to_owned()))
163 .or_insert_with(|| vec![entry.to_owned()]);
164 }
165 }
166 }
167 Err(why) => {
168 return Err(io::Error::new(
169 io::ErrorKind::InvalidData,
170 format!("invalid checksum entry: {}", why),
171 ))
172 }
173 }
174 } else {
175 if !active_components.is_empty() {
176 release
177 .sums
178 .insert(active_hash.clone(), active_components.clone());
179 active_components.clear();
180 }
181
182 active_hash.clear();
183 active_hash.push_str(line.trim());
184 active_hash.pop();
185 }
186 }
187
188 if !active_components.is_empty() {
189 release.sums.insert(active_hash, active_components);
190 }
191
192 Ok(release)
193 }
194}
195
196#[derive(Debug, Default, Clone, Hash, PartialEq)]
198pub struct EntryComponents {
199 pub base: BTreeMap<String, Vec<ReleaseEntry>>,
200 pub components: BTreeMap<String, BTreeMap<String, Vec<ReleaseEntry>>>,
201}
202
203impl EntryComponents {
204 pub fn clear(&mut self) {
205 self.base.clear();
206 self.components.clear();
207 }
208
209 pub fn is_empty(&self) -> bool {
210 self.base.is_empty() && self.components.is_empty()
211 }
212}