simple web server

This commit is contained in:
xenofem 2022-04-06 18:46:17 -04:00
parent 50a25c494a
commit 921b62ed97
5 changed files with 624 additions and 40 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
data.pdf

473
Cargo.lock generated
View file

@ -2,6 +2,214 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "actix-codec"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-sink",
"log",
"memchr",
"pin-project-lite",
"tokio",
"tokio-util 0.7.1",
]
[[package]]
name = "actix-files"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d81bde9a79336aa51ebed236e91fc1a0528ff67cfdf4f68ca4c61ede9fd26fb5"
dependencies = [
"actix-http",
"actix-service",
"actix-utils",
"actix-web",
"askama_escape",
"bitflags",
"bytes",
"derive_more",
"futures-core",
"http-range",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
]
[[package]]
name = "actix-http"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a"
dependencies = [
"actix-codec",
"actix-rt",
"actix-service",
"actix-utils",
"ahash",
"base64",
"bitflags",
"brotli",
"bytes",
"bytestring",
"derive_more",
"encoding_rs",
"flate2",
"futures-core",
"h2",
"http",
"httparse",
"httpdate",
"itoa 1.0.1",
"language-tags",
"local-channel",
"log",
"mime",
"percent-encoding",
"pin-project-lite",
"rand 0.8.5",
"sha-1",
"smallvec",
"zstd",
]
[[package]]
name = "actix-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "actix-router"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80"
dependencies = [
"bytestring",
"firestorm",
"http",
"log",
"regex",
"serde",
]
[[package]]
name = "actix-rt"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000"
dependencies = [
"futures-core",
"tokio",
]
[[package]]
name = "actix-server"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824"
dependencies = [
"actix-rt",
"actix-service",
"actix-utils",
"futures-core",
"futures-util",
"mio",
"num_cpus",
"socket2",
"tokio",
"tracing",
]
[[package]]
name = "actix-service"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a"
dependencies = [
"futures-core",
"paste",
"pin-project-lite",
]
[[package]]
name = "actix-utils"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94"
dependencies = [
"local-waker",
"pin-project-lite",
]
[[package]]
name = "actix-web"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4e5ebffd51d50df56a3ae0de0e59487340ca456f05dd0b90c0a7a6dd6a74d31"
dependencies = [
"actix-codec",
"actix-http",
"actix-macros",
"actix-router",
"actix-rt",
"actix-server",
"actix-service",
"actix-utils",
"actix-web-codegen",
"ahash",
"bytes",
"bytestring",
"cfg-if",
"cookie",
"derive_more",
"encoding_rs",
"futures-core",
"futures-util",
"itoa 1.0.1",
"language-tags",
"log",
"mime",
"once_cell",
"pin-project-lite",
"regex",
"serde",
"serde_json",
"serde_urlencoded",
"smallvec",
"socket2",
"time 0.3.9",
"url",
]
[[package]]
name = "actix-web-codegen"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc"
dependencies = [
"actix-router",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler32"
version = "1.2.0"
@ -39,6 +247,17 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.6",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
@ -48,6 +267,27 @@ dependencies = [
"memchr",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3"
[[package]]
name = "alloc-stdlib"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -75,6 +315,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
dependencies = [
"generic-array",
]
[[package]]
name = "block-modes"
version = "0.7.0"
@ -91,6 +340,27 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "brotli"
version = "3.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
name = "bumpalo"
version = "3.9.1"
@ -109,11 +379,23 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "bytestring"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d"
dependencies = [
"bytes",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
@ -149,6 +431,17 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "cookie"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
dependencies = [
"percent-encoding",
"time 0.3.9",
"version_check",
]
[[package]]
name = "cpufeatures"
version = "0.2.2"
@ -158,6 +451,25 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "cssparser"
version = "0.27.2"
@ -216,6 +528,16 @@ dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
dependencies = [
"block-buffer 0.10.2",
"crypto-common",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
@ -278,6 +600,24 @@ dependencies = [
"syn",
]
[[package]]
name = "firestorm"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d6188b8804df28032815ea256b6955c9625c24da7525f387a7af02fbb8f01"
[[package]]
name = "flate2"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
dependencies = [
"cfg-if",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -519,6 +859,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.6.0"
@ -625,6 +971,15 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]]
name = "jpeg-decoder"
version = "0.1.22"
@ -640,6 +995,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "language-tags"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -652,6 +1013,24 @@ version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
[[package]]
name = "local-channel"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6246c68cf195087205a0512559c97e15eaf95198bf0e206d662092cdcb03fe9f"
dependencies = [
"futures-core",
"futures-sink",
"futures-util",
"local-waker",
]
[[package]]
name = "local-waker"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902eb695eb0591864543cbfbf6d742510642a605a61fc5e97fe6ceb5a30ac4fb"
[[package]]
name = "lock_api"
version = "0.4.7"
@ -715,6 +1094,26 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
"adler",
"autocfg",
]
[[package]]
name = "mio"
version = "0.8.2"
@ -838,6 +1237,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "paste"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
[[package]]
name = "pdf"
version = "0.7.2"
@ -972,6 +1377,8 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
name = "poop-graph"
version = "0.1.0"
dependencies = [
"actix-files",
"actix-web",
"bytes",
"futures",
"lazy_static",
@ -982,6 +1389,7 @@ dependencies = [
"thiserror",
"time 0.3.9",
"tokio",
"tokio-stream",
"tokio-util 0.7.1",
"url",
]
@ -1316,16 +1724,27 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "sha-1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.3",
]
[[package]]
name = "sha2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer",
"block-buffer 0.9.0",
"cfg-if",
"cpufeatures",
"digest",
"digest 0.9.0",
"opaque-debug",
]
@ -1569,6 +1988,17 @@ dependencies = [
"webpki",
]
[[package]]
name = "tokio-stream"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.6.9"
@ -1610,6 +2040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@ -1647,6 +2078,15 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
@ -1906,3 +2346,32 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"winapi",
]
[[package]]
name = "zstd"
version = "0.10.0+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "4.1.4+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "1.6.3+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8"
dependencies = [
"cc",
"libc",
]

View file

@ -7,6 +7,8 @@ authors = ["xenofem <xenofem@xeno.science>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-files = "0.6.0"
actix-web = "4.0.1"
bytes = "1.1"
futures = "0.3"
lazy_static = "1.4"
@ -17,5 +19,6 @@ scraper = "0.12"
thiserror = "1"
time = { version = "0.3.9", features = ["formatting", "macros", "parsing"] }
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1.8"
tokio-util = { version = "0.7", features = ["codec"] }
url = "2.2.2"

View file

@ -1,6 +1,4 @@
use std::collections::HashMap;
use std::fmt::Write;
use std::rc::Rc;
use std::{collections::HashMap, fmt::Write, sync::Arc};
use lazy_static::lazy_static;
use pdf::{backend::Backend, content::Operation, primitive::Primitive};
@ -21,15 +19,21 @@ const DATE_DISPLAY_FORMAT: &[time::format_description::FormatItem] =
time::macros::format_description!("[year]-[month]-[day]");
pub struct DataSet {
pub columns: Vec<Rc<String>>,
pub columns: Vec<Arc<String>>,
pub rows: Vec<DataPoint>,
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("PDF contained no data rows")]
NoData,
}
impl DataSet {
pub fn extract<B: Backend>(doc: &pdf::file::File<B>) -> Option<Self> {
pub fn extract<B: Backend>(doc: &pdf::file::File<B>) -> Result<Self, Error> {
let mut doc_iter = DocumentIterator::new(doc).peekable();
let mut columns: Vec<(Rc<String>, f32)> = Vec::new();
let mut columns: Vec<(Arc<String>, f32)> = Vec::new();
let mut rows: Vec<DataPoint> = Vec::new();
let (mut current_datapoint, mut current_y) = loop {
@ -39,7 +43,7 @@ impl DataSet {
let column_x = text.x;
while let Some(more) = doc_iter.peek() {
if is_new_column_header(&more.text) || DATE_REGEX.is_match(&more.text) {
columns.push((Rc::new(column_name), column_x));
columns.push((Arc::new(column_name), column_x));
break;
}
column_name += " ";
@ -53,7 +57,7 @@ impl DataSet {
);
}
} else {
return None;
return Err(Error::NoData);
}
};
@ -79,40 +83,44 @@ impl DataSet {
}
}
Some(Self {
Ok(Self {
columns: columns.into_iter().map(|(column, _)| column).collect(),
rows,
})
}
pub fn csv_header(&self) -> Result<String, std::fmt::Error> {
let mut header = String::from("Date");
for column in self.columns.iter() {
write!(&mut header, ",{}", column)?;
}
Ok(header)
}
pub fn csv_row(&self, datapoint: &DataPoint) -> Result<String, std::fmt::Error> {
let mut csv_row = datapoint
.date
.format(DATE_DISPLAY_FORMAT)
.expect("Failed to format date!");
for column in self.columns.iter() {
if let Some(val) = datapoint.values.get(column) {
write!(&mut csv_row, ",{}", val)?;
} else {
write!(&mut csv_row, ",")?;
}
}
Ok(csv_row)
}
pub fn csv_rows(&self) -> impl Iterator<Item = Result<String, std::fmt::Error>> + '_ {
std::iter::once_with(|| {
let mut header = String::from("Date");
for column in self.columns.iter() {
write!(&mut header, ",{}", column)?;
}
Ok(header)
})
.chain(self.rows.iter().map(|datapoint| {
let mut csv_row = datapoint
.date
.format(DATE_DISPLAY_FORMAT)
.expect("Failed to format date!");
for column in self.columns.iter() {
if let Some(val) = datapoint.values.get(column) {
write!(&mut csv_row, ",{}", val)?;
} else {
write!(&mut csv_row, ",")?;
}
}
Ok(csv_row)
}))
std::iter::once_with(|| self.csv_header())
.chain(self.rows.iter().map(|datapoint| self.csv_row(datapoint)))
}
}
pub struct DataPoint {
pub date: Date,
pub values: HashMap<Rc<String>, u32>,
pub values: HashMap<Arc<String>, u32>,
}
impl DataPoint {

View file

@ -1,15 +1,118 @@
use std::{sync::Arc, time::Duration};
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use lazy_static::lazy_static;
use tokio::sync::RwLock;
mod extract;
mod fetch;
use extract::DataSet;
use fetch::PdfFetcher;
#[tokio::main]
async fn main() {
let mut fetcher = PdfFetcher::new().expect("Failed to initialize PDF fetcher");
let doc = fetcher.fetch().await.expect("Failed to fetch PDF");
let dataset = DataSet::extract(&doc).expect("Failed to extract dataset");
for row in dataset.csv_rows() {
println!("{}", row.unwrap());
lazy_static! {
static ref UPDATE_INTERVAL: Duration = Duration::from_secs(3600);
}
struct AppState {
dataset: RwLock<Arc<DataSet>>,
}
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("Failed to fetch PDF")]
Fetch(#[from] fetch::Error),
#[error("Failed to extract data from PDF")]
Extract(#[from] extract::Error),
}
async fn load_data(fetcher: &mut PdfFetcher) -> Result<DataSet, Error> {
Ok(DataSet::extract(&fetcher.fetch().await?)?)
}
async fn try_update(state: &AppState, fetcher: &mut PdfFetcher) -> Result<(), Error> {
*state.dataset.write().await = Arc::new(load_data(fetcher).await?);
Ok(())
}
async fn start_updater() -> Result<web::Data<AppState>, Error> {
let mut fetcher = PdfFetcher::new()?;
let state = web::Data::new(AppState {
dataset: RwLock::new(Arc::new(load_data(&mut fetcher).await?)),
});
let state_copy = state.clone();
std::thread::spawn(move || {
actix_web::rt::System::new().block_on(async {
loop {
if let Err(e) = try_update(&state_copy, &mut fetcher).await {
eprintln!("Error updating data: {:#?}", e);
}
actix_web::rt::time::sleep(*UPDATE_INTERVAL).await;
}
});
});
Ok(state)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let state = start_updater().await.expect("Failed to initialize state");
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.service(csv)
.service(actix_files::Files::new("/", "./static/").index_file("index.html"))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
struct DataIterator {
dataset: Arc<DataSet>,
index: Option<usize>,
}
impl DataIterator {
fn new(dataset: Arc<DataSet>) -> Self {
Self {
dataset,
index: None,
}
}
}
impl Iterator for DataIterator {
type Item = Result<String, std::fmt::Error>;
fn next(&mut self) -> Option<Self::Item> {
match self.index {
None => {
self.index = Some(0);
Some(self.dataset.csv_header().map(|s| s + "\n"))
}
Some(i) => {
if let Some(row) = self.dataset.rows.get(i) {
self.index = Some(i + 1);
Some(self.dataset.csv_row(row).map(|s| s + "\n"))
} else {
None
}
}
}
}
}
#[get("/data.csv")]
async fn csv(data: web::Data<AppState>) -> impl Responder {
let dataset = { data.dataset.read().await.clone() };
let rows =
tokio_stream::iter(DataIterator::new(dataset).map(|item| item.map(bytes::Bytes::from)));
HttpResponse::Ok()
.content_type("text/csv; charset=utf-8")
.body(actix_web::body::BodyStream::new(rows))
}