From b311644824bc0bff9603fea69d979fa6bb4b97e0 Mon Sep 17 00:00:00 2001 From: "Jakob L. Kreuze" Date: Sun, 26 Jul 2020 19:23:37 -0400 Subject: Rebrand (again). --- src/birka-cli.rs | 121 ----------------- src/birka-web.rs | 406 ------------------------------------------------------- src/database.rs | 20 +-- src/kona-cli.rs | 121 +++++++++++++++++ src/kona-web.rs | 406 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 537 insertions(+), 537 deletions(-) delete mode 100644 src/birka-cli.rs delete mode 100644 src/birka-web.rs create mode 100644 src/kona-cli.rs create mode 100644 src/kona-web.rs (limited to 'src') diff --git a/src/birka-cli.rs b/src/birka-cli.rs deleted file mode 100644 index 4a8681f..0000000 --- a/src/birka-cli.rs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright © 2020 Jakob L. Kreuze -// -// This file is part of бирка-тян. -// -// бирка-тян is free software; you can redistribute it and/or modify it -// under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation; either version 3 of the License, or -// (at your option) any later version. -// -// бирка-тян is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public -// License along with бирка-тян. If not, see . - -#[macro_use] -extern crate anyhow; -extern crate dirs; -#[macro_use] -extern crate rusqlite; - -mod database; - -use database::TagDatabase; -use std::env; -use std::path::Path; - -fn main() { - let mut args: Vec = env::args().collect(); - if args.len() < 2 || args.iter().any(|arg| arg == "-h" || arg == "--help") { - println!("usage: {} ACTION [ARGS ...]", args[0]); - println!(); - println!("\t-o, --output [PATH]: Path to the tag database."); - println!(); - println!("\tACTION: add [PATH] [TAGS ...]: index an image, optionally tagged."); - println!("\tACTION: remove [PATH]: remove an image from the database."); - println!("\tACTION: add_tags [PATH] [TAGS ...]: add tags to an indexed image."); - println!("\tACTION: remove_tags [PATH] [TAGS ...]: remove tags from an indexed image."); - println!("\tACTION: query [QUERY_STRING]: list images in the database."); - std::process::exit(1); - } - - let tb = if let Some(i) = args.iter().position(|x| x == "-o" || x == "--output") { - // Ensure that a path follows the "-o" argument. - if i + 1 >= args.len() { - eprintln!("{} requires an argument", args[i]); - std::process::exit(1); - } - - args.remove(i); - TagDatabase::new(args.remove(i + 1)) - } else if let Ok(path) = std::env::var("TB_PATH") { - TagDatabase::new(path) - } else { - TagDatabase::new("./birka.db") - } - .unwrap(); - - let action = &args[1]; - match action.to_lowercase().as_str() { - "add" => { - if args.len() < 3 { - eprintln!("usage: {} add PATH [TAGS ...]", args[0]); - std::process::exit(1); - } - - let path = Path::new(&args[2]).canonicalize().unwrap(); - if path.is_dir() { - eprintln!("'{}' is a directory", args[2]); - } - - tb.add_file(&path, &args[3..].to_vec()).unwrap(); - } - "remove" => { - if args.len() < 3 { - eprintln!("usage: {} remove PATH", args[0]); - std::process::exit(1); - } - - let file = tb.file_by_path(&args[2]).unwrap(); - tb.remove_file(file.id).unwrap(); - } - "add_tags" => { - if args.len() < 3 { - eprintln!("usage: {} add_tags PATH [TAGS ...]", args[0]); - std::process::exit(1); - } - let file = tb.file_by_path(&args[2]).unwrap(); - tb.tag_file(file.id, &args[3..].to_vec()).unwrap(); - } - "remove_tags" => { - if args.len() < 3 { - eprintln!("usage: {} remove_tags PATH [TAGS ...]", args[0]); - std::process::exit(1); - } - let file = tb.file_by_path(&args[2]).unwrap(); - tb.untag_file(file.id, &args[3..].to_vec()).unwrap(); - } - "query" => { - let query = if args.len() < 3 { - database::parse_query("").unwrap() - } else { - database::parse_query(&args[2]).unwrap() - }; - for file in tb.query(&query, None).unwrap() { - println!("{}", file.path); - } - } - "tags" => { - for (count, tag) in tb.top_tags(None).unwrap() { - println!("{}: {}", count, tag); - } - } - _ => { - eprintln!("Unknown action '{}'", action); - std::process::exit(1); - } - } -} diff --git a/src/birka-web.rs b/src/birka-web.rs deleted file mode 100644 index cd68923..0000000 --- a/src/birka-web.rs +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright © 2020 Jakob L. Kreuze -// -// This file is part of бирка-тян. -// -// бирка-тян is free software; you can redistribute it and/or modify it -// under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation; either version 3 of the License, or -// (at your option) any later version. -// -// бирка-тян is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public -// License along with бирка-тян. If not, see . - -#![feature(proc_macro_hygiene, decl_macro)] - -#[macro_use] -extern crate anyhow; -extern crate image; -#[macro_use] -extern crate rocket; -extern crate rocket_contrib; -#[macro_use] -extern crate rusqlite; -#[macro_use] -extern crate serde_derive; - -mod database; - -use anyhow::Result; -use database::{Keyset, TagDatabase}; -use image::ImageFormat; -use multipart::{MultipartFormData, MultipartFormDataField, MultipartFormDataOptions}; -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; -use rocket::http::ContentType; -use rocket::request::Form; -use rocket::{Data, State}; -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; -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>; - -/// Everything necessary to serve files from the tag database. -struct DatabaseConnection { - tb: TagDatabase, - file_dir: String, -} - -impl DatabaseConnection { - fn new() -> Result { - // FIXME: It is somewhat of an arbitrary choice that the directory - // containing symbolic links and thumbnails be relative to the binary. - let bin = std::env::current_exe()?.canonicalize()?; - let bin_dir = bin.parent().unwrap(); - let file_dir = bin_dir.join("files/").to_str().unwrap().to_string(); - let tb = TagDatabase::new(&bin_dir.join("birka.db"))?; - Ok(DatabaseConnection { tb, file_dir }) - } -} - -/// Information about a file, as returned by the 'posts' endpoint. -#[derive(Debug, Serialize)] -struct FileResult { - id: i64, - filename: String, - thumb_filename: String, - orig_filename: String, - tags: Vec, -} - -/// Return whether or not `filename` ends with one of `extensions`. -fn has_extension>(filename: T, extensions: Vec<&str>) -> bool { - filename - .as_ref() - .extension() - .and_then(|ext| ext.to_str()) - .map(|ext| extensions.contains(&ext)) - .unwrap_or(false) -} - -/// Return whether or not `filename` ends with the extension of a known video file type. -fn is_video>(filename: T) -> bool { - has_extension(filename, vec!["mp4", "mkv", "webm"]) -} - -/// Return whether or not `filename` ends with the extension of a known image file type. -fn is_image>(filename: T) -> bool { - has_extension(filename, vec!["png", "jpg", "jpeg", "gif"]) -} - -/// Return the store file name of `filename`. -fn store_filename>(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 of `filename`. -fn thumb_filename>(id: i64, path: T) -> String { - if let Some(ext) = path.as_ref().extension() { - if let Some(ext) = ext.to_str() { - let ext = if is_video(path.as_ref()) { "png" } else { ext }; - format!("{id}_thumb.{ext}", id = id, ext = ext) - } else { - format!("{}_thumb", id) - } - } else { - format!("{}_thumb", id) - } -} - -#[get("/posts?&")] -fn get_posts(conn: SiteState, query: String, last: Option) -> Result>> { - let tb = &conn - .inner() - .lock() - .map_err(|_| anyhow!("Could not lock database."))? - .tb; - - let keyset = if let Some(id) = last { - Keyset { - last_id: id, - how_many: RESULTS_PER_QUERY, - } - } else { - Keyset { - last_id: i64::MAX, - how_many: RESULTS_PER_QUERY, - } - }; - let results = tb.query(&database::parse_query(query)?, Some(keyset))?; - - Ok(Json( - results - .iter() - .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/")] -fn get_post(conn: SiteState, id: i64) -> Result> { - let tb = &conn - .inner() - .lock() - .map_err(|_| anyhow!("Could not lock database."))? - .tb; - let file = tb.file_by_id(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, - })) -} - -#[derive(FromForm)] -struct UpdateTags { - tags: String, -} - -#[post("/posts/", data = "
")] -fn update_post(conn: SiteState, id: i64, form: Form) -> Result> { - let tb = &conn - .inner() - .lock() - .map_err(|_| anyhow!("Could not lock database."))? - .tb; - tb.tag_file(id, &form.tags.split(',').collect::>()[..])?; - Ok(Json(String::from("Updated!"))) -} - -#[post("/posts", data = "")] -fn put_post(conn: SiteState, content_type: &ContentType, data: Data) -> Result> { - let conn = conn - .inner() - .lock() - .map_err(|_| anyhow!("Could not lock database."))?; - let options = MultipartFormDataOptions::with_multipart_form_data_fields(vec![ - MultipartFormDataField::text("tags"), - MultipartFormDataField::raw("image") - .size_limit(32 * 1024 * 1024) - .content_type_by_string(Some(multipart::mime::IMAGE_STAR))?, - ]); - - let mut data = MultipartFormData::parse(content_type, data, options)?; - let raw = if let Some(mut data) = data.raw.remove("image") { - data.remove(0) - } else { - bail!("No `image` field.") - }; - - let filename = raw - .file_name - .unwrap_or_else(|| thread_rng().sample_iter(&Alphanumeric).take(30).collect()); - let path = Path::new(&conn.file_dir).join(&filename); - let mut f = File::create(&path)?; - f.write_all(&raw.raw)?; - - let tags = if let Some(mut vec) = data.texts.remove("tags") { - let text = vec.remove(0).text; - text.split(',').map(String::from).collect() - } else { - vec![] - }; - let file = conn.tb.add_file(&path, &tags)?; - add_to_store(&file, &conn.file_dir); - - Ok(Json("Uploaded!".into())) -} - -#[get("/")] -fn index(conn: SiteState) -> Result