summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2020-05-28 19:02:00 -0400
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2020-05-28 19:02:00 -0400
commit7e8b253a271b76c1fbda10ca3e9c8f45008f8f55 (patch)
tree3098c10239eb2afdd384a54efb2a43560167b9d5 /src/main.rs
parent4dbb8998f840a771949ed30f9f2b652713edd0f9 (diff)
Refactor.
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs148
1 files changed, 79 insertions, 69 deletions
diff --git a/src/main.rs b/src/main.rs
index e2290b9..d53d0d0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -42,8 +42,13 @@ use std::os::unix::fs::symlink;
use std::path::Path;
use std::sync::Mutex;
+/// Maximum number of results returned by any API endpoint.
+const RESULTS_PER_QUERY: i64 = 50;
+
+/// Shorthand for the state that is passed to all handlers.
type SiteState<'a> = State<'a, Mutex<DatabaseConnection>>;
+/// Collection of data necessary to serve images from the tag database.
struct DatabaseConnection {
tb: TagDatabase,
image_dir: String,
@@ -60,52 +65,7 @@ impl DatabaseConnection {
}
}
-#[get("/")]
-fn index(conn: SiteState) -> Template {
- display_posts(conn, String::from(""), None)
-}
-
-#[get("/posts?<tags>&<last>")]
-fn display_posts(conn: SiteState, tags: String, last: Option<i64>) -> Template {
- #[derive(Serialize)]
- struct TagResult {
- name: String,
- count: i64,
- }
-
- #[derive(Serialize)]
- struct Context {
- tags: Vec<TagResult>,
- images: Vec<ImageResult>,
- next_page: String,
- }
-
- let tagstr = &tags.clone();
- let tag_results = {
- let tb = &conn.inner().lock().unwrap().tb;
- tb.top_tags(20)
- .unwrap()
- .iter()
- .map(|tup| TagResult {
- name: tup.1.clone(),
- count: tup.0,
- })
- .collect()
- };
- let images = get_posts(conn, tags, last).into_inner();
- let context = Context {
- tags: tag_results,
- // FIXME: Magic number for results per request.
- next_page: if images.len() == 50 {
- format!("/posts?tags={}&last={}", tagstr, images[49].id)
- } else {
- String::from("")
- },
- images,
- };
- Template::render("index", context)
-}
-
+/// Information about an image, as returned by the 'posts' endpoint.
#[derive(Serialize)]
struct ImageResult {
id: i64,
@@ -114,11 +74,26 @@ struct ImageResult {
tags: Vec<String>,
}
+/// Return the file name of the thumbnail for the image at `filename`.
+fn thumb_filename(filename: &str) -> String {
+ String::from(format!(
+ "{}_thumb.png",
+ // Strip extension.
+ if let Some(n) = filename.rfind('.') {
+ &filename[..n]
+ } else {
+ &filename[..]
+ }
+ ))
+}
+
#[get("/posts?<tags>&<last>")]
fn get_posts(conn: SiteState, tags: String, last: Option<i64>) -> Json<Vec<ImageResult>> {
let tb = &conn.inner().lock().unwrap().tb;
- let tags = if tags != "" {
+ // Ensure that an empty vector is passed to `tb.query` if no tags were
+ // specified.
+ let tags = if !tags.is_empty() {
tags.split(",").collect::<Vec<&str>>()
} else {
vec![]
@@ -127,7 +102,7 @@ fn get_posts(conn: SiteState, tags: String, last: Option<i64>) -> Json<Vec<Image
.query(Query {
tags: &tags[..],
last_id: last,
- limit: Some(50),
+ limit: Some(RESULTS_PER_QUERY),
})
.unwrap();
@@ -156,16 +131,54 @@ fn put_posts(conn: SiteState, filename: String, data: Data) -> Json<&str> {
Json("uploaded!")
}
-fn thumb_filename(filename: &str) -> String {
- String::from(format!(
- "{}_thumb.png",
- // Strip extension.
- if let Some(n) = filename.rfind('.') {
- &filename[..n]
+#[get("/")]
+fn index(conn: SiteState) -> Template {
+ display_posts(conn, String::from(""), None)
+}
+
+#[get("/posts?<tags>&<last>")]
+fn display_posts(conn: SiteState, tags: String, last: Option<i64>) -> Template {
+ #[derive(Serialize)]
+ struct TagResult {
+ name: String,
+ count: i64,
+ }
+
+ #[derive(Serialize)]
+ struct Context {
+ tags: Vec<TagResult>,
+ images: Vec<ImageResult>,
+ next_page: String,
+ }
+
+ let base_params = format!("/posts?tags={}", tags);
+ let tag_results = {
+ // This is done in a block because `tb` needs to go out of scope before
+ // calling out to `get_posts` to perform the query. Otherwise, we
+ // deadlock because the mutex is locked for the entirety of
+ // `display_posts`.
+ let tb = &conn.inner().lock().unwrap().tb;
+ tb.top_tags(20)
+ .unwrap()
+ .iter()
+ .map(|tup| TagResult {
+ name: tup.1.clone(),
+ count: tup.0,
+ })
+ .collect()
+ };
+ let images = get_posts(conn, tags, last).into_inner();
+ let context = Context {
+ tags: tag_results,
+ next_page: if images.len() as i64 == RESULTS_PER_QUERY {
+ let last_image = images.last().unwrap();
+ format!("{}&last={}", base_params, last_image.id)
} else {
- &filename[..]
- }
- ))
+ String::from("")
+ },
+ images,
+ };
+ Template::render("index", context)
}
/// Maybe initialize the directory for storing image symlinks and thumbnails.
@@ -173,19 +186,17 @@ fn create_image_directory(conn: &DatabaseConnection) {
fs::create_dir(&conn.image_dir).ok();
for image in conn.tb.images_by_tags(&vec![][..]).unwrap() {
- let src = format!("{}/{}", image.orig_dir, image.filename);
- let dst = format!("{}/{}", conn.image_dir, image.filename);
+ let src = Path::new(&image.orig_dir).join(&image.filename);
+ let dst = Path::new(&conn.image_dir).join(&image.filename);
symlink(&src, &dst).ok();
- let stem = Path::new(&dst).file_stem().unwrap().to_str().unwrap();
- let thumb_path = format!("{}/{}", conn.image_dir, thumb_filename(&stem));
- let thumb_path = Path::new(&thumb_path);
-
+ let stem = dst.file_stem().unwrap().to_str().unwrap();
+ let thumb_path = Path::new(&conn.image_dir).join(&thumb_filename(&stem));
if !thumb_path.exists() {
let im = image::open(&Path::new(&src)).unwrap();
- let fout = &mut File::create(&thumb_path).unwrap();
+ let out = &mut File::create(&thumb_path).unwrap();
im.thumbnail(100, 100)
- .write_to(fout, ImageFormat::Png)
+ .write_to(out, ImageFormat::Png)
.unwrap();
}
}
@@ -193,14 +204,13 @@ fn create_image_directory(conn: &DatabaseConnection) {
fn main() {
let conn = DatabaseConnection::new().unwrap();
- let image_dir = conn.image_dir.clone();
create_image_directory(&conn);
rocket::ignite()
- .attach(Template::fairing())
- .manage(Mutex::new(conn))
.mount("/public", StaticFiles::from("./static/"))
- .mount("/image", StaticFiles::from(image_dir))
+ .mount("/image", StaticFiles::from(&conn.image_dir))
.mount("/", routes![index, display_posts])
.mount("/api", routes![get_posts, put_posts])
+ .attach(Template::fairing())
+ .manage(Mutex::new(conn))
.launch();
}