diff options
Diffstat (limited to 'src/birka-web.rs')
| -rw-r--r-- | src/birka-web.rs | 162 |
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()) |