Add file adding/deleting/archiving functions

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/2/head
trivernis 3 years ago
parent 9fe17d4c90
commit 968156aa7d
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -10,7 +10,12 @@ edition = "2018"
serde = "^1.0" serde = "^1.0"
serde_derive = "^1.0" serde_derive = "^1.0"
reqwest = {version = "0.11.4", features = ["json"]} reqwest = {version = "0.11.4", features = ["json"]}
log = "0.4.14"
[dev-dependencies]
env_logger = "0.8.4"
lazy_static = "1.4.0"
[dev-dependencies.tokio] [dev-dependencies.tokio]
version = "1.8.0" version = "1.8.0"
features = ["macros", "rt-multi-thread"] features = ["macros", "rt-multi-thread"]

@ -1,8 +1,14 @@
use crate::error::Result; use crate::error::{Error, Result};
use crate::paths::access_management::{ use crate::paths::access_management::{
ApiVersionResponse, GetServicesResponse, SessionKeyResponse, VerifyAccessKeyResponse, ApiVersionResponse, GetServicesResponse, SessionKeyResponse, VerifyAccessKeyResponse,
}; };
use crate::paths::adding_files::{
AddFileRequest, AddFileResponse, ArchiveFilesRequest, ArchiveFilesResponse, DeleteFilesRequest,
DeleteFilesResponse, UnarchiveFilesRequest, UnarchiveFilesResponse, UndeleteFilesRequest,
UndeleteFilesResponse,
};
use crate::paths::Path; use crate::paths::Path;
use reqwest::Response;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
@ -24,53 +30,139 @@ impl Client {
} }
/// Starts a get request to the path associated with the return type /// Starts a get request to the path associated with the return type
async fn get<T: DeserializeOwned + Path, Q: Serialize + ?Sized>( async fn get_and_parse<T: DeserializeOwned + Path, Q: Serialize + ?Sized>(
&mut self, &mut self,
query: &Q, query: &Q,
) -> Result<T> { ) -> Result<T> {
let response: T = self let response = self
.inner .inner
.get(format!("{}/{}", self.base_url, T::get_path())) .get(format!("{}/{}", self.base_url, T::get_path()))
.header(ACCESS_KEY_HEADER, &self.access_key) .header(ACCESS_KEY_HEADER, &self.access_key)
.query(query) .query(query)
.send() .send()
.await?
.json()
.await?; .await?;
Ok(response) let response = Self::extract_error(response).await?;
Self::extract_content(response).await
} }
/// Stats a post request to the path associated with the return type /// Stats a post request to the path associated with the return type
async fn post<T: DeserializeOwned + Path, B: Serialize>(&mut self, body: B) -> Result<T> { async fn post<T: Path, B: Serialize>(&mut self, body: B) -> Result<Response> {
let response: T = self let response = self
.inner .inner
.post(format!("{}/{}", self.base_url, T::get_path())) .post(format!("{}/{}", self.base_url, T::get_path()))
.json(&body) .json(&body)
.header(ACCESS_KEY_HEADER, &self.access_key) .header(ACCESS_KEY_HEADER, &self.access_key)
.send() .send()
.await?
.json()
.await?; .await?;
let response = Self::extract_error(response).await?;
Ok(response) Ok(response)
} }
/// Stats a post request and parses the body as json
async fn post_and_parse<T: DeserializeOwned + Path, B: Serialize>(
&mut self,
body: B,
) -> Result<T> {
let response = self.post::<T, B>(body).await?;
Self::extract_content(response).await
}
/// Stats a post request to the path associated with the return type
async fn post_binary<T: DeserializeOwned + Path>(&mut self, data: Vec<u8>) -> Result<T> {
let response = self
.inner
.post(format!("{}/{}", self.base_url, T::get_path()))
.body(data)
.header(ACCESS_KEY_HEADER, &self.access_key)
.header("Content-Type", "application/octet-stream")
.send()
.await?;
let response = Self::extract_error(response).await?;
Self::extract_content(response).await
}
/// Returns an error with the response text content if the status doesn't indicate success
async fn extract_error(response: Response) -> Result<Response> {
if !response.status().is_success() {
let msg = response.text().await?;
Err(Error::Hydrus(msg))
} else {
Ok(response)
}
}
/// Parses the response as JSOn
async fn extract_content<T: DeserializeOwned>(response: Response) -> Result<T> {
response.json::<T>().await.map_err(Error::from)
}
/// Returns the current API version. It's being incremented every time the API changes. /// Returns the current API version. It's being incremented every time the API changes.
pub async fn api_version(&mut self) -> Result<ApiVersionResponse> { pub async fn api_version(&mut self) -> Result<ApiVersionResponse> {
self.get(&()).await self.get_and_parse(&()).await
} }
/// Creates a new session key /// Creates a new session key
pub async fn session_key(&mut self) -> Result<SessionKeyResponse> { pub async fn session_key(&mut self) -> Result<SessionKeyResponse> {
self.get(&()).await self.get_and_parse(&()).await
} }
/// Verifies if the access key is valid and returns some information about its permissions /// Verifies if the access key is valid and returns some information about its permissions
pub async fn verify_access_key(&mut self) -> Result<VerifyAccessKeyResponse> { pub async fn verify_access_key(&mut self) -> Result<VerifyAccessKeyResponse> {
self.get(&()).await self.get_and_parse(&()).await
} }
/// Returns the list of tag and file services of the client /// Returns the list of tag and file services of the client
pub async fn get_services(&mut self) -> Result<GetServicesResponse> { pub async fn get_services(&mut self) -> Result<GetServicesResponse> {
self.get(&()).await self.get_and_parse(&()).await
}
/// Adds a file to hydrus
pub async fn add_file<S: AsRef<str>>(&mut self, path: S) -> Result<AddFileResponse> {
self.post_and_parse(AddFileRequest {
path: path.as_ref().to_string(),
})
.await
}
/// Adds a file from binary data to hydrus
pub async fn add_binary_file(&mut self, data: Vec<u8>) -> Result<AddFileResponse> {
self.post_binary(data).await
}
/// Moves files with matching hashes to the trash
pub async fn delete_files(&mut self, hashes: Vec<String>) -> Result<()> {
self.post::<DeleteFilesResponse, DeleteFilesRequest>(DeleteFilesRequest { hashes })
.await?;
Ok(())
}
/// Pulls files out of the trash by hash
pub async fn undelete_files(&mut self, hashes: Vec<String>) -> Result<()> {
self.post::<UndeleteFilesResponse, UndeleteFilesRequest>(UndeleteFilesRequest { hashes })
.await?;
Ok(())
}
/// Moves files from the inbox into the archive
pub async fn archive_files(&mut self, hashes: Vec<String>) -> Result<()> {
self.post::<ArchiveFilesResponse, ArchiveFilesRequest>(ArchiveFilesRequest { hashes })
.await?;
Ok(())
}
/// Moves files from the archive into the inbox
pub async fn unarchive_files(&mut self, hashes: Vec<String>) -> Result<()> {
self.post::<UnarchiveFilesResponse, UnarchiveFilesRequest>(UnarchiveFilesRequest {
hashes,
})
.await?;
Ok(())
} }
} }

@ -6,12 +6,14 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
Reqwest(reqwest::Error), Reqwest(reqwest::Error),
Hydrus(String),
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Reqwest(e) => {e.fmt(f)} Self::Reqwest(e) => e.fmt(f),
Self::Hydrus(msg) => msg.fmt(f),
} }
} }
} }
@ -20,6 +22,7 @@ impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> { fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self { match self {
Self::Reqwest(e) => e.source(), Self::Reqwest(e) => e.source(),
Self::Hydrus(_) => None,
} }
} }
} }
@ -28,4 +31,4 @@ impl From<reqwest::Error> for Error {
fn from(e: reqwest::Error) -> Self { fn from(e: reqwest::Error) -> Self {
Self::Reqwest(e) Self::Reqwest(e)
} }
} }

@ -2,7 +2,7 @@ use crate::paths::common::BasicServiceInfo;
use crate::paths::Path; use crate::paths::Path;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct ApiVersionResponse { pub struct ApiVersionResponse {
pub version: u32, pub version: u32,
pub hydrus_version: u32, pub hydrus_version: u32,
@ -14,7 +14,7 @@ impl Path for ApiVersionResponse {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct SessionKeyResponse { pub struct SessionKeyResponse {
pub session_key: String, pub session_key: String,
} }
@ -25,7 +25,7 @@ impl Path for SessionKeyResponse {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct VerifyAccessKeyResponse { pub struct VerifyAccessKeyResponse {
pub basic_permissions: Vec<u32>, pub basic_permissions: Vec<u32>,
pub human_description: String, pub human_description: String,
@ -37,7 +37,7 @@ impl Path for VerifyAccessKeyResponse {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Deserialize)]
pub struct GetServicesResponse(pub HashMap<String, Vec<BasicServiceInfo>>); pub struct GetServicesResponse(pub HashMap<String, Vec<BasicServiceInfo>>);
impl Path for GetServicesResponse { impl Path for GetServicesResponse {

@ -0,0 +1,56 @@
use crate::paths::common::BasicHashList;
use crate::paths::Path;
#[derive(Debug, Clone, Serialize)]
pub struct AddFileRequest {
pub path: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AddFileResponse {
pub status: u8,
pub hash: String,
pub note: String,
}
impl Path for AddFileResponse {
fn get_path() -> String {
String::from("add_files/add_file")
}
}
pub type DeleteFilesRequest = BasicHashList;
pub struct DeleteFilesResponse;
impl Path for DeleteFilesResponse {
fn get_path() -> String {
String::from("add_files/delete_files")
}
}
pub type UndeleteFilesRequest = BasicHashList;
pub struct UndeleteFilesResponse;
impl Path for UndeleteFilesResponse {
fn get_path() -> String {
String::from("add_files/undelete_files")
}
}
pub type ArchiveFilesRequest = BasicHashList;
pub struct ArchiveFilesResponse;
impl Path for ArchiveFilesResponse {
fn get_path() -> String {
String::from("add_files/archive_files")
}
}
pub type UnarchiveFilesRequest = BasicHashList;
pub struct UnarchiveFilesResponse;
impl Path for UnarchiveFilesResponse {
fn get_path() -> String {
String::from("add_files/unarchive_files")
}
}

@ -2,4 +2,9 @@
pub struct BasicServiceInfo { pub struct BasicServiceInfo {
pub name: String, pub name: String,
pub service_key: String, pub service_key: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BasicHashList {
pub hashes: Vec<String>,
}

@ -1,6 +1,7 @@
pub mod access_management; pub mod access_management;
pub mod adding_files;
pub mod common; pub mod common;
pub trait Path { pub trait Path {
fn get_path() -> String; fn get_path() -> String;
} }

@ -1,6 +1,23 @@
use hydrus_api::client::Client; use hydrus_api::client::Client;
use log::LevelFilter;
use std::env; use std::env;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
pub fn setup() {
lazy_static::lazy_static! { static ref SETUP_DONE: Arc<AtomicBool> = Arc::new(AtomicBool::new(false)); }
if !SETUP_DONE.swap(true, Ordering::SeqCst) {
env_logger::builder()
.filter_level(LevelFilter::Trace)
.init();
}
}
pub fn get_client() -> Client { pub fn get_client() -> Client {
Client::new(env::var("HYDRUS_URL").unwrap(), env::var("HYDRUS_ACCESS_KEY").unwrap()).unwrap() setup();
} Client::new(
env::var("HYDRUS_URL").unwrap(),
env::var("HYDRUS_ACCESS_KEY").unwrap(),
)
.unwrap()
}

@ -0,0 +1,42 @@
mod common;
#[tokio::test]
async fn it_adds_files() {
let mut client = common::get_client();
let result = client.add_file("/does/not/exist").await;
assert!(result.is_err()); // because the path does not exist
}
#[tokio::test]
async fn it_adds_binary_files() {
let mut client = common::get_client();
let result = client
.add_binary_file(vec![0u8, 0u8, 0u8, 0u8])
.await
.unwrap();
assert_eq!(result.status, 4); // should fail because the filetype is unknown
}
#[tokio::test]
async fn it_deletes_files() {
let mut client = common::get_client();
client.delete_files(vec![]).await.unwrap();
}
#[tokio::test]
async fn it_undeletes_files() {
let mut client = common::get_client();
client.undelete_files(vec![]).await.unwrap();
}
#[tokio::test]
async fn it_archives_files() {
let mut client = common::get_client();
client.archive_files(vec![]).await.unwrap();
}
#[tokio::test]
async fn it_unarchives_files() {
let mut client = common::get_client();
client.unarchive_files(vec![]).await.unwrap();
}
Loading…
Cancel
Save