initial commit
commit
48cf5889e7
|
@ -0,0 +1,4 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
|
||||
upend.sqlite3
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "upend-rust"
|
||||
version = "0.1.0"
|
||||
authors = ["Tomáš Mládek <t@mldk.cz>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
|
||||
log = "0.4"
|
||||
env_logger = "0.7.1"
|
||||
|
||||
anyhow = "1.0"
|
||||
|
||||
diesel = { version = "1.4.4", features=["sqlite", "r2d2"] }
|
||||
diesel_migrations = "1.4.0"
|
||||
|
||||
actix = "0.9.0"
|
||||
actix-web = "2.0"
|
||||
actix-files = "0.2.2"
|
||||
actix-rt = "1.0.0"
|
||||
actix_derive = "0.3.2"
|
||||
|
||||
filebuffer = "0.4.0"
|
||||
walkdir = "2"
|
||||
tiny-keccak = { version = "2.0", features = ["k12"] }
|
||||
bs58 = "0.3.1"
|
||||
|
||||
dotenv = "0.15.0"
|
||||
xdg = "^2.1"
|
|
@ -0,0 +1,5 @@
|
|||
# For documentation on how to configure this file,
|
||||
# see diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE vaults;
|
|
@ -0,0 +1,5 @@
|
|||
-- Your SQL goes here
|
||||
CREATE TABLE vaults (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path VARCHAR NOT NULL
|
||||
)
|
|
@ -0,0 +1,2 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE files;
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
hash VARCHAR NOT NULL,
|
||||
path VARCHAR NOT NULL,
|
||||
size BIGINT NOT NULL,
|
||||
valid BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
|
||||
CREATE INDEX files_hash ON files(hash);
|
|
@ -0,0 +1,122 @@
|
|||
use crate::models::NewFile;
|
||||
use actix::prelude::*;
|
||||
use actix_derive::Message;
|
||||
use anyhow::Result;
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{self, ConnectionManager};
|
||||
use diesel::sqlite::SqliteConnection;
|
||||
use log::debug;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
pub type DbPool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
|
||||
|
||||
pub struct DbExecutor(pub DbPool);
|
||||
|
||||
impl Actor for DbExecutor {
|
||||
type Context = SyncContext<Self>;
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<usize>")]
|
||||
pub struct InsertFile {
|
||||
pub file: NewFile,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<Option<String>>")]
|
||||
pub struct RetrieveByHash {
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
impl Handler<InsertFile> for DbExecutor {
|
||||
type Result = Result<usize>;
|
||||
|
||||
fn handle(&mut self, msg: InsertFile, _: &mut Self::Context) -> Self::Result {
|
||||
use crate::schema::files;
|
||||
|
||||
let connection = &self.0.get()?;
|
||||
|
||||
debug!("Inserting {:?}...", msg.file);
|
||||
|
||||
Ok(diesel::insert_into(files::table)
|
||||
.values(msg.file)
|
||||
.execute(connection)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<RetrieveByHash> for DbExecutor {
|
||||
type Result = Result<Option<String>>;
|
||||
|
||||
fn handle(&mut self, msg: RetrieveByHash, _: &mut Self::Context) -> Self::Result {
|
||||
use crate::models::File;
|
||||
use crate::schema::files::dsl::*;
|
||||
|
||||
let connection = &self.0.get()?;
|
||||
|
||||
let matches = files.filter(hash.eq(msg.hash)).load::<File>(connection)?;
|
||||
|
||||
Ok(matches.get(0).map(|f| f.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectionOptions {
|
||||
pub enable_foreign_keys: bool,
|
||||
pub busy_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl ConnectionOptions {
|
||||
pub fn apply(&self, conn: &SqliteConnection) -> QueryResult<()> {
|
||||
if self.enable_foreign_keys {
|
||||
conn.execute("PRAGMA foreign_keys = ON;")?;
|
||||
}
|
||||
if let Some(duration) = self.busy_timeout {
|
||||
conn.execute(&format!("PRAGMA busy_timeout = {};", duration.as_millis()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
|
||||
for ConnectionOptions
|
||||
{
|
||||
fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> {
|
||||
self.apply(conn).map_err(diesel::r2d2::Error::QueryError)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_upend<P: AsRef<Path>>(dirpath: P) -> Result<DbPool, Box<dyn Error>> {
|
||||
embed_migrations!("./migrations/upend/");
|
||||
|
||||
let database_path = dirpath.as_ref().join("upend.sqlite3");
|
||||
|
||||
let manager = ConnectionManager::<SqliteConnection>::new(database_path.to_str().unwrap());
|
||||
let pool = r2d2::Pool::builder()
|
||||
.connection_customizer(Box::new(ConnectionOptions {
|
||||
enable_foreign_keys: true,
|
||||
busy_timeout: Some(Duration::from_secs(3)),
|
||||
}))
|
||||
.build(manager)
|
||||
.expect("Failed to create pool.");
|
||||
|
||||
embedded_migrations::run_with_output(&pool.get().unwrap(), &mut std::io::stdout())?;
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
// extern crate xdg;
|
||||
|
||||
// pub fn open_config() -> Result<SqliteConnection, Box<dyn Error>> {
|
||||
// embed_migrations!("./migrations/config/");
|
||||
|
||||
// let dirpath = xdg::BaseDirectories::with_prefix("upend")
|
||||
// .unwrap()
|
||||
// .place_config_file("config.sqlite3")
|
||||
// .expect("Could not create config file?!");
|
||||
|
||||
// let database_url = dirpath.join("base.sqlite3");
|
||||
// let connection = SqliteConnection::establish(database_url.to_str().unwrap())?;
|
||||
// embedded_migrations::run_with_output(&connection, &mut std::io::stdout())?;
|
||||
// Ok(connection)
|
||||
// }
|
|
@ -0,0 +1,111 @@
|
|||
use crate::models::NewFile;
|
||||
use anyhow::Result;
|
||||
use filebuffer::FileBuffer;
|
||||
use log::info;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tiny_keccak::{Hasher, KangarooTwelve};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use actix::prelude::*;
|
||||
// use rayon::prelude::*;
|
||||
|
||||
// pub struct VaultUpdater(
|
||||
// pub Addr<crate::database::DbExecutor>,
|
||||
// pub Addr<HasherWorker>,
|
||||
// );
|
||||
|
||||
// impl Actor for VaultUpdater {
|
||||
// type Context = Context<Self>;
|
||||
// }
|
||||
|
||||
// struct UpdateDirectory<'a> {
|
||||
// path: &'a Path,
|
||||
// }
|
||||
|
||||
// impl Message for UpdateDirectory<'_> {
|
||||
// type Result = Result<(), Box<dyn error::Error>>;
|
||||
// }
|
||||
|
||||
// impl Handler<UpdateDirectory<'_>> for VaultUpdater {
|
||||
// type Result = Result<(), Box<dyn error::Error>>;
|
||||
|
||||
// fn handle(&mut self, msg: UpdateDirectory, _: &mut Self::Context) -> Self::Result {
|
||||
// update_directory(msg.path, &self.0, &self.1).await
|
||||
// }
|
||||
// }
|
||||
|
||||
pub async fn update_directory<T: AsRef<Path>>(
|
||||
directory: T,
|
||||
db_executor: &Addr<crate::database::DbExecutor>,
|
||||
hasher_worker: &Addr<HasherWorker>,
|
||||
) -> Result<()> {
|
||||
for entry in WalkDir::new(&directory)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().is_file())
|
||||
{
|
||||
info!("Processing: {}", entry.path().display());
|
||||
|
||||
let metadata = fs::metadata(entry.path())?;
|
||||
let size = metadata.len() as i64;
|
||||
if size < 0 {
|
||||
panic!("File {} too large?!", entry.path().display());
|
||||
}
|
||||
|
||||
let msg = ComputeHash {
|
||||
path: entry.path().to_path_buf(),
|
||||
};
|
||||
|
||||
let digest = hasher_worker.send(msg).await;
|
||||
|
||||
let new_file = NewFile {
|
||||
path: entry
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("path not valid unicode?!")
|
||||
.to_string(),
|
||||
hash: digest.unwrap().unwrap(),
|
||||
size: size,
|
||||
};
|
||||
|
||||
let _insert_result = db_executor
|
||||
.send(crate::database::InsertFile { file: new_file })
|
||||
.await?;
|
||||
}
|
||||
info!("Finished updating {}.", directory.as_ref().display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct HasherWorker;
|
||||
|
||||
impl Actor for HasherWorker {
|
||||
type Context = SyncContext<Self>;
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<String, io::Error>")]
|
||||
struct ComputeHash {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl Handler<ComputeHash> for HasherWorker {
|
||||
type Result = Result<String, io::Error>;
|
||||
|
||||
fn handle(&mut self, msg: ComputeHash, _: &mut Self::Context) -> Self::Result {
|
||||
compute_digest(msg.path)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_digest<P: AsRef<Path>>(filepath: P) -> Result<String, io::Error> {
|
||||
let fbuffer = FileBuffer::open(&filepath)?;
|
||||
let mut k12 = KangarooTwelve::new(b"");
|
||||
k12.update(&fbuffer);
|
||||
|
||||
let mut result = [0u8; 256 / 8];
|
||||
k12.finalize(&mut result);
|
||||
|
||||
Ok(bs58::encode(&result).into_string())
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
#[macro_use]
|
||||
extern crate diesel;
|
||||
|
||||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix_files::NamedFile;
|
||||
use actix_web::{error, get, middleware, post, web, App, Error, HttpResponse, HttpServer};
|
||||
use clap::{App as ClapApp, Arg};
|
||||
use std::env;
|
||||
mod database;
|
||||
mod dataops;
|
||||
mod models;
|
||||
mod schema;
|
||||
use env_logger;
|
||||
use log::info;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
struct State {
|
||||
directory: PathBuf,
|
||||
db: Addr<database::DbExecutor>,
|
||||
hasher: Addr<dataops::HasherWorker>,
|
||||
}
|
||||
|
||||
#[get("/raw/{hash}")]
|
||||
async fn get_raw(state: web::Data<State>, hash: web::Path<String>) -> Result<NamedFile, Error> {
|
||||
let response = state
|
||||
.db
|
||||
.send(database::RetrieveByHash {
|
||||
hash: hash.into_inner(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
match response {
|
||||
Ok(result) => match result {
|
||||
Some(path) => Ok(NamedFile::open(path)?),
|
||||
None => Err(error::ErrorNotFound("NOT FOUND")),
|
||||
},
|
||||
Err(e) => Err(error::ErrorInternalServerError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/refresh")]
|
||||
async fn api_refresh(state: web::Data<State>) -> Result<HttpResponse, Error> {
|
||||
dataops::update_directory(&state.directory, &state.db, &state.hasher)
|
||||
.await
|
||||
.map_err(|_| error::ErrorInternalServerError("UPDATE ERROR"))?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let env = env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info");
|
||||
env_logger::init_from_env(env);
|
||||
|
||||
let app = ClapApp::new("upend")
|
||||
.version(VERSION)
|
||||
.author("Tomáš Mládek <t@mldk.cz>")
|
||||
.arg(Arg::with_name("DIRECTORY").required(true).index(1))
|
||||
.arg(
|
||||
Arg::with_name("BIND")
|
||||
.long("bind")
|
||||
.default_value("127.0.0.1:8093")
|
||||
.help("address and port to bind the Web interface on")
|
||||
.required(true),
|
||||
);
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
info!("Starting UpEnd {}...", VERSION);
|
||||
let dirname = matches.value_of("DIRECTORY").unwrap();
|
||||
let path = PathBuf::from(dirname);
|
||||
|
||||
let _ = fs::remove_file(format!("{}/upend.sqlite3", dirname)); // TODO REMOVE!!!
|
||||
let db_pool = database::open_upend(&dirname).expect("failed to open database!");
|
||||
|
||||
let sys = actix::System::new("upend");
|
||||
|
||||
// let connection = db_pool.get().expect("Could not get SQL connection.");
|
||||
let db_addr = SyncArbiter::start(3, move || database::DbExecutor(db_pool.clone()));
|
||||
let hash_addr = SyncArbiter::start(4, move || dataops::HasherWorker);
|
||||
|
||||
let bind = matches.value_of("BIND").unwrap();
|
||||
info!("Starting server at: {}", &bind);
|
||||
|
||||
// Start HTTP server
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.data(State {
|
||||
directory: path.clone(),
|
||||
db: db_addr.clone(),
|
||||
hasher: hash_addr.clone(),
|
||||
})
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(get_raw)
|
||||
.service(api_refresh)
|
||||
})
|
||||
.bind(&bind)?
|
||||
.run();
|
||||
|
||||
sys.run()
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
use super::schema::files;
|
||||
|
||||
#[derive(Queryable)]
|
||||
pub struct File {
|
||||
pub id: i32,
|
||||
pub hash: String,
|
||||
pub path: String,
|
||||
pub size: i64,
|
||||
pub valid: bool,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Debug)]
|
||||
#[table_name = "files"]
|
||||
pub struct NewFile {
|
||||
pub hash: String,
|
||||
pub path: String,
|
||||
pub size: i64,
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
table! {
|
||||
files (id) {
|
||||
id -> Integer,
|
||||
hash -> Text,
|
||||
path -> Text,
|
||||
size -> BigInt,
|
||||
valid -> Bool,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue