Refresh the GTFS bundle every time the program starts.
This commit is contained in:
+10
-9
@@ -1,15 +1,13 @@
|
||||
mod arrival;
|
||||
mod loader;
|
||||
mod utils;
|
||||
mod refresher;
|
||||
pub mod structs;
|
||||
use chrono::{DateTime, Local, Timelike};
|
||||
use log::{debug};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet}, fs::File, io::Error
|
||||
};
|
||||
use log::{debug, trace};
|
||||
use std::{collections::{HashMap, HashSet}, fs::File };
|
||||
use gtfs_structures::{Exception, RawTrip};
|
||||
|
||||
use crate::gtfs::{loader::load_gtfs, structs::{Arrival, Gtfs, Preferences}};
|
||||
use crate::gtfs::{loader::load_gtfs, structs::{Arrival, Gtfs, Preferences, Error}};
|
||||
|
||||
|
||||
impl Gtfs {
|
||||
@@ -75,7 +73,7 @@ impl Gtfs {
|
||||
trip: &trip,
|
||||
departure_time: stop_timestamp.into()
|
||||
};
|
||||
debug!("{:#?}: Arrival to {:#?} for trip ID {:#?}.",
|
||||
trace!("{:#?}: Arrival to {:#?} for trip ID {:#?}.",
|
||||
format!("{:02}:{:02}", (arrival.departure_time/3600) as u32, ((arrival.departure_time / 60) % 60) as u32),
|
||||
arrival.trip.trip_headsign.as_ref().unwrap(),
|
||||
arrival.trip.id);
|
||||
@@ -91,9 +89,12 @@ impl Gtfs {
|
||||
|
||||
|
||||
/// Load a GTFS structure from a zip file
|
||||
pub fn load(src_file: &str, prefs: &Preferences) -> Result<Gtfs, Error> {
|
||||
pub fn load(prefs: &Preferences) -> Result<Gtfs, Error> {
|
||||
|
||||
_ = refresher::refresh(prefs);
|
||||
|
||||
// Open zip file
|
||||
let zip_file = File::open(src_file)?;
|
||||
let zip_file = File::open(prefs.gtfs_file_path()?)?;
|
||||
let mut zip_reader = zip::ZipArchive::new(zip_file)?;
|
||||
|
||||
let mut gtfs: Gtfs = Gtfs {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
use log::{debug, error, info};
|
||||
use time_format::format_common_utc;
|
||||
use std::{fs::{self}, time::{Duration, SystemTime, UNIX_EPOCH}};
|
||||
use crate::gtfs::structs::{Error, Preferences};
|
||||
use reqwest::{StatusCode, blocking::Client};
|
||||
|
||||
|
||||
pub(crate) fn refresh(prefs: &Preferences) -> Result<(), Error> {
|
||||
|
||||
let mut modified_timestamp: SystemTime = SystemTime::UNIX_EPOCH;
|
||||
let gtfs_file_name = prefs.gtfs_file_path()?;
|
||||
|
||||
// Obtain the GTFS zip's creation time.
|
||||
let metadata = fs::metadata(>fs_file_name);
|
||||
if metadata.is_ok() {
|
||||
modified_timestamp = metadata?.modified()?;
|
||||
}
|
||||
|
||||
let modified_seconds = modified_timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64;
|
||||
let modified_header = format_common_utc(modified_seconds, time_format::DateFormat::HTTP).unwrap();
|
||||
|
||||
debug!("Using if-modified-since: {}", modified_header);
|
||||
|
||||
// request updates from the server if-modified-since last time
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.build()?;
|
||||
|
||||
let mut response = client
|
||||
.get(&prefs.gtfs_url)
|
||||
.header(reqwest::header::IF_MODIFIED_SINCE, modified_header)
|
||||
.send()
|
||||
.unwrap();
|
||||
|
||||
if response.status() == StatusCode::NOT_MODIFIED {
|
||||
debug!("GTFS data is still up-to-date");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if response.status() == StatusCode::OK {
|
||||
info!("Refreshing GTFS data");
|
||||
// Stream the response data into a temp file and swap them
|
||||
|
||||
let tmp_full_file_name = format!("{}.new", >fs_file_name);
|
||||
let mut tmp_file = fs::File::create(&tmp_full_file_name)?;
|
||||
|
||||
_ = response.copy_to(&mut tmp_file)?;
|
||||
drop(tmp_file);
|
||||
|
||||
if fs::exists(>fs_file_name)? {
|
||||
_ = fs::remove_file(>fs_file_name);
|
||||
}
|
||||
_ = fs::rename(&tmp_full_file_name, >fs_file_name);
|
||||
info!("GTFS data refreshed");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let errmsg = format!("GET on {} returned result code {}", prefs.gtfs_url, response.status());
|
||||
return Err(Error { message: errmsg });
|
||||
}
|
||||
@@ -1,6 +1,33 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use gtfs_structures::{Agency, Calendar, CalendarDate, RawStopTime, RawTrip, Route, Stop};
|
||||
use log::error;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use zip::result::ZipError;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub(crate) message: String,
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
return Error { message: value.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(value: reqwest::Error) -> Self {
|
||||
return Error { message: value.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ZipError> for Error {
|
||||
fn from(value: ZipError) -> Self {
|
||||
return Error { message: value.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This is to store the preferences for the GTFS(-R) side of the code.
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
@@ -27,6 +54,23 @@ pub struct Preferences {
|
||||
pub refresh_seconds: u64,
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
impl Preferences {
|
||||
pub fn gtfs_file_name(&self) -> Result<String, Error> {
|
||||
let file_name_part = self.gtfs_url.split("/").last();
|
||||
if file_name_part.is_none() {
|
||||
error!("The config for gtfs-url is not a valid URL: {}", self.gtfs_url);
|
||||
return Err(Error {message: String::from("Failed to refresh GTFS data")});
|
||||
}
|
||||
return Ok(String::from(file_name_part.unwrap()));
|
||||
}
|
||||
|
||||
pub fn gtfs_file_path(&self) -> Result<String, Error> {
|
||||
return Ok(format!("{}/{}", self.data_folder, self.gtfs_file_name()?));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// The main GTFS struct. This is similar to (but not exactly) gtfs-structures::Gtfs because we don't need everything
|
||||
#[derive(Debug)]
|
||||
|
||||
+5
-6
@@ -3,11 +3,10 @@ mod renderer;
|
||||
mod config;
|
||||
use std::{ops::Add, process, thread::Builder, time::SystemTime};
|
||||
use chrono::{DateTime, Duration, Local, NaiveTime};
|
||||
use log::{Metadata, Record, error, info};
|
||||
use log::{Level, Metadata, Record, error, info};
|
||||
use sdl3::event::Event;
|
||||
use crate::{config::load_config, gtfs::structs::{Arrival, Gtfs}, renderer::structs::{DisplayData, DisplayEntry, Screen}};
|
||||
|
||||
const SRC_FILE: &str = "/home/nahuel/Downloads/GTFS_Realtime.zip";
|
||||
const NUM_ARRIVALS: usize = 4;
|
||||
|
||||
// Custom Event to signal data refresh
|
||||
@@ -59,8 +58,8 @@ fn main() {
|
||||
static MY_LOGGER: MyLogger = MyLogger;
|
||||
struct MyLogger;
|
||||
impl log::Log for MyLogger {
|
||||
fn enabled(&self, _metadata: &Metadata) -> bool {
|
||||
true
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
return metadata.level() < Level::Trace;
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
@@ -92,10 +91,10 @@ fn main() {
|
||||
|
||||
// Init GTFS static info
|
||||
info!("Loading GTFS data...");
|
||||
let res = Gtfs::load(SRC_FILE, >fs_prefs);
|
||||
let res = Gtfs::load(>fs_prefs);
|
||||
|
||||
if let Err(e) = res {
|
||||
error!("Error loading GTFS data: {}", e);
|
||||
error!("Error loading GTFS data: {:#?}", e);
|
||||
process::exit(-1);
|
||||
}
|
||||
let gtfs = res.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user