1use serde::Deserialize;
2use sha2::{Digest, Sha256};
3
4use crate::{Error, Gtfs, RawGtfs};
5use std::collections::HashMap;
6use std::convert::TryFrom;
7use std::fs::File;
8use std::io::Read;
9use std::path::Path;
10use web_time::Instant;
11
12pub struct GtfsReader {
35 pub read_stop_times: bool,
37 pub read_shapes: bool,
39 pub unkown_enum_as_default: bool,
41 pub trim_fields: bool,
46}
47
48impl Default for GtfsReader {
49 fn default() -> Self {
50 GtfsReader {
51 read_stop_times: true,
52 read_shapes: true,
53 unkown_enum_as_default: false,
54 trim_fields: true,
55 }
56 }
57}
58
59impl GtfsReader {
60 pub fn read_stop_times(mut self, read_stop_times: bool) -> Self {
65 self.read_stop_times = read_stop_times;
66 self
67 }
68
69 pub fn read_shapes(mut self, read_shapes: bool) -> Self {
72 self.read_shapes = read_shapes;
73 self
74 }
75
76 pub fn unkown_enum_as_default(mut self, unkown_enum_as_default: bool) -> Self {
83 self.unkown_enum_as_default = unkown_enum_as_default;
84 self
85 }
86
87 pub fn trim_fields(mut self, trim_fields: bool) -> Self {
92 self.trim_fields = trim_fields;
93 self
94 }
95
96 #[cfg(not(target_arch = "wasm32"))]
101 pub fn read(self, gtfs: &str) -> Result<Gtfs, Error> {
102 self.raw().read(gtfs).and_then(Gtfs::try_from)
103 }
104
105 pub fn read_from_path<P>(self, path: P) -> Result<Gtfs, Error>
107 where
108 P: AsRef<Path>,
109 {
110 self.raw().read_from_path(path).and_then(Gtfs::try_from)
111 }
112
113 #[cfg(all(feature = "read-url", not(target_arch = "wasm32")))]
117 pub fn read_from_url<U: reqwest::IntoUrl>(self, url: U) -> Result<Gtfs, Error> {
118 self.raw().read_from_url(url).and_then(Gtfs::try_from)
119 }
120
121 #[cfg(feature = "read-url")]
125 pub async fn read_from_url_async<U: reqwest::IntoUrl>(self, url: U) -> Result<Gtfs, Error> {
126 self.raw()
127 .read_from_url_async(url)
128 .await
129 .and_then(Gtfs::try_from)
130 }
131
132 pub fn raw(self) -> RawGtfsReader {
144 RawGtfsReader { reader: self }
145 }
146}
147
148pub struct RawGtfsReader {
152 reader: GtfsReader,
153}
154
155impl RawGtfsReader {
156 fn read_from_directory(&self, p: &std::path::Path) -> Result<RawGtfs, Error> {
157 let start_of_read_instant = Instant::now();
158 let files = std::fs::read_dir(p)?
161 .filter_map(|d| {
162 d.ok().and_then(|e| {
163 e.path()
164 .strip_prefix(p)
165 .ok()
166 .and_then(|f| f.to_str().map(|s| s.to_owned()))
167 })
168 })
169 .collect();
170
171 let mut result = RawGtfs {
172 trips: self.read_objs_from_path(p.join("trips.txt")),
173 calendar: self.read_objs_from_optional_path(p, "calendar.txt"),
174 calendar_dates: self.read_objs_from_optional_path(p, "calendar_dates.txt"),
175 stops: self.read_objs_from_path(p.join("stops.txt")),
176 routes: self.read_objs_from_path(p.join("routes.txt")),
177 stop_times: if self.reader.read_stop_times {
178 self.read_objs_from_path(p.join("stop_times.txt"))
179 } else {
180 Ok(Vec::new())
181 },
182 agencies: self.read_objs_from_path(p.join("agency.txt")),
183 shapes: self.read_objs_from_optional_path(p, "shapes.txt"),
184 fare_attributes: self.read_objs_from_optional_path(p, "fare_attributes.txt"),
185 fare_rules: self.read_objs_from_optional_path(p, "fare_rules.txt"),
186 fare_products: self.read_objs_from_optional_path(p, "fare_products.txt"),
187 fare_media: self.read_objs_from_optional_path(p, "fare_media.txt"),
188 rider_categories: self.read_objs_from_optional_path(p, "rider_categories.txt"),
189 frequencies: self.read_objs_from_optional_path(p, "frequencies.txt"),
190 transfers: self.read_objs_from_optional_path(p, "transfers.txt"),
191 pathways: self.read_objs_from_optional_path(p, "pathways.txt"),
192 feed_info: self.read_objs_from_optional_path(p, "feed_info.txt"),
193 read_duration: start_of_read_instant.elapsed(),
194 translations: self.read_objs_from_optional_path(p, "translations.txt"),
195 ticketing_deep_links: self.read_objs_from_optional_path(p, "ticketing_deep_links.txt"),
196 ticketing_identifiers: self
197 .read_objs_from_optional_path(p, "ticketing_identifiers.txt"),
198 attributions: self.read_objs_from_optional_path(p, "attributions.txt"),
199 licenses: self.read_objs_from_optional_path(p, "licenses.txt"),
200 files,
201 source_format: crate::SourceFormat::Directory,
202 sha256: None,
203 };
204
205 if self.reader.unkown_enum_as_default {
206 result.unknown_to_default();
207 }
208 Ok(result)
209 }
210
211 #[cfg(not(target_arch = "wasm32"))]
214 pub fn read(self, gtfs: &str) -> Result<RawGtfs, Error> {
215 #[cfg(feature = "read-url")]
216 if gtfs.starts_with("http") {
217 return self.read_from_url(gtfs);
218 }
219 self.read_from_path(gtfs)
220 }
221
222 #[cfg(all(feature = "read-url", not(target_arch = "wasm32")))]
224 pub fn read_from_url<U: reqwest::IntoUrl>(self, url: U) -> Result<RawGtfs, Error> {
225 let mut res = reqwest::blocking::get(url)?;
226 let mut body = Vec::new();
227 res.read_to_end(&mut body)?;
228 let cursor = std::io::Cursor::new(body);
229 self.read_from_reader(cursor)
230 }
231
232 #[cfg(feature = "read-url")]
234 pub async fn read_from_url_async<U: reqwest::IntoUrl>(self, url: U) -> Result<RawGtfs, Error> {
235 let res = reqwest::get(url).await?.bytes().await?;
236 let reader = std::io::Cursor::new(res);
237 self.read_from_reader(reader)
238 }
239
240 pub fn read_from_path<P>(&self, path: P) -> Result<RawGtfs, Error>
242 where
243 P: AsRef<Path>,
244 {
245 let p = path.as_ref();
246 if p.is_file() {
247 let reader = File::open(p)?;
248 self.read_from_reader(reader)
249 } else if p.is_dir() {
250 self.read_from_directory(p)
251 } else {
252 Err(Error::NotFileNorDirectory(format!("{}", p.display())))
253 }
254 }
255
256 pub fn read_from_reader<T: std::io::Read + std::io::Seek>(
257 &self,
258 reader: T,
259 ) -> Result<RawGtfs, Error> {
260 let start_of_read_instant = Instant::now();
261 let hasher = Sha256::new();
262 let mut buf_reader = std::io::BufReader::new(reader);
263 let mut hash_io = digest_io::IoWrapper(hasher);
264 let _n = std::io::copy(&mut buf_reader, &mut hash_io)?;
265 let digest_io::IoWrapper(hasher) = hash_io;
266 let hash = hasher.finalize();
267 let mut archive = zip::ZipArchive::new(buf_reader)?;
268 let mut file_mapping = HashMap::new();
269 let mut files = Vec::new();
270
271 for i in 0..archive.len() {
272 let archive_file = archive.by_index(i)?;
273 files.push(archive_file.name().to_owned());
274
275 for gtfs_file in &[
276 "agency.txt",
277 "calendar.txt",
278 "calendar_dates.txt",
279 "routes.txt",
280 "stops.txt",
281 "stop_times.txt",
282 "trips.txt",
283 "fare_attributes.txt",
284 "fare_rules.txt",
285 "fare_products.txt",
286 "fare_media.txt",
287 "rider_categories.txt",
288 "frequencies.txt",
289 "transfers.txt",
290 "pathways.txt",
291 "feed_info.txt",
292 "shapes.txt",
293 "translations.txt",
294 "ticketing_deep_links.txt",
295 "ticketing_identifiers.txt",
296 "attributions.txt",
297 "licenses.txt",
298 ] {
299 let path = std::path::Path::new(archive_file.name());
300 if path.file_name() == Some(std::ffi::OsStr::new(gtfs_file)) {
301 file_mapping.insert(gtfs_file, i);
302 break;
303 }
304 }
305 }
306
307 let mut result = RawGtfs {
308 agencies: self.read_file(&file_mapping, &mut archive, "agency.txt"),
309 calendar: self.read_optional_file(&file_mapping, &mut archive, "calendar.txt"),
310 calendar_dates: self.read_optional_file(
311 &file_mapping,
312 &mut archive,
313 "calendar_dates.txt",
314 ),
315 routes: self.read_file(&file_mapping, &mut archive, "routes.txt"),
316 stops: self.read_file(&file_mapping, &mut archive, "stops.txt"),
317 stop_times: if self.reader.read_stop_times {
318 self.read_file(&file_mapping, &mut archive, "stop_times.txt")
319 } else {
320 Ok(Vec::new())
321 },
322 trips: self.read_file(&file_mapping, &mut archive, "trips.txt"),
323 fare_attributes: self.read_optional_file(
324 &file_mapping,
325 &mut archive,
326 "fare_attributes.txt",
327 ),
328 fare_rules: self.read_optional_file(&file_mapping, &mut archive, "fare_rules.txt"),
329 fare_products: self.read_optional_file(
330 &file_mapping,
331 &mut archive,
332 "fare_products.txt",
333 ),
334 fare_media: self.read_optional_file(&file_mapping, &mut archive, "fare_media.txt"),
335 rider_categories: self.read_optional_file(
336 &file_mapping,
337 &mut archive,
338 "rider_categories.txt",
339 ),
340 frequencies: self.read_optional_file(&file_mapping, &mut archive, "frequencies.txt"),
341 transfers: self.read_optional_file(&file_mapping, &mut archive, "transfers.txt"),
342 pathways: self.read_optional_file(&file_mapping, &mut archive, "pathways.txt"),
343 feed_info: self.read_optional_file(&file_mapping, &mut archive, "feed_info.txt"),
344 shapes: if self.reader.read_shapes {
345 self.read_optional_file(&file_mapping, &mut archive, "shapes.txt")
346 } else {
347 Some(Ok(Vec::new()))
348 },
349 translations: self.read_optional_file(&file_mapping, &mut archive, "translations.txt"),
350 ticketing_deep_links: self.read_optional_file(
351 &file_mapping,
352 &mut archive,
353 "ticketing_deep_links.txt",
354 ),
355 ticketing_identifiers: self.read_optional_file(
356 &file_mapping,
357 &mut archive,
358 "ticketing_identifiers.txt",
359 ),
360 attributions: self.read_optional_file(&file_mapping, &mut archive, "attributions.txt"),
361 licenses: self.read_optional_file(&file_mapping, &mut archive, "licenses.txt"),
362 read_duration: start_of_read_instant.elapsed(),
363 files,
364 source_format: crate::SourceFormat::Zip,
365 sha256: Some(base16ct::lower::encode_string(&hash)),
366 };
367
368 if self.reader.unkown_enum_as_default {
369 result.unknown_to_default();
370 }
371 Ok(result)
372 }
373
374 fn read_objs<T, O>(&self, mut reader: T, file_name: &str) -> Result<Vec<O>, Error>
375 where
376 for<'de> O: Deserialize<'de>,
377 T: std::io::Read,
378 {
379 let mut bom = [0; 3];
380 reader
381 .read_exact(&mut bom)
382 .map_err(|e| Error::NamedFileIO {
383 file_name: file_name.to_owned(),
384 source: Box::new(e),
385 })?;
386
387 let chained = if bom != [0xefu8, 0xbbu8, 0xbfu8] {
388 bom.chain(reader)
389 } else {
390 [].chain(reader)
391 };
392
393 let mut reader = csv::ReaderBuilder::new()
394 .flexible(true)
395 .trim(if self.reader.trim_fields {
396 csv::Trim::Fields
397 } else {
398 csv::Trim::None
399 })
400 .from_reader(chained);
401 let headers = reader
403 .headers()
404 .map_err(|e| Error::CSVError {
405 file_name: file_name.to_owned(),
406 source: e,
407 line_in_error: None,
408 })?
409 .clone()
410 .into_iter()
411 .map(|x| x.trim())
412 .collect::<csv::StringRecord>();
413
414 let mut rec = csv::StringRecord::new();
416 let mut objs = Vec::new();
417
418 while reader.read_record(&mut rec).map_err(|e| Error::CSVError {
420 file_name: file_name.to_owned(),
421 source: e,
422 line_in_error: None,
423 })? {
424 let obj = rec
425 .deserialize(Some(&headers))
426 .map_err(|e| Error::CSVError {
427 file_name: file_name.to_owned(),
428 source: e,
429 line_in_error: Some(crate::error::LineError {
430 headers: headers.into_iter().map(String::from).collect(),
431 values: rec.into_iter().map(String::from).collect(),
432 }),
433 })?;
434 objs.push(obj);
435 }
436 Ok(objs)
437 }
438
439 fn read_objs_from_path<O>(&self, path: std::path::PathBuf) -> Result<Vec<O>, Error>
440 where
441 for<'de> O: Deserialize<'de>,
442 {
443 let file_name = path
444 .file_name()
445 .and_then(|f| f.to_str())
446 .unwrap_or("invalid_file_name")
447 .to_string();
448 if path.exists() {
449 File::open(path)
450 .map_err(|e| Error::NamedFileIO {
451 file_name: file_name.to_owned(),
452 source: Box::new(e),
453 })
454 .and_then(|r| self.read_objs(r, &file_name))
455 } else {
456 Err(Error::MissingFile(file_name))
457 }
458 }
459
460 fn read_objs_from_optional_path<O>(
461 &self,
462 dir_path: &std::path::Path,
463 file_name: &str,
464 ) -> Option<Result<Vec<O>, Error>>
465 where
466 for<'de> O: Deserialize<'de>,
467 {
468 File::open(dir_path.join(file_name))
469 .ok()
470 .map(|r| self.read_objs(r, file_name))
471 }
472
473 fn read_file<O, T>(
474 &self,
475 file_mapping: &HashMap<&&str, usize>,
476 archive: &mut zip::ZipArchive<T>,
477 file_name: &str,
478 ) -> Result<Vec<O>, Error>
479 where
480 for<'de> O: Deserialize<'de>,
481 T: std::io::Read + std::io::Seek,
482 {
483 self.read_optional_file(file_mapping, archive, file_name)
484 .unwrap_or_else(|| Err(Error::MissingFile(file_name.to_owned())))
485 }
486
487 fn read_optional_file<O, T>(
488 &self,
489 file_mapping: &HashMap<&&str, usize>,
490 archive: &mut zip::ZipArchive<T>,
491 file_name: &str,
492 ) -> Option<Result<Vec<O>, Error>>
493 where
494 for<'de> O: Deserialize<'de>,
495 T: std::io::Read + std::io::Seek,
496 {
497 file_mapping.get(&file_name).map(|i| {
498 self.read_objs(
499 archive.by_index(*i).map_err(|e| Error::NamedFileIO {
500 file_name: file_name.to_owned(),
501 source: Box::new(e),
502 })?,
503 file_name,
504 )
505 })
506 }
507}