Verified Commit 93b42e32 authored by Ole Martin Ruud's avatar Ole Martin Ruud
Browse files

Improve fetching of options

parent e1aa10f3
......@@ -9,18 +9,16 @@ use unhtml::scraper::{Html, Selector};
use unhtml::FromHtml;
use crate::auth::*;
use crate::config::Config;
use crate::config::{Config, QUERY_OPTIONS_FILENAME};
use crate::error::*;
use crate::query::*;
use crate::QUERY_OPTIONS_FILENAME;
pub struct App<'p> {
pub struct App {
pub config: Config,
pub client: Client,
pub project_dirs: Option<&'p ProjectDirs>,
}
impl App<'_> {
impl App {
fn request(&self, req: Request) -> Result<Response> {
trace!("Sending request:\n{:#?}", req);
let res = self.client.execute(req).context(RequestError)?;
......@@ -119,116 +117,125 @@ impl App<'_> {
Ok(content)
}
pub fn fetch(&self, fresh: bool) -> Result<()> {
let project_dirs = self.project_dirs.context(MsgError {
msg: "Project directories are required to fetch query options",
})?;
fn fetch(&self, fresh: bool) -> Result<OrderFormOpts> {
// Cache is "dirty" if we fetch new options from the network.
let mut dirty = false;
let already_exists = File::open(
project_dirs
.data_dir()
.join(QUERY_OPTIONS_FILENAME)
.with_extension("yml"),
)
.is_ok();
ensure!(
!already_exists || fresh,
MsgError {
msg: concat!(
"Query options already exist. ",
"Run with `--fresh` if you want to overwrite existing cache."
),
}
);
let mut network_fetch = || {
dirty = true;
info!("Logging in to fetch query options");
let content = self.authenticate()?;
info!(
"Creating project directory: {}",
project_dirs.data_dir().display()
);
std::fs::create_dir_all(project_dirs.data_dir()).context(DirectoryError {
action: "create",
path: project_dirs.data_dir(),
})?;
info!("Fetching fresh options from service");
let content = self.authenticate()?;
info!("Parsing query options");
let order_form_config = OrderFormOpts::from_html(&content).context(ParseHtmlError {
name: "OrderFormOpts",
})?;
info!("Parsing options");
OrderFormOpts::from_html(&content).context(ParseHtmlError {
name: "OrderFormOpts",
})
};
let file_name = project_dirs
.data_dir()
.join(QUERY_OPTIONS_FILENAME)
.with_extension("yml");
let mut file = File::create(&file_name).context(FileError {
action: "create",
path: &file_name,
})?;
let options = if fresh {
network_fetch()?
} else {
self.config
.project_dirs
.as_ref()
.context(MsgError {
msg: "Project directories are required to cache query options",
})
.and_then(|ref project_dirs| {
let file_name = project_dirs
.cache_dir()
.join(QUERY_OPTIONS_FILENAME)
.with_extension("yml");
File::open(&file_name)
.context(FileError {
action: "open",
path: &file_name,
})
.and_then(|mut file| {
serde_yaml::from_reader(file).context(YamlError {
name: "OrderFormOpts",
})
})
.map_err(|e| {
warn!("Unable to open local cache of query options: {}", e);
e
})
})
.or_else(|e| network_fetch())?
};
info!("Writing query options to file: {}", file_name.display());
serde_yaml::to_writer(&mut file, &order_form_config).context(YamlError {
name: "query options",
})?;
if dirty {
// Try to cache results if cache is "dirty"
if let Err(e) = self
.config
.project_dirs
.as_ref()
.context(MsgError {
msg: "Project directories are required to cache query options",
})
.and_then(|ref project_dirs| {
std::fs::create_dir_all(project_dirs.cache_dir()).context(DirectoryError {
action: "create",
path: project_dirs.cache_dir(),
})?;
let file_name = project_dirs
.cache_dir()
.join(QUERY_OPTIONS_FILENAME)
.with_extension("yml");
let mut file = File::create(&file_name).context(FileError {
action: "create",
path: &file_name,
})?;
info!("Writing options to cache file '{}'", file_name.display());
serde_yaml::to_writer(&mut file, &options)
.context(YamlError { name: "options" })
})
{
warn!("Unable to cache query options: {}", e);
}
}
Ok(())
Ok(options)
}
/// Query cached results and display all options
pub fn query_options(&self) -> Result<()> {
if let Some(ref project_dirs) = self.project_dirs {
let file_name = project_dirs
.data_dir()
.join(QUERY_OPTIONS_FILENAME)
.with_extension("yml");
info!("Opening query options file: {}", file_name.display());
let mut file = File::open(&file_name).context(FileError {
action: "open",
path: &file_name,
})?;
let query_options: OrderFormOpts =
serde_yaml::from_reader(file).context(YamlError {
name: "OrderFormOpts",
})?;
eprintln!("AREAS:");
query_options
.areas
.iter()
.for_each(|area| eprintln!(" - {} ({})", area.name, area.id));
eprintln!("#############");
eprintln!("BUILDINGS:");
query_options.buildings.iter().for_each(|building| {
eprintln!(
" - {} ({}) [{}]",
building.name,
building.id,
building.area.as_ref().unwrap_or(&"-".into()),
)
});
eprintln!("#############");
eprintln!("ROOMTYPES:");
query_options
.roomtypes
.iter()
.for_each(|roomtype| eprintln!(" - {} ({})", roomtype.name, roomtype.id));
eprintln!("#############");
eprintln!("EQUIPMENT:");
query_options
.equipments
.iter()
.for_each(|equipment| eprintln!(" - {} ({})", equipment.name, equipment.id));
eprintln!("#############");
pub fn options(&self, fresh: bool) -> Result<()> {
let options = self.fetch(fresh)?;
eprintln!("AREAS:");
options
.areas
.iter()
.for_each(|area| eprintln!(" - {} ({})", area.name, area.id));
eprintln!("#############");
eprintln!("BUILDINGS:");
options.buildings.iter().for_each(|building| {
eprintln!(
" - {} ({}) [{}]",
building.name,
building.id,
building.area.as_ref().unwrap_or(&"-".into()),
)
});
eprintln!("#############");
eprintln!("ROOMTYPES:");
options
.roomtypes
.iter()
.for_each(|roomtype| eprintln!(" - {} ({})", roomtype.name, roomtype.id));
eprintln!("#############");
eprintln!("EQUIPMENT:");
options
.equipments
.iter()
.for_each(|equipment| eprintln!(" - {} ({})", equipment.name, equipment.id));
eprintln!("#############");
Ok(())
} else {
MsgError {
msg: "Project directories are required to query options",
}
.fail()
}
Ok(())
}
/// Query for avaliable rooms
......
......@@ -7,6 +7,8 @@ use std::fs::File;
use crate::error::*;
use crate::Opts;
pub const QUERY_OPTIONS_FILENAME: &str = "query-options";
#[derive(Deserialize, Serialize, Debug)]
struct ConfigFile {
username: Option<String>,
......@@ -20,16 +22,18 @@ pub struct Config {
pub username: String,
pub password: String,
pub base_url: Url,
pub project_dirs: Option<ProjectDirs>,
}
impl Config {
pub fn try_new(opts: &Opts, project_dirs: Option<&ProjectDirs>) -> Result<Self> {
pub fn try_new(opts: &Opts, project_dirs: Option<ProjectDirs>) -> Result<Self> {
match (opts.username.as_ref(), opts.password) {
(Some(username), true) => {
Ok(Config {
username: username.clone(),
password: "".into(), // TODO prompt for password
base_url: opts.base_url.clone(),
project_dirs,
})
}
_ => {
......@@ -67,6 +71,7 @@ impl Config {
})?
},
base_url: opts.base_url.clone(),
project_dirs: Some(project_dirs),
})
}
}
......
......@@ -23,8 +23,6 @@ use config::Config;
use error::*;
use query::*;
const QUERY_OPTIONS_FILENAME: &str = "query-options";
#[derive(StructOpt)]
#[structopt(author, about)]
pub struct Opts {
......@@ -67,13 +65,15 @@ pub struct Opts {
enum Command {
#[structopt(about = "Authenticate to service to ensure it works")]
Authenticate,
#[structopt(about = "Fetch query options and cache them for later use")]
Fetch {
#[structopt(short, long, help = "Fetch query options even if they already exist")]
#[structopt(about = "Display options regarding buildings, rooms, etc.")]
Options {
#[structopt(
long,
short,
help = "Ensures that options are fetched regardless of existing cache"
)]
fresh: bool,
},
#[structopt(about = "Display options regarding buildings, rooms, etc.")]
QueryOptions,
#[structopt(about = "Query the service for avaliable rooms")]
Query {
#[structopt(short, long, help = "Day to order room (DD.MM.YYYY)")]
......@@ -134,19 +134,14 @@ fn run() -> Result<()> {
warn!("Unable to create project directories");
}
let config = Config::try_new(&opts, project_dirs.as_ref())?;
let config = Config::try_new(&opts, project_dirs)?;
let mut app = App {
config,
client,
project_dirs: project_dirs.as_ref(),
};
let mut app = App { config, client };
use crate::Command::*;
match opts.command {
Authenticate => app.authenticate().map(|_| ()),
Fetch { fresh } => app.fetch(fresh),
QueryOptions => app.query_options(),
Options { fresh } => app.options(fresh),
Query {
ref day,
ref hour,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment