summaryrefslogtreecommitdiff
path: root/src/birka-web.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/birka-web.rs')
-rw-r--r--src/birka-web.rs162
1 files changed, 79 insertions, 83 deletions
diff --git a/src/birka-web.rs b/src/birka-web.rs
index 359b153..76a194f 100644
--- a/src/birka-web.rs
+++ b/src/birka-web.rs
@@ -31,7 +31,7 @@ extern crate serde_derive;
mod database;
use anyhow::Result;
-use database::{Image, Query, TagDatabase};
+use database::{Keyset, TagDatabase};
use image::ImageFormat;
use multipart::{MultipartFormData, MultipartFormDataField, MultipartFormDataOptions};
use rand::distributions::Alphanumeric;
@@ -43,6 +43,7 @@ use rocket_contrib::json::Json;
use rocket_contrib::serve::StaticFiles;
use rocket_contrib::templates::Template;
+use std::convert::AsRef;
use std::fs;
use std::fs::File;
use std::io::Write;
@@ -56,10 +57,10 @@ const RESULTS_PER_QUERY: i64 = 50;
/// Shorthand for the state that is passed to all handlers.
type SiteState<'a> = State<'a, Mutex<DatabaseConnection>>;
-/// Everything necessary to serve images from the tag database.
+/// Everything necessary to serve files from the tag database.
struct DatabaseConnection {
tb: TagDatabase,
- image_dir: String,
+ file_dir: String,
}
impl DatabaseConnection {
@@ -68,15 +69,15 @@ impl DatabaseConnection {
// containing symbolic links and thumbnails be relative to the binary.
let bin = std::env::current_exe()?.canonicalize()?;
let bin_dir = bin.parent().unwrap();
- let image_dir = bin_dir.join("image/").to_str().unwrap().to_string();
+ let file_dir = bin_dir.join("files/").to_str().unwrap().to_string();
let tb = TagDatabase::new(&bin_dir.join("birka.db"))?;
- Ok(DatabaseConnection { tb, image_dir })
+ Ok(DatabaseConnection { tb, file_dir })
}
}
-/// Information about an image, as returned by the 'posts' endpoint.
+/// Information about a file, as returned by the 'posts' endpoint.
#[derive(Debug, Serialize)]
-struct ImageResult {
+struct FileResult {
id: i64,
filename: String,
thumb_filename: String,
@@ -84,71 +85,84 @@ struct ImageResult {
tags: Vec<String>,
}
-/// Return the store file name of the image at `filename`.
-fn image_store_filename(id: i64, filename: &str) -> String {
- if let Some(n) = filename.rfind('.') {
- format!("{id}.{ext}", id = id, ext = &filename[n + 1..])
+/// Return the store file name of `filename`.
+fn store_filename<T: AsRef<Path>>(id: i64, path: T) -> String {
+ if let Some(filename) = path.as_ref().file_name() {
+ let filename = filename.to_string_lossy();
+ if let Some(n) = filename.rfind('.') {
+ format!("{id}.{ext}", id = id, ext = &filename[n + 1..])
+ } else {
+ id.to_string()
+ }
} else {
id.to_string()
}
}
-/// Return the store file name of the thumbnail for the image at `filename`.
-fn thumb_filename(id: i64, filename: &str) -> String {
- if let Some(n) = filename.rfind('.') {
- format!("{id}_thumb.{ext}", id = id, ext = &filename[n + 1..])
+/// Return the store file name of the thumbnail of `filename`.
+fn thumb_filename<T: AsRef<Path>>(id: i64, path: T) -> String {
+ if let Some(filename) = path.as_ref().file_name() {
+ let filename = filename.to_string_lossy();
+ if let Some(n) = filename.rfind('.') {
+ format!("{id}_thumb.{ext}", id = id, ext = &filename[n + 1..])
+ } else {
+ format!("{}_thumb", id)
+ }
} else {
- format!("{id}_thumb", id = id)
+ format!("{}_thumb", id)
}
}
#[get("/posts?<tags>&<last>")]
-fn get_posts(conn: SiteState, tags: String, last: Option<i64>) -> Result<Json<Vec<ImageResult>>> {
+fn get_posts(conn: SiteState, tags: String, last: Option<i64>) -> Result<Json<Vec<FileResult>>> {
let tb = &conn
.inner()
.lock()
.map_err(|_| anyhow!("Could not lock database."))?
.tb;
- // 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>>()
+ let keyset = if let Some(id) = last {
+ Keyset {
+ last_id: id,
+ how_many: RESULTS_PER_QUERY,
+ }
} else {
- vec![]
+ Keyset {
+ last_id: i64::MAX,
+ how_many: RESULTS_PER_QUERY,
+ }
};
-
- let results = tb.query(Query::new(&tags[..], last, Some(RESULTS_PER_QUERY)))?;
+ let results = tb.query(&database::parse_query(tags)?, Some(keyset))?;
Ok(Json(
results
.iter()
- .map(|image| ImageResult {
- id: image.id,
- filename: image_store_filename(image.id, &image.filename),
- thumb_filename: thumb_filename(image.id, &image.filename),
- orig_filename: image.filename.clone(),
- tags: tb.tags_for_image(image.id).unwrap(),
+ .map(|file| FileResult {
+ id: file.id,
+ filename: store_filename(file.id, &file.path),
+ thumb_filename: thumb_filename(file.id, &file.path),
+ orig_filename: file.path.clone(),
+ tags: file.tags.clone(),
})
.collect(),
))
}
#[get("/posts/<id>")]
-fn get_post(conn: SiteState, id: i64) -> Result<Json<ImageResult>> {
+fn get_post(conn: SiteState, id: i64) -> Result<Json<FileResult>> {
let tb = &conn
.inner()
.lock()
.map_err(|_| anyhow!("Could not lock database."))?
.tb;
- let image = tb.image_by_id(id)?;
+ let file = tb.file_by_id(id)?;
- Ok(Json(ImageResult {
- id: image.id,
- filename: image_store_filename(image.id, &image.filename),
- thumb_filename: thumb_filename(image.id, &image.filename),
- orig_filename: image.filename.clone(),
- tags: tb.tags_for_image(image.id)?,
+ Ok(Json(FileResult {
+ id: file.id,
+ filename: store_filename(file.id, &file.path),
+ thumb_filename: thumb_filename(file.id, &file.path),
+ orig_filename: file.path.clone(),
+ tags: file.tags,
}))
}
@@ -164,12 +178,7 @@ fn update_post(conn: SiteState, id: i64, form: Form<UpdateTags>) -> Result<Json<
.lock()
.map_err(|_| anyhow!("Could not lock database."))?
.tb;
-
- for tag in form.tags.split(",") {
- tb.add_tag(tag)?;
- tb.tag_image(id, tag)?;
- }
-
+ tb.tag_file(id, &form.tags.split(",").collect())?;
Ok(Json(String::from("Updated!")))
}
@@ -196,31 +205,18 @@ fn put_post(conn: SiteState, content_type: &ContentType, data: Data) -> Result<J
let filename = raw
.file_name
.unwrap_or(thread_rng().sample_iter(&Alphanumeric).take(30).collect());
- let path = Path::new(&conn.image_dir).join(&filename);
+ let path = Path::new(&conn.file_dir).join(&filename);
let mut f = File::create(&path)?;
f.write_all(&raw.raw)?;
- let hash = database::hash_file(&path)?;
- let id = conn.tb.import_image(&path, &hash)?;
- add_to_store(
- &Image {
- id,
- blake2: hash,
- filename: filename.clone(),
- orig_dir: conn.image_dir.clone(),
- },
- &conn.image_dir,
- );
-
- if let Some(mut vec) = data.texts.remove("tags") {
- if vec.len() == 1 {
- let tags = vec.remove(0).text;
- for tag in tags.split(',') {
- conn.tb.add_tag(tag)?;
- conn.tb.tag_image(id, tag)?;
- }
- }
- }
+ let tags = if let Some(mut vec) = data.texts.remove("tags") {
+ let text = vec.remove(0).text;
+ text.split(',').map(|tag| String::from(tag)).collect()
+ } else {
+ vec![]
+ };
+ let file = conn.tb.add_file(&path, &tags)?;
+ add_to_store(&file, &conn.file_dir);
Ok(Json("Uploaded!".into()))
}
@@ -246,7 +242,7 @@ fn display_posts(conn: SiteState, tags: String, last: Option<i64>) -> Result<Tem
#[derive(Serialize)]
struct Context {
tags: Vec<TagResult>,
- images: Vec<ImageResult>,
+ images: Vec<FileResult>,
next_page: String,
}
@@ -298,27 +294,26 @@ fn display_post(conn: SiteState, id: i64) -> Result<Template> {
.lock()
.map_err(|_| anyhow!("Could not lock database."))?
.tb;
- let image = tb.image_by_id(id)?;
+ let file = tb.file_by_id(id)?;
Ok(Template::render(
"image",
Context {
id,
- tags: tb.tags_for_image(image.id)?,
- filename: image_store_filename(image.id, &image.filename),
- orig_filename: image.filename,
+ tags: file.tags,
+ filename: store_filename(file.id, &file.path),
+ orig_filename: file.path,
},
))
}
-/// Create a thumbnail for `image` in the store at `image_dir`.
-fn add_to_store(image: &Image, image_dir: &str) {
- let orig_path = Path::new(&image.orig_dir).join(&image.filename);
- let store_path = image_store_filename(image.id, &image.filename);
- let store_path = Path::new(image_dir).join(store_path);
- symlink(orig_path, &store_path).ok();
- let thumb_path = thumb_filename(image.id, &image.filename);
- let thumb_path = Path::new(image_dir).join(&thumb_path);
+/// Create a thumbnail for `file` in the store at `file_dir`.
+fn add_to_store(file: &database::File, file_dir: &str) {
+ let store_path = store_filename(file.id, &file.path);
+ let store_path = Path::new(file_dir).join(store_path);
+ symlink(&file.path, &store_path).ok();
+ let thumb_path = thumb_filename(file.id, &file.path);
+ let thumb_path = Path::new(file_dir).join(&thumb_path);
if !thumb_path.exists() {
let im = image::open(store_path).unwrap();
let out = &mut File::create(&thumb_path).unwrap();
@@ -330,9 +325,10 @@ fn add_to_store(image: &Image, image_dir: &str) {
/// Maybe initialize the directory for storing image symlinks and thumbnails.
fn create_image_directory(conn: &DatabaseConnection) {
- fs::create_dir(&conn.image_dir).ok();
- for image in conn.tb.images_by_tags(&vec![][..]).unwrap() {
- add_to_store(&image, &conn.image_dir);
+ fs::create_dir(&conn.file_dir).ok();
+ let query = database::parse_query("").unwrap();
+ for file in conn.tb.query(&query, None).unwrap() {
+ add_to_store(&file, &conn.file_dir);
}
}
@@ -341,7 +337,7 @@ fn main() {
create_image_directory(&conn);
rocket::ignite()
.mount("/public", StaticFiles::from("./static/"))
- .mount("/image", StaticFiles::from(&conn.image_dir))
+ .mount("/image", StaticFiles::from(&conn.file_dir))
.mount("/", routes![index, upload, display_posts, display_post])
.mount("/api", routes![get_posts, get_post, put_post, update_post])
.attach(Template::fairing())