Merge branch 'develop' into feature/hashing
- with "clippy fix" and "cargo fmt"
This commit is contained in:
commit
1f9c247439
12 changed files with 171 additions and 146 deletions
3
.vscode/tasks.json
vendored
3
.vscode/tasks.json
vendored
|
|
@ -32,6 +32,9 @@
|
||||||
"command": "clippy",
|
"command": "clippy",
|
||||||
"args": [
|
"args": [
|
||||||
"--fix",
|
"--fix",
|
||||||
|
"--lib",
|
||||||
|
"--bin",
|
||||||
|
"shrupl",
|
||||||
"--allow-dirty",
|
"--allow-dirty",
|
||||||
"--allow-staged",
|
"--allow-staged",
|
||||||
"--",
|
"--",
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ use log::{debug, info, warn};
|
||||||
use crate::{
|
use crate::{
|
||||||
cachefile::CacheFile,
|
cachefile::CacheFile,
|
||||||
cli::Cli,
|
cli::Cli,
|
||||||
|
error,
|
||||||
file::{Chunk, FileTrait},
|
file::{Chunk, FileTrait},
|
||||||
output::new_progressbar,
|
output::new_progressbar,
|
||||||
sharry::{self, Client},
|
sharry::Client,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
|
@ -41,12 +42,12 @@ impl AppState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_resume(args: &Cli) -> sharry::Result<Self> {
|
pub fn try_resume(args: &Cli) -> error::Result<Self> {
|
||||||
fn check_hash<'a>(file: &'a impl FileTrait<'a>, bar: &ProgressBar) -> sharry::Result<()> {
|
fn check_hash<'a>(file: &'a impl FileTrait<'a>, bar: &ProgressBar) -> error::Result<()> {
|
||||||
bar.set_message(format!("checking {:?}", file.get_name()));
|
bar.set_message(format!("checking {:?}", file.get_name()));
|
||||||
match file.check_hash(|bytes| bar.inc(bytes)) {
|
match file.check_hash(|bytes| bar.inc(bytes)) {
|
||||||
Ok(true) => Ok(()),
|
Ok(true) => Ok(()),
|
||||||
Ok(false) => Err(sharry::ClientError::unknown(format!(
|
Ok(false) => Err(error::Error::unknown(format!(
|
||||||
"Hash mismatch for file {:?}!",
|
"Hash mismatch for file {:?}!",
|
||||||
file.get_name()
|
file.get_name()
|
||||||
))),
|
))),
|
||||||
|
|
@ -90,7 +91,7 @@ impl AppState {
|
||||||
Ok(Self::new(new_http(args.get_timeout()), inner))
|
Ok(Self::new(new_http(args.get_timeout()), inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_args(args: &Cli) -> sharry::Result<Self> {
|
pub fn from_args(args: &Cli) -> error::Result<Self> {
|
||||||
// TODO CLI switch begin
|
// TODO CLI switch begin
|
||||||
|
|
||||||
let mut files = args.files.clone();
|
let mut files = args.files.clone();
|
||||||
|
|
@ -148,7 +149,7 @@ impl AppState {
|
||||||
self.with_progressbar(f, true);
|
self.with_progressbar(f, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> sharry::Result<Option<Chunk<'t>>> {
|
fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> error::Result<Option<Chunk<'t>>> {
|
||||||
if self.inner.get_uploading(&self.http)?.is_none() {
|
if self.inner.get_uploading(&self.http)?.is_none() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +165,7 @@ impl AppState {
|
||||||
Ok(Some(chunk))
|
Ok(Some(chunk))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> sharry::Result<bool> {
|
pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> error::Result<bool> {
|
||||||
let Some(chunk) = self.next_chunk(buffer)? else {
|
let Some(chunk) = self.next_chunk(buffer)? else {
|
||||||
self.inner
|
self.inner
|
||||||
.share_notify(&self.http)
|
.share_notify(&self.http)
|
||||||
|
|
@ -185,6 +186,7 @@ impl AppState {
|
||||||
Ok(self.inner.peek_uploading().is_none() && self.inner.queue().is_empty())
|
Ok(self.inner.peek_uploading().is_none() && self.inner.queue().is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn rewind_chunk(mut self) -> Option<Self> {
|
pub fn rewind_chunk(mut self) -> Option<Self> {
|
||||||
self.inner = self.inner.rewind_chunk()?;
|
self.inner = self.inner.rewind_chunk()?;
|
||||||
|
|
||||||
|
|
@ -196,7 +198,7 @@ impl AppState {
|
||||||
self.drop_progressbar(ProgressBar::abandon);
|
self.drop_progressbar(ProgressBar::abandon);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_share(self, args: &Cli) -> sharry::Result<Self> {
|
pub fn rebuild_share(self, args: &Cli) -> error::Result<Self> {
|
||||||
let share_id =
|
let share_id =
|
||||||
self.http
|
self.http
|
||||||
.share_create(&args.get_uri(), &args.alias, args.get_share_request())?;
|
.share_create(&args.get_uri(), &args.alias, args.get_share_request())?;
|
||||||
|
|
@ -215,4 +217,8 @@ impl AppState {
|
||||||
pub fn discard(self) -> io::Result<()> {
|
pub fn discard(self) -> io::Result<()> {
|
||||||
self.inner.discard()
|
self.inner.discard()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_any(args: &Cli) {
|
||||||
|
CacheFile::clear_any(args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
mod appstate;
|
|
||||||
mod cachefile;
|
|
||||||
mod cli;
|
|
||||||
mod file;
|
|
||||||
mod impl_ureq;
|
|
||||||
mod output;
|
|
||||||
mod sharry;
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
process,
|
process,
|
||||||
sync::{
|
sync::{
|
||||||
|
|
@ -16,12 +8,12 @@ use std::{
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use console::{StyledObject, style};
|
use console::{StyledObject, style};
|
||||||
use log::{info, trace};
|
use log::{debug, info, trace};
|
||||||
|
|
||||||
use appstate::AppState;
|
use shrupl::{
|
||||||
use cli::Cli;
|
AppState, Cli, error,
|
||||||
use output::{Log, SHRUPL};
|
output::{self, Log, SHRUPL},
|
||||||
use sharry::{ClientError, Parameter};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Cli::parse();
|
let args = Cli::parse();
|
||||||
|
|
@ -56,7 +48,7 @@ fn main() {
|
||||||
|
|
||||||
let mut state = resumed
|
let mut state = resumed
|
||||||
.inspect_err(|e| {
|
.inspect_err(|e| {
|
||||||
cachefile::CacheFile::clear_any(&args);
|
AppState::clear_any(&args);
|
||||||
Log::handle(e);
|
Log::handle(e);
|
||||||
info!("could not resume from hash {:?}: {e}", args.get_hash());
|
info!("could not resume from hash {:?}: {e}", args.get_hash());
|
||||||
})
|
})
|
||||||
|
|
@ -94,18 +86,18 @@ fn main() {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
Log::handle(&e);
|
Log::handle(&e);
|
||||||
|
|
||||||
if let ClientError::InvalidParameter(p) = e {
|
if let error::Error::InvalidParameter(p) = e {
|
||||||
match p {
|
match p {
|
||||||
// Error 404 (File not found)
|
// Error 404 (File not found)
|
||||||
Parameter::FileID(fid) => {
|
error::Parameter::FileID(fid) => {
|
||||||
trace!("requeueing file {fid:?}");
|
debug!("requeueing file {fid:?}");
|
||||||
|
|
||||||
state.abort_upload();
|
state.abort_upload();
|
||||||
}
|
}
|
||||||
// Error 404 (Share not found)
|
// Error 404 (Share not found)
|
||||||
Parameter::ShareID(sid) => {
|
error::Parameter::ShareID(sid) => {
|
||||||
// TODO ask
|
// TODO ask
|
||||||
trace!("rebuilding share {sid:?}");
|
debug!("rebuilding share {sid:?}");
|
||||||
|
|
||||||
// rebuild share
|
// rebuild share
|
||||||
let Ok(s) = state.rebuild_share(&args) else {
|
let Ok(s) = state.rebuild_share(&args) else {
|
||||||
|
|
@ -122,7 +114,7 @@ fn main() {
|
||||||
};
|
};
|
||||||
tries += 1;
|
tries += 1;
|
||||||
|
|
||||||
trace!("State rewound, retrying last chunk (tries: {tries})");
|
debug!("State rewound, retrying last chunk (tries: {tries})");
|
||||||
state = s;
|
state = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,8 +10,9 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::Cli,
|
cli::Cli,
|
||||||
|
error,
|
||||||
file::{self, Chunk},
|
file::{self, Chunk},
|
||||||
sharry::{self, Client, Uri},
|
sharry::{Client, Uri},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
@ -74,7 +75,7 @@ impl CacheFile {
|
||||||
pub fn get_uploading(
|
pub fn get_uploading(
|
||||||
&mut self,
|
&mut self,
|
||||||
client: &impl Client,
|
client: &impl Client,
|
||||||
) -> sharry::Result<Option<&mut file::Uploading>> {
|
) -> error::Result<Option<&mut file::Uploading>> {
|
||||||
if self.uploading.is_some() {
|
if self.uploading.is_some() {
|
||||||
Ok(self.uploading.as_mut())
|
Ok(self.uploading.as_mut())
|
||||||
} else if let Some(chk) = self.files.pop_front() {
|
} else if let Some(chk) = self.files.pop_front() {
|
||||||
|
|
@ -128,11 +129,11 @@ impl CacheFile {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn share_notify(&self, client: &impl Client) -> sharry::Result<()> {
|
pub fn share_notify(&self, client: &impl Client) -> error::Result<()> {
|
||||||
client.share_notify(&self.uri, &self.alias_id, &self.share_id)
|
client.share_notify(&self.uri, &self.alias_id, &self.share_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_patch(&self, client: &impl Client, chunk: &Chunk) -> sharry::Result<()> {
|
pub fn file_patch(&self, client: &impl Client, chunk: &Chunk) -> error::Result<()> {
|
||||||
client.file_patch(&self.uri, &self.alias_id, &self.share_id, chunk)
|
client.file_patch(&self.uri, &self.alias_id, &self.share_id, chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,14 +107,17 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
|
#[must_use]
|
||||||
pub fn get_timeout(&self) -> Option<Duration> {
|
pub fn get_timeout(&self) -> Option<Duration> {
|
||||||
(!self.timeout.is_zero()).then_some(self.timeout)
|
(!self.timeout.is_zero()).then_some(self.timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_uri(&self) -> Uri {
|
pub fn get_uri(&self) -> Uri {
|
||||||
Uri::new(&self.protocol, &self.url)
|
Uri::new(&self.protocol, &self.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn may_retry(&self, tries: u32) -> bool {
|
pub fn may_retry(&self, tries: u32) -> bool {
|
||||||
match self.retry_limit {
|
match self.retry_limit {
|
||||||
0 => true,
|
0 => true,
|
||||||
|
|
@ -122,10 +125,12 @@ impl Cli {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_share_request(&self) -> NewShareRequest {
|
pub fn get_share_request(&self) -> NewShareRequest {
|
||||||
NewShareRequest::new(&self.name, self.description.as_ref(), self.max_views)
|
NewShareRequest::new(&self.name, self.description.as_ref(), self.max_views)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_level_filter(&self) -> LevelFilter {
|
pub fn get_level_filter(&self) -> LevelFilter {
|
||||||
match self.verbose {
|
match self.verbose {
|
||||||
0 => LevelFilter::Error,
|
0 => LevelFilter::Error,
|
||||||
|
|
@ -140,6 +145,7 @@ impl Cli {
|
||||||
self.files.iter().map(FileTrait::get_name).collect()
|
self.files.iter().map(FileTrait::get_name).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn get_hash(&self) -> String {
|
pub fn get_hash(&self) -> String {
|
||||||
let mut hasher = Blake2b::new().hash_length(16).to_state();
|
let mut hasher = Blake2b::new().hash_length(16).to_state();
|
||||||
|
|
||||||
|
|
|
||||||
76
src/error.rs
Normal file
76
src/error.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Parameter {
|
||||||
|
#[error("given URI {0:?}")]
|
||||||
|
Uri(String),
|
||||||
|
|
||||||
|
#[error("given Alias ID {0:?}")]
|
||||||
|
AliasID(String),
|
||||||
|
|
||||||
|
#[error("stored Share ID {0:?}")]
|
||||||
|
ShareID(String),
|
||||||
|
|
||||||
|
#[error("stored File ID {0:?}")]
|
||||||
|
FileID(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parameter {
|
||||||
|
fn is_fatal(&self) -> bool {
|
||||||
|
matches!(self, Self::Uri(_) | Self::AliasID(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
StdIo(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("response error: {0}")]
|
||||||
|
Response(String),
|
||||||
|
|
||||||
|
#[error("Invalid {0}")]
|
||||||
|
InvalidParameter(Parameter),
|
||||||
|
|
||||||
|
#[error("Unknown error: {0}")]
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
fn into_string(val: impl ToString) -> String {
|
||||||
|
val.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn res_status_check<T>(actual: T, expected: T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: PartialEq + fmt::Display + Copy,
|
||||||
|
{
|
||||||
|
if actual == expected {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Self::Response(format!(
|
||||||
|
"unexpected status: {actual} (expected {expected})"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn response(e: impl ToString) -> Self {
|
||||||
|
Self::Response(into_string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unknown(e: impl ToString) -> Self {
|
||||||
|
Self::Unknown(into_string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_fatal(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::InvalidParameter(p) => p.is_fatal(),
|
||||||
|
Self::Unknown(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
@ -5,7 +5,7 @@ use std::{
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::sharry;
|
use crate::{error, sharry};
|
||||||
|
|
||||||
use super::{FileTrait, Uploading};
|
use super::{FileTrait, Uploading};
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ impl Checked {
|
||||||
uri: &sharry::Uri,
|
uri: &sharry::Uri,
|
||||||
alias_id: &str,
|
alias_id: &str,
|
||||||
share_id: &str,
|
share_id: &str,
|
||||||
) -> sharry::Result<Uploading> {
|
) -> error::Result<Uploading> {
|
||||||
let file_id = client.file_create(uri, alias_id, share_id, &self)?;
|
let file_id = client.file_create(uri, alias_id, share_id, &self)?;
|
||||||
|
|
||||||
Ok(Uploading::new(self.path, self.size, self.hash, file_id))
|
Ok(Uploading::new(self.path, self.size, self.hash, file_id))
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error,
|
||||||
file::{self, FileTrait},
|
file::{self, FileTrait},
|
||||||
sharry::{self, ClientError, Uri},
|
sharry::{self, Uri},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn find_cause(
|
fn find_cause(
|
||||||
|
|
@ -10,22 +11,22 @@ fn find_cause(
|
||||||
alias_id: &str,
|
alias_id: &str,
|
||||||
share_id: Option<&str>,
|
share_id: Option<&str>,
|
||||||
file_id: Option<&str>,
|
file_id: Option<&str>,
|
||||||
) -> impl FnOnce(ureq::Error) -> ClientError {
|
) -> impl FnOnce(ureq::Error) -> error::Error {
|
||||||
move |error| match error {
|
move |error| match error {
|
||||||
ureq::Error::StatusCode(403) => {
|
ureq::Error::StatusCode(403) => {
|
||||||
trace!("HTTP Error 403: Alias not found!");
|
trace!("HTTP Error 403: Alias not found!");
|
||||||
|
|
||||||
ClientError::InvalidParameter(sharry::Parameter::AliasID(alias_id.to_owned()))
|
error::Error::InvalidParameter(error::Parameter::AliasID(alias_id.to_owned()))
|
||||||
}
|
}
|
||||||
ureq::Error::StatusCode(404) => {
|
ureq::Error::StatusCode(404) => {
|
||||||
trace!("HTTP Error 404: Share and/or file may have been deleted!");
|
trace!("HTTP Error 404: Share and/or file may have been deleted!");
|
||||||
|
|
||||||
if let Some(file_id) = file_id {
|
if let Some(file_id) = file_id {
|
||||||
ClientError::InvalidParameter(sharry::Parameter::FileID(file_id.to_owned()))
|
error::Error::InvalidParameter(error::Parameter::FileID(file_id.to_owned()))
|
||||||
} else if let Some(share_id) = share_id {
|
} else if let Some(share_id) = share_id {
|
||||||
ClientError::InvalidParameter(sharry::Parameter::ShareID(share_id.to_owned()))
|
error::Error::InvalidParameter(error::Parameter::ShareID(share_id.to_owned()))
|
||||||
} else {
|
} else {
|
||||||
ClientError::unknown(error)
|
error::Error::InvalidParameter(error::Parameter::Uri(uri.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ureq::Error::Io(error) => {
|
ureq::Error::Io(error) => {
|
||||||
|
|
@ -33,7 +34,7 @@ fn find_cause(
|
||||||
|
|
||||||
if let Some(msg) = error.get_ref().map(ToString::to_string) {
|
if let Some(msg) = error.get_ref().map(ToString::to_string) {
|
||||||
if msg.starts_with("failed to lookup address information") {
|
if msg.starts_with("failed to lookup address information") {
|
||||||
ClientError::InvalidParameter(sharry::Parameter::Uri(uri.to_string()))
|
error::Error::InvalidParameter(error::Parameter::Uri(uri.to_string()))
|
||||||
} else {
|
} else {
|
||||||
error.into()
|
error.into()
|
||||||
}
|
}
|
||||||
|
|
@ -41,7 +42,7 @@ fn find_cause(
|
||||||
error.into()
|
error.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
error => ClientError::unknown(error),
|
error => error::Error::unknown(error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +52,7 @@ impl sharry::Client for ureq::Agent {
|
||||||
uri: &Uri,
|
uri: &Uri,
|
||||||
alias_id: &str,
|
alias_id: &str,
|
||||||
data: sharry::NewShareRequest,
|
data: sharry::NewShareRequest,
|
||||||
) -> sharry::Result<String> {
|
) -> error::Result<String> {
|
||||||
let res = {
|
let res = {
|
||||||
let endpoint = uri.share_create();
|
let endpoint = uri.share_create();
|
||||||
|
|
||||||
|
|
@ -62,11 +63,11 @@ impl sharry::Client for ureq::Agent {
|
||||||
.map_err(find_cause(uri, alias_id, None, None))?;
|
.map_err(find_cause(uri, alias_id, None, None))?;
|
||||||
|
|
||||||
trace!("{endpoint:?} response: {res:?}");
|
trace!("{endpoint:?} response: {res:?}");
|
||||||
ClientError::res_status_check(res.status(), ureq::http::StatusCode::OK)?;
|
error::Error::res_status_check(res.status(), ureq::http::StatusCode::OK)?;
|
||||||
|
|
||||||
res.body_mut()
|
res.body_mut()
|
||||||
.read_json::<sharry::NewShareResponse>()
|
.read_json::<sharry::NewShareResponse>()
|
||||||
.map_err(ClientError::response)?
|
.map_err(error::Error::response)?
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("{res:?}");
|
debug!("{res:?}");
|
||||||
|
|
@ -76,11 +77,11 @@ impl sharry::Client for ureq::Agent {
|
||||||
|
|
||||||
Ok(res.id)
|
Ok(res.id)
|
||||||
} else {
|
} else {
|
||||||
Err(ClientError::response(format!("{res:?}")))
|
Err(error::Error::response(format!("{res:?}")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> sharry::Result<()> {
|
fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> error::Result<()> {
|
||||||
let res = {
|
let res = {
|
||||||
let endpoint = uri.share_notify(share_id);
|
let endpoint = uri.share_notify(share_id);
|
||||||
|
|
||||||
|
|
@ -91,11 +92,11 @@ impl sharry::Client for ureq::Agent {
|
||||||
.map_err(find_cause(uri, alias_id, Some(share_id), None))?;
|
.map_err(find_cause(uri, alias_id, Some(share_id), None))?;
|
||||||
|
|
||||||
trace!("{endpoint:?} response: {res:?}");
|
trace!("{endpoint:?} response: {res:?}");
|
||||||
ClientError::res_status_check(res.status(), ureq::http::StatusCode::OK)?;
|
error::Error::res_status_check(res.status(), ureq::http::StatusCode::OK)?;
|
||||||
|
|
||||||
res.body_mut()
|
res.body_mut()
|
||||||
.read_json::<sharry::NotifyShareResponse>()
|
.read_json::<sharry::NotifyShareResponse>()
|
||||||
.map_err(ClientError::response)?
|
.map_err(error::Error::response)?
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("{res:?}");
|
debug!("{res:?}");
|
||||||
|
|
@ -109,7 +110,7 @@ impl sharry::Client for ureq::Agent {
|
||||||
alias_id: &str,
|
alias_id: &str,
|
||||||
share_id: &str,
|
share_id: &str,
|
||||||
file: &file::Checked,
|
file: &file::Checked,
|
||||||
) -> sharry::Result<String> {
|
) -> error::Result<String> {
|
||||||
let res = {
|
let res = {
|
||||||
let endpoint = uri.file_create(share_id);
|
let endpoint = uri.file_create(share_id);
|
||||||
|
|
||||||
|
|
@ -122,14 +123,14 @@ impl sharry::Client for ureq::Agent {
|
||||||
.map_err(find_cause(uri, alias_id, Some(share_id), None))?;
|
.map_err(find_cause(uri, alias_id, Some(share_id), None))?;
|
||||||
|
|
||||||
trace!("{endpoint:?} response: {res:?}");
|
trace!("{endpoint:?} response: {res:?}");
|
||||||
ClientError::res_status_check(res.status(), ureq::http::StatusCode::CREATED)?;
|
error::Error::res_status_check(res.status(), ureq::http::StatusCode::CREATED)?;
|
||||||
res
|
res
|
||||||
};
|
};
|
||||||
|
|
||||||
let location = (res.headers().get("Location"))
|
let location = (res.headers().get("Location"))
|
||||||
.ok_or_else(|| ClientError::response("Location header not found"))?
|
.ok_or_else(|| error::Error::response("Location header not found"))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(ClientError::response)?
|
.map_err(error::Error::response)?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let file_id = Self::get_file_id(&location)?;
|
let file_id = Self::get_file_id(&location)?;
|
||||||
|
|
@ -145,7 +146,7 @@ impl sharry::Client for ureq::Agent {
|
||||||
alias_id: &str,
|
alias_id: &str,
|
||||||
share_id: &str,
|
share_id: &str,
|
||||||
chunk: &file::Chunk,
|
chunk: &file::Chunk,
|
||||||
) -> sharry::Result<()> {
|
) -> error::Result<()> {
|
||||||
let res = {
|
let res = {
|
||||||
let endpoint = uri.file_patch(share_id, chunk.get_file_id());
|
let endpoint = uri.file_patch(share_id, chunk.get_file_id());
|
||||||
|
|
||||||
|
|
@ -162,21 +163,21 @@ impl sharry::Client for ureq::Agent {
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
trace!("{endpoint:?} response: {res:?}");
|
trace!("{endpoint:?} response: {res:?}");
|
||||||
ClientError::res_status_check(res.status(), ureq::http::StatusCode::NO_CONTENT)?;
|
error::Error::res_status_check(res.status(), ureq::http::StatusCode::NO_CONTENT)?;
|
||||||
res
|
res
|
||||||
};
|
};
|
||||||
|
|
||||||
let res_offset = (res.headers().get("Upload-Offset"))
|
let res_offset = (res.headers().get("Upload-Offset"))
|
||||||
.ok_or_else(|| ClientError::response("Upload-Offset header not found"))?
|
.ok_or_else(|| error::Error::response("Upload-Offset header not found"))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(ClientError::response)?
|
.map_err(error::Error::response)?
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
.map_err(ClientError::response)?;
|
.map_err(error::Error::response)?;
|
||||||
|
|
||||||
if chunk.get_behind() == res_offset {
|
if chunk.get_behind() == res_offset {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ClientError::response(format!(
|
Err(error::Error::response(format!(
|
||||||
"Unexpected Upload-Offset: {} (expected {})",
|
"Unexpected Upload-Offset: {} (expected {})",
|
||||||
res_offset,
|
res_offset,
|
||||||
chunk.get_behind()
|
chunk.get_behind()
|
||||||
|
|
|
||||||
14
src/lib.rs
Normal file
14
src/lib.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
// TODO fix with documentation
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
|
||||||
|
mod appstate;
|
||||||
|
mod cachefile;
|
||||||
|
mod cli;
|
||||||
|
pub mod error;
|
||||||
|
mod file;
|
||||||
|
mod impl_ureq;
|
||||||
|
pub mod output;
|
||||||
|
mod sharry;
|
||||||
|
|
||||||
|
pub use appstate::AppState;
|
||||||
|
pub use cli::Cli;
|
||||||
|
|
@ -5,12 +5,11 @@ use dialoguer::{Select, theme::ColorfulTheme};
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
|
||||||
use crate::sharry;
|
|
||||||
|
|
||||||
type StaticStyled<'t> = LazyLock<StyledObject<&'t str>>;
|
type StaticStyled<'t> = LazyLock<StyledObject<&'t str>>;
|
||||||
|
|
||||||
pub static SHRUPL: StaticStyled = LazyLock::new(|| style("ShrUpl").yellow().bold());
|
pub static SHRUPL: StaticStyled = LazyLock::new(|| style("ShrUpl").yellow().bold());
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn prompt_continue() -> bool {
|
pub fn prompt_continue() -> bool {
|
||||||
let prompt = format!(
|
let prompt = format!(
|
||||||
"This operation has previously been stopped. {}",
|
"This operation has previously been stopped. {}",
|
||||||
|
|
@ -44,6 +43,8 @@ where
|
||||||
strs.iter().map(|&s| f(style(s)).to_string()).collect()
|
strs.iter().map(|&s| f(style(s)).to_string()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn new_progressbar() -> ProgressBar {
|
pub fn new_progressbar() -> ProgressBar {
|
||||||
ProgressBar::hidden().with_style(
|
ProgressBar::hidden().with_style(
|
||||||
ProgressStyle::with_template(&format!(
|
ProgressStyle::with_template(&format!(
|
||||||
|
|
@ -74,7 +75,7 @@ impl Log {
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(e: &sharry::ClientError) {
|
pub fn handle(e: &crate::error::Error) {
|
||||||
if e.is_fatal() {
|
if e.is_fatal() {
|
||||||
// react to fatal error
|
// react to fatal error
|
||||||
error!("fatal error: {e:?}");
|
error!("fatal error: {e:?}");
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
use std::{fmt, sync::LazyLock};
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::file;
|
use crate::{error, file};
|
||||||
|
|
||||||
use super::api::{NewShareRequest, Uri};
|
use super::api::{NewShareRequest, Uri};
|
||||||
|
|
||||||
pub trait Client {
|
pub trait Client {
|
||||||
fn get_file_id(uri: &str) -> super::Result<&str> {
|
fn get_file_id(uri: &str) -> error::Result<&str> {
|
||||||
/// Pattern breakdown:
|
/// Pattern breakdown:
|
||||||
/// - `^([^:/?#]+)://` – scheme (anything but `:/?#`) + `"://"`
|
/// - `^([^:/?#]+)://` – scheme (anything but `:/?#`) + `"://"`
|
||||||
/// - `([^/?#]+)` – authority/host (anything but `/?#`)
|
/// - `([^/?#]+)` – authority/host (anything but `/?#`)
|
||||||
|
|
@ -33,7 +32,7 @@ pub trait Client {
|
||||||
{
|
{
|
||||||
Ok(fid)
|
Ok(fid)
|
||||||
} else {
|
} else {
|
||||||
Err(super::ClientError::unknown(format!(
|
Err(error::Error::unknown(format!(
|
||||||
"Could not extract File ID from {uri:?}"
|
"Could not extract File ID from {uri:?}"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
@ -44,9 +43,9 @@ pub trait Client {
|
||||||
uri: &Uri,
|
uri: &Uri,
|
||||||
alias_id: &str,
|
alias_id: &str,
|
||||||
data: NewShareRequest,
|
data: NewShareRequest,
|
||||||
) -> super::Result<String>;
|
) -> error::Result<String>;
|
||||||
|
|
||||||
fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> super::Result<()>;
|
fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> error::Result<()>;
|
||||||
|
|
||||||
fn file_create(
|
fn file_create(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -54,7 +53,7 @@ pub trait Client {
|
||||||
alias_id: &str,
|
alias_id: &str,
|
||||||
share_id: &str,
|
share_id: &str,
|
||||||
file: &file::Checked,
|
file: &file::Checked,
|
||||||
) -> super::Result<String>;
|
) -> error::Result<String>;
|
||||||
|
|
||||||
fn file_patch(
|
fn file_patch(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -62,7 +61,7 @@ pub trait Client {
|
||||||
alias_id: &str,
|
alias_id: &str,
|
||||||
share_id: &str,
|
share_id: &str,
|
||||||
chunk: &file::Chunk,
|
chunk: &file::Chunk,
|
||||||
) -> super::Result<()>;
|
) -> error::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move into tests subdir
|
// TODO move into tests subdir
|
||||||
|
|
@ -85,75 +84,3 @@ pub trait Client {
|
||||||
// assert!(Client::get_file_id(bad).is_err());
|
// assert!(Client::get_file_id(bad).is_err());
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Parameter {
|
|
||||||
#[error("given URI {0:?}")]
|
|
||||||
Uri(String),
|
|
||||||
|
|
||||||
#[error("given Alias ID {0:?}")]
|
|
||||||
AliasID(String),
|
|
||||||
|
|
||||||
#[error("stored Share ID {0:?}")]
|
|
||||||
ShareID(String),
|
|
||||||
|
|
||||||
#[error("stored File ID {0:?}")]
|
|
||||||
FileID(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parameter {
|
|
||||||
fn is_fatal(&self) -> bool {
|
|
||||||
matches!(self, Self::Uri(_) | Self::AliasID(_))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum ClientError {
|
|
||||||
#[error(transparent)]
|
|
||||||
StdIo(#[from] std::io::Error),
|
|
||||||
|
|
||||||
#[error("response error: {0}")]
|
|
||||||
Response(String),
|
|
||||||
|
|
||||||
#[error("Invalid {0}")]
|
|
||||||
InvalidParameter(Parameter),
|
|
||||||
|
|
||||||
#[error("{0}")]
|
|
||||||
Unknown(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
fn into_string(val: impl ToString) -> String {
|
|
||||||
val.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClientError {
|
|
||||||
pub fn res_status_check<T>(actual: T, expected: T) -> super::Result<()>
|
|
||||||
where
|
|
||||||
T: PartialEq + fmt::Display + Copy,
|
|
||||||
{
|
|
||||||
if actual == expected {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(Self::Response(format!(
|
|
||||||
"unexpected status: {actual} (expected {expected})"
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn response(e: impl ToString) -> Self {
|
|
||||||
Self::Response(into_string(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unknown(e: impl ToString) -> Self {
|
|
||||||
Self::Unknown(into_string(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_fatal(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::InvalidParameter(p) => p.is_fatal(),
|
|
||||||
Self::Unknown(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,4 @@ mod api;
|
||||||
mod client;
|
mod client;
|
||||||
|
|
||||||
pub use api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri};
|
pub use api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri};
|
||||||
pub use client::{Client, ClientError, Parameter};
|
pub use client::Client;
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, ClientError>;
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue