summaryrefslogtreecommitdiff
path: root/angelsharkd/src
diff options
context:
space:
mode:
Diffstat (limited to 'angelsharkd/src')
-rw-r--r--angelsharkd/src/routes/extensions/mod.rs9
-rw-r--r--angelsharkd/src/routes/extensions/simple_search/README.md3
-rw-r--r--angelsharkd/src/routes/extensions/simple_search/mod.rs66
-rw-r--r--angelsharkd/src/routes/extensions/simple_search/types.rs (renamed from angelsharkd/src/routes/extensions/simple_search.rs)97
4 files changed, 109 insertions, 66 deletions
diff --git a/angelsharkd/src/routes/extensions/mod.rs b/angelsharkd/src/routes/extensions/mod.rs
index adcea3f..7f217d6 100644
--- a/angelsharkd/src/routes/extensions/mod.rs
+++ b/angelsharkd/src/routes/extensions/mod.rs
@@ -6,18 +6,22 @@ mod simple_deprov;
#[cfg(feature = "simple_search")]
mod simple_search;
+/// The extension filter; consists of all compiled optional Angelshark extension
+/// filters combined under `/extensions`.
pub fn filter(config: &Config) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
// Note: this next line deals with the common edge case of having no
// extensions loaded with feature flags. It ensures that the the type
// checking is right when the return `.and()` is called below.
let filters = default().or(default());
+ // Block to enable simple_search extension feature. Instantiates a
+ // searchable haystack and configures filters to handle search requests.
#[cfg(feature = "simple_search")]
let haystack = simple_search::Haystack::new(config.runner.clone());
#[cfg(feature = "simple_search")]
let filters = filters
- .or(simple_search::search(haystack.clone()))
- .or(simple_search::refresh(haystack));
+ .or(simple_search::search_filter(haystack.clone()))
+ .or(simple_search::refresh_filter(haystack));
#[cfg(feature = "simple_deprov")]
let filters = filters.or(simple_deprov::filter());
@@ -25,6 +29,7 @@ pub fn filter(config: &Config) -> impl Filter<Extract = impl Reply, Error = Reje
path("extensions").and(filters)
}
+/// The default, informational extension route.
fn default() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::path::end().map(|| "Angelshark extension route index. Enable extensions with feature switches and access them at `/extensions/<feature>`.")
}
diff --git a/angelsharkd/src/routes/extensions/simple_search/README.md b/angelsharkd/src/routes/extensions/simple_search/README.md
new file mode 100644
index 0000000..527f686
--- /dev/null
+++ b/angelsharkd/src/routes/extensions/simple_search/README.md
@@ -0,0 +1,3 @@
+# Angelshark Simple Extension Search
+
+This extension implements very simple extension searching for end users. It allows for multi-keyword searches on a pre-downloaded collection of CM extension-type and station data. This data can be refreshed on-demand or periodically. Searches always return immediately, even when no data has been downloaded yet.
diff --git a/angelsharkd/src/routes/extensions/simple_search/mod.rs b/angelsharkd/src/routes/extensions/simple_search/mod.rs
new file mode 100644
index 0000000..5b5dc8e
--- /dev/null
+++ b/angelsharkd/src/routes/extensions/simple_search/mod.rs
@@ -0,0 +1,66 @@
+use log::{error, info};
+use std::convert::Infallible;
+pub use types::Haystack;
+use types::*;
+use warp::{
+ body::{content_length_limit, json},
+ get,
+ hyper::{header, StatusCode},
+ path, post,
+ reply::{self, with},
+ Filter, Rejection, Reply,
+};
+
+mod types;
+
+/// Returns a warp filter to handle HTTP POSTs for searching the haystack.
+pub fn search_filter(
+ haystack: Haystack,
+) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
+ path("search")
+ .and(post())
+ .and(content_length_limit(1024 * 16))
+ .and(json())
+ .and_then(move |terms: Needles| search(haystack.to_owned(), terms))
+ .with(with::header(header::PRAGMA, "no-cache"))
+ .with(with::header(header::CACHE_CONTROL, "no-store, max-age=0"))
+ .with(with::header(header::X_FRAME_OPTIONS, "DENY"))
+}
+
+/// Returns a warp filter to handle HTTP GETs for refreshing the haystack.
+pub fn refresh_filter(
+ haystack: Haystack,
+) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
+ path!("search" / "refresh")
+ .and(get())
+ .and_then(move || refresh(haystack.to_owned()))
+}
+
+/// Runs the search request to find all needles in the haystack and converts the
+/// results into a reply.
+async fn search(haystack: Haystack, needles: Needles) -> Result<impl Reply, Infallible> {
+ // Ok(haystack.search(Vec::new())?)
+ // if let Ok(matches = haystack.search(needle);
+ match haystack.search(needles) {
+ Ok(matches) => Ok(reply::with_status(reply::json(&matches), StatusCode::OK)),
+ Err(e) => Ok(reply::with_status(
+ reply::json(&e.to_string()),
+ StatusCode::INTERNAL_SERVER_ERROR,
+ )),
+ }
+}
+
+/// Immediately returns. Spawns an asynchronous task to complete the haystack
+/// refresh in the background.
+async fn refresh(haystack: Haystack) -> Result<impl Reply, Infallible> {
+ // Run refresh as a background task and immediately return.
+ tokio::spawn(async move {
+ if let Err(e) = haystack.refresh() {
+ error!("{}", e.to_string()); // TODO: use logger
+ } else {
+ info!("Search haystack refreshed.");
+ }
+ });
+
+ Ok("Refresh scheduled")
+}
diff --git a/angelsharkd/src/routes/extensions/simple_search.rs b/angelsharkd/src/routes/extensions/simple_search/types.rs
index b728c1e..184b1a6 100644
--- a/angelsharkd/src/routes/extensions/simple_search.rs
+++ b/angelsharkd/src/routes/extensions/simple_search/types.rs
@@ -1,69 +1,25 @@
use anyhow::{anyhow, Context, Error};
use libangelshark::{AcmRunner, Message, ParallelIterator};
-use log::{error, info};
+use log::error;
use std::{
collections::HashMap,
- convert::Infallible,
+ env,
sync::{Arc, Mutex},
};
-use warp::{
- body::{content_length_limit, json},
- get,
- hyper::{header, StatusCode},
- path, post,
- reply::{self, with},
- Filter, Rejection, Reply,
-};
+
+const ANGELSHARKD_EXT_SEARCH_ACMS: &str = "ANGELSHARKD_EXT_SEARCH_ACMS";
+const OSSI_STAT_NUMBER_FIELD: &str = "8005ff00";
+const OSSI_STAT_ROOM_FIELD: &str = "0031ff00";
+const OSSI_LIST_STAT_CMD: &str = "list station";
+const OSSI_LIST_EXT_CMD: &str = "list extension-type";
/// Collection of search terms
-type Needles = Vec<String>;
+pub type Needles = Vec<String>;
/// Collection of ACM extension types with ROOMs (if applicable) and ACM names
type HaystackEntries = Vec<Vec<String>>;
-pub fn search(haystack: Haystack) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
- path("search")
- .and(post())
- .and(content_length_limit(1024 * 16))
- .and(json())
- .and_then(move |terms: Needles| handle_search(haystack.to_owned(), terms))
- .with(with::header(header::PRAGMA, "no-cache"))
- .with(with::header(header::CACHE_CONTROL, "no-store, max-age=0"))
- .with(with::header(header::X_FRAME_OPTIONS, "DENY"))
-}
-
-pub fn refresh(haystack: Haystack) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
- path!("search" / "refresh")
- .and(get())
- .and_then(move || handle_refresh(haystack.to_owned()))
-}
-
-async fn handle_search(haystack: Haystack, needle: Needles) -> Result<impl Reply, Infallible> {
- // Ok(haystack.search(Vec::new())?)
- // if let Ok(matches = haystack.search(needle);
- match haystack.search(needle) {
- Ok(matches) => Ok(reply::with_status(reply::json(&matches), StatusCode::OK)),
- Err(e) => Ok(reply::with_status(
- reply::json(&e.to_string()),
- StatusCode::INTERNAL_SERVER_ERROR,
- )),
- }
-}
-
-async fn handle_refresh(haystack: Haystack) -> Result<impl Reply, Infallible> {
- // Run refresh as a background task and immediately return.
- tokio::spawn(async move {
- if let Err(e) = haystack.refresh() {
- error!("{}", e.to_string()); // TODO: use logger
- } else {
- info!("Search haystack refreshed.");
- }
- });
-
- Ok("Refresh scheduled")
-}
-
-/// A lazy-loaded, asynchronously-refreshed exension-type haystack cache.
+/// Represents a searchable, refreshable collection of ACM extension data.
#[derive(Clone)]
pub struct Haystack {
entries: Arc<Mutex<HaystackEntries>>,
@@ -78,6 +34,7 @@ impl Haystack {
}
}
+ /// Searches for haystack entries that contain all given needles and returns them.
pub fn search(&self, needles: Needles) -> Result<HaystackEntries, Error> {
let needles: Vec<_> = needles.iter().map(|n| n.to_lowercase()).collect();
@@ -100,20 +57,32 @@ impl Haystack {
Ok(matches)
}
+ /// Refreshes the haystack data by running relevant commands on a runner,
+ /// parsing the results, and updating the entries field with the fresh data.
+ /// TODO: Do we want simultaneous refreshes to be possible?
pub fn refresh(&self) -> Result<(), Error> {
- let mut runner = self.runner.to_owned(); // TODO: This alone allows multiple refreshers to be run at once. Do we want that?
+ let mut runner = self.runner.to_owned();
// Queue jobs in ACM runner
- for acm in &[
- "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11",
- ] {
+ let configured_acms = env::var(ANGELSHARKD_EXT_SEARCH_ACMS).with_context(|| {
+ format!(
+ "{} var missing. Cannot refresh haystack.",
+ ANGELSHARKD_EXT_SEARCH_ACMS
+ )
+ })?;
+
+ // Generate jobs and queue on runner.
+ for acm in configured_acms.split_whitespace() {
runner
- .queue_input(acm, &Message::new("list extension-type"))
+ .queue_input(acm, &Message::new(OSSI_LIST_EXT_CMD))
.queue_input(
acm,
&Message {
- command: String::from("list station"),
- fields: Some(vec![String::from("8005ff00"), String::from("0031ff00")]),
+ command: String::from(OSSI_LIST_STAT_CMD),
+ fields: Some(vec![
+ String::from(OSSI_STAT_NUMBER_FIELD),
+ String::from(OSSI_STAT_ROOM_FIELD),
+ ]),
datas: None,
error: None,
},
@@ -149,7 +118,7 @@ impl Haystack {
.map(|(_, messages)| {
messages
.iter()
- .filter(|message| message.command == "list station")
+ .filter(|message| message.command == OSSI_LIST_STAT_CMD)
.filter_map(|message| message.datas.to_owned())
.flatten()
.filter_map(|stat| Some((stat.get(0)?.to_owned(), stat.get(1)?.to_owned())))
@@ -166,7 +135,7 @@ impl Haystack {
messages
.into_iter()
- .filter(|message| message.command == "list extension-type")
+ .filter(|message| message.command == OSSI_LIST_EXT_CMD)
.filter_map(|message| message.datas)
.flatten()
.map(move |mut extension| {
@@ -185,7 +154,7 @@ impl Haystack {
.flatten()
.collect();
- // Propogate the new data to the shared haystack entries
+ // Overwrite shared haystack entries with new data.
let mut lock = self
.entries
.lock()