Compare commits
4 commits
b999c35965
...
7edadd7ca1
| Author | SHA1 | Date | |
|---|---|---|---|
| 7edadd7ca1 | |||
| d197c06688 | |||
| fc38e51a03 | |||
| 256f3ce8ba |
9 changed files with 203 additions and 19 deletions
92
Cargo.lock
generated
92
Cargo.lock
generated
|
|
@ -73,6 +73,12 @@ version = "0.22.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
|
|
@ -187,6 +193,27 @@ dependencies = [
|
|||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
|
@ -460,6 +487,16 @@ version = "0.2.172"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.0"
|
||||
|
|
@ -565,6 +602,17 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
|
|
@ -701,9 +749,11 @@ name = "shrupl"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dirs-next",
|
||||
"env_logger",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
|
|
@ -753,6 +803,26 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.41"
|
||||
|
|
@ -898,6 +968,28 @@ dependencies = [
|
|||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ description = "ShrUpl is a tool to upload files to a Sharry Instance through a p
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.38", features = ["derive"] }
|
||||
dirs-next = "2.0.0"
|
||||
env_logger = "0.11.8"
|
||||
log = "0.4.27"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
ureq = { version = "3.0.11", features = ["json"] }
|
||||
|
||||
[profile.release]
|
||||
|
|
|
|||
49
src/appstate.rs
Normal file
49
src/appstate.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use std::{
|
||||
fs,
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use log::{debug, trace};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
cli::Cli,
|
||||
sharry::{Alias, File, Share},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AppState {
|
||||
alias: Alias,
|
||||
share: Share,
|
||||
files: Vec<File>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn load(file_name: impl AsRef<Path>) -> io::Result<Self> {
|
||||
let content = fs::read_to_string(file_name)?;
|
||||
let state = serde_json::from_str(&content).map_err(io::Error::other)?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
fn save(&self, file_name: impl AsRef<Path>) -> io::Result<()> {
|
||||
let json = serde_json::to_string_pretty(self).map_err(io::Error::other)?;
|
||||
let mut file = fs::File::create(file_name)?;
|
||||
file.write_all(json.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn try_resume(args: &Cli) -> Option<Self> {
|
||||
let file_name = dirs_next::cache_dir()?
|
||||
.join("shrupl")
|
||||
.join(format!("{}.json", args.get_hash()));
|
||||
|
||||
trace!("loading from {}", file_name.display());
|
||||
|
||||
Self::load(&file_name)
|
||||
.inspect_err(|e| debug!("could not resume from {}: {e}", file_name.display()))
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
23
src/cli.rs
23
src/cli.rs
|
|
@ -1,15 +1,18 @@
|
|||
use std::time::Duration;
|
||||
use std::{
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use clap::{Parser, builder::PossibleValuesParser};
|
||||
|
||||
use super::sharry::{File, Alias, Uri, NewShareRequest};
|
||||
use super::sharry::{Alias, File, NewShareRequest, Uri};
|
||||
|
||||
#[derive(Parser, Debug, Hash)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
/// Timeout in seconds for HTTP actions (set 0 or invalid to disable)
|
||||
#[arg(
|
||||
short, long,
|
||||
short, long,
|
||||
default_value = "10", value_name = "SECS",
|
||||
value_parser = parse_seconds,
|
||||
)]
|
||||
|
|
@ -70,4 +73,18 @@ impl Cli {
|
|||
pub fn get_share_request(&self) -> NewShareRequest {
|
||||
NewShareRequest::new(&self.name, self.description.as_ref(), self.max_views)
|
||||
}
|
||||
|
||||
pub fn get_hash(&self) -> String {
|
||||
let file_refs = {
|
||||
let mut refs: Vec<_> = self.files.iter().map(File::get_path).collect();
|
||||
refs.sort_unstable();
|
||||
|
||||
refs
|
||||
};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
(self.get_alias(), file_refs).hash(&mut hasher);
|
||||
|
||||
format!("{:x}", hasher.finish())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
mod appstate;
|
||||
mod cli;
|
||||
mod sharry;
|
||||
|
||||
|
|
@ -5,6 +6,7 @@ use clap::Parser;
|
|||
use log::{error, info};
|
||||
use ureq::Agent;
|
||||
|
||||
use appstate::AppState;
|
||||
use cli::Cli;
|
||||
use sharry::Share;
|
||||
|
||||
|
|
@ -20,6 +22,10 @@ fn main() {
|
|||
.build()
|
||||
.into();
|
||||
|
||||
if let Some(state) = AppState::try_resume(&args) {
|
||||
info!("state: {state:?}");
|
||||
}
|
||||
|
||||
let alias = args.get_alias();
|
||||
let share = Share::create(&agent, &alias, args.get_share_request()).unwrap();
|
||||
info!("share: {share:?}");
|
||||
|
|
@ -29,7 +35,7 @@ fn main() {
|
|||
info!("file: {file:?}");
|
||||
|
||||
for chunk in file.chunked(args.chunk_size * 1024 * 1024).seek(0) {
|
||||
info!("chunk len: {}", chunk.bytes.len());
|
||||
info!("chunk: {chunk:?}");
|
||||
|
||||
file.upload_chunk(&agent, &alias, &chunk)
|
||||
.unwrap_or_else(|e| {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use std::fmt::{Debug, Display};
|
||||
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ureq::RequestBuilder;
|
||||
|
||||
use super::api::Uri;
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
#[derive(Serialize, Deserialize, Debug, Hash)]
|
||||
pub struct Alias {
|
||||
pub(super) api_uri: String,
|
||||
pub(super) id: String,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
fmt::Debug,
|
||||
fs::File,
|
||||
io::{Read, Seek, SeekFrom},
|
||||
path::Path,
|
||||
|
|
@ -8,7 +9,7 @@ use log::error;
|
|||
|
||||
pub struct FileChunks<'t> {
|
||||
file_path: &'t Path,
|
||||
chunk_index: u64,
|
||||
offset: u64,
|
||||
chunk_size: usize,
|
||||
}
|
||||
|
||||
|
|
@ -16,15 +17,15 @@ impl<'t> FileChunks<'t> {
|
|||
pub(super) fn new(path: &'t Path, chunk_size: usize) -> Self {
|
||||
Self {
|
||||
file_path: path,
|
||||
chunk_index: 0,
|
||||
offset: 0,
|
||||
chunk_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn seek(self, chunk_index: u64) -> Self {
|
||||
pub fn seek(self, offset: u64) -> Self {
|
||||
Self {
|
||||
file_path: self.file_path,
|
||||
chunk_index,
|
||||
offset,
|
||||
chunk_size: self.chunk_size,
|
||||
}
|
||||
}
|
||||
|
|
@ -34,10 +35,7 @@ impl Iterator for FileChunks<'_> {
|
|||
type Item = Chunk;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let offset = self.chunk_index
|
||||
* u64::try_from(self.chunk_size)
|
||||
.inspect_err(|e| error!("Error converting to u64: {e}"))
|
||||
.ok()?;
|
||||
let offset = self.offset;
|
||||
|
||||
let mut f = File::open(self.file_path)
|
||||
.inspect_err(|e| error!("Error opening file: {e}"))
|
||||
|
|
@ -50,7 +48,11 @@ impl Iterator for FileChunks<'_> {
|
|||
.ok()?;
|
||||
bytes.truncate(read_len);
|
||||
|
||||
self.chunk_index += 1;
|
||||
let read_len: u64 = read_len
|
||||
.try_into()
|
||||
.inspect_err(|e| error!("Error converting to u64: {e}"))
|
||||
.ok()?;
|
||||
self.offset += read_len;
|
||||
|
||||
Some(Self::Item { offset, bytes }).filter(|c| !c.bytes.is_empty())
|
||||
}
|
||||
|
|
@ -61,6 +63,15 @@ pub struct Chunk {
|
|||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Debug for Chunk {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Chunk")
|
||||
.field("offset", &self.offset)
|
||||
.field("len", &self.bytes.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn after(&self) -> u64 {
|
||||
let len: u64 = self.bytes.len().try_into().unwrap();
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ mod chunks;
|
|||
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs::metadata,
|
||||
fs::{canonicalize, metadata},
|
||||
hash::{Hash, Hasher},
|
||||
io::{self, ErrorKind},
|
||||
path::{Path, PathBuf, absolute},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use log::{debug, error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ureq::{Error::Other, http::StatusCode};
|
||||
|
||||
use super::{
|
||||
|
|
@ -17,7 +18,7 @@ use super::{
|
|||
};
|
||||
pub use chunks::{Chunk, FileChunks};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct File {
|
||||
abs_path: PathBuf,
|
||||
name: String,
|
||||
|
|
@ -33,7 +34,7 @@ impl Hash for File {
|
|||
|
||||
impl File {
|
||||
pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
|
||||
let abs_path = absolute(path)?;
|
||||
let abs_path = canonicalize(path)?;
|
||||
|
||||
let m = metadata(&abs_path)?;
|
||||
if !m.is_file() {
|
||||
|
|
@ -52,6 +53,10 @@ impl File {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_path(&self) -> &Path {
|
||||
&self.abs_path
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
self,
|
||||
http: &ureq::Agent,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
alias::{Alias, SharryAlias},
|
||||
api::{NewShareRequest, NewShareResponse, NotifyShareResponse},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Share {
|
||||
pub(super) id: String,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue