initial version

This commit is contained in:
Robin Appelman 2022-05-21 17:18:31 +02:00
commit 67497df8ba
5 changed files with 1426 additions and 4 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
.env

1209
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,14 @@ name = "mover"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1.0.30"
demostf-client = { version = "0.4.1", default-features = false, features = ["rustls-tls"] }
tokio = { version = "1.17.0", features = ["rt-multi-thread", "macros"] }
tracing = "0.1.33"
tracing-subscriber = "0.3.11"
futures-util = "0.3.21"
main_error = "0.1.2"
dotenv = "0.15.0"
time = "0.3.9"
md5 = "0.7.0"

View file

@ -1,3 +1,141 @@
fn main() {
println!("Hello, world!");
use demostf_client::{ApiClient, ListOrder, ListParams};
use main_error::MainError;
use md5::Context;
use std::collections::HashMap;
use std::fs::{rename, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::Duration;
use thiserror::Error;
use time::OffsetDateTime;
use tracing::{error, info, info_span};
#[derive(Debug, Error)]
pub enum Error {
#[error("Request failed: {0}")]
Request(#[from] std::io::Error),
#[error(transparent)]
Api(#[from] demostf_client::Error),
#[error("Backup timed out")]
Timeout,
}
#[tokio::main]
async fn main() -> Result<(), MainError> {
tracing_subscriber::fmt::init();
let mut args: HashMap<_, _> = dotenv::vars().collect();
let source_root: PathBuf = args
.remove("SOURCE_ROOT")
.expect("no SOURCE_ROOT set")
.trim_end_matches("/")
.into();
let target_root: PathBuf = args
.remove("TARGET_ROOT")
.expect("no TARGET_ROOT set")
.trim_end_matches("/")
.into();
let api_key = args.remove("KEY").expect("no KEY set");
let source_backend = args
.remove("SOURCE_BACKEND")
.expect("no SOURCE_BACKEND set");
let target_backend = args
.remove("TARGET_BACKEND")
.expect("no TARGET_BACKEND set");
let age: u64 = args
.get("AGE")
.expect("no AGE set")
.parse()
.expect("invalid AGE");
let cutoff = OffsetDateTime::now_utc() - Duration::from_secs(age);
info!(cutoff = display(cutoff), "starting move");
let client = ApiClient::new();
let demos = client
.list(
ListParams::default()
.with_before(cutoff)
.with_order(ListOrder::Ascending)
.with_backend(source_backend),
1,
)
.await?;
for demo in demos {
let source_path = generate_path(&source_root, &demo.name);
let target_path = generate_path(&target_root, &demo.name);
let _span = info_span!(
"name",
demo = demo.id,
source_path = display(source_path.display()),
target_path = display(target_path.display()),
)
.entered();
if !source_path.is_file() {
error!("source not found");
return Ok(());
}
if target_path.is_file() {
error!("target exists");
return Ok(());
}
let calculated_hash = hash(&source_path)?;
if calculated_hash != demo.hash {
error!(
calculated = debug(calculated_hash),
stored = debug(demo.hash),
"hash mismatch"
);
return Ok(());
}
rename(source_path, target_path)?;
info!("renamed");
client
.set_url(
demo.id,
&target_backend,
&demo.path,
&demo.url,
calculated_hash,
&api_key,
)
.await?;
}
Ok(())
}
fn generate_path(basedir: &PathBuf, name: &str) -> PathBuf {
let mut path = basedir.clone();
path.push(&name[0..2]);
path.push(&name[2..4]);
path.push(name);
path
}
fn hash<P: AsRef<Path>>(path: P) -> Result<[u8; 16], Error> {
let mut file = File::open(path)?;
let mut hash = Context::new();
let mut buff = vec![0; 1024 * 1024];
loop {
let read = file.read(&mut buff)?;
if read == 0 {
break;
}
let data = &buff[0..read];
hash.consume(&data);
}
Ok(hash.compute().0)
}

66
src/store.rs Normal file
View file

@ -0,0 +1,66 @@
use crate::Error;
use md5::Context;
use std::fs;
use std::fs::{File, Permissions};
use std::io::Read;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct Store {
basedir: PathBuf,
baseurl: String,
}
impl Store {
pub fn new(basedir: impl AsRef<Path>, baseurl: &str) -> Self {
Store {
basedir: basedir.as_ref().to_path_buf(),
baseurl: baseurl.trim_end_matches("/").to_string(),
}
}
pub async fn create(&self, name: &str) -> Result<File, Error> {
let path = self.generate_path(name);
fs::create_dir_all(path.parent().unwrap())?;
let file = File::create(&path)?;
file.set_permissions(Permissions::from_mode(0o644))?;
Ok(file)
}
pub fn remove(&self, name: &str) -> std::io::Result<()> {
fs::remove_file(self.generate_path(name))
}
pub fn hash(&self, name: &str) -> Result<[u8; 16], Error> {
let path = self.generate_path(name);
let mut file = File::open(path)?;
let mut hash = Context::new();
let mut buff = vec![0; 1024 * 1024];
loop {
let read = file.read(&mut buff)?;
if read == 0 {
break;
}
let data = &buff[0..read];
hash.consume(&data);
}
Ok(hash.compute().0)
}
pub fn generate_path(&self, name: &str) -> PathBuf {
let mut path = self.basedir.clone();
path.push(&name[0..2]);
path.push(&name[2..4]);
path.push(name);
path
}
}