Add additional options to file deletion

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/15/head
trivernis 2 years ago
parent 5dce13508c
commit 039d510a61
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -1,5 +1,6 @@
use crate::api_core::common::BasicHashList;
use crate::api_core::common::{BasicHashList, ServiceIdentifier};
use crate::api_core::Endpoint;
use serde::Serialize;
pub static STATUS_IMPORT_SUCCESS: u8 = 1;
pub static STATUS_IMPORT_ALREADY_EXISTS: u8 = 2;
@ -30,7 +31,42 @@ impl Endpoint for AddFile {
}
}
pub type DeleteFilesRequest = BasicHashList;
#[derive(Clone, Debug, Serialize)]
pub struct DeleteFilesRequest {
/// The files by hashes to delete
pub hashes: Vec<String>,
/// The files by file ids to delete
pub file_ids: Vec<u64>,
pub file_service_name: Option<String>,
pub file_service_key: Option<String>,
pub reason: Option<String>,
}
impl DeleteFilesRequest {
pub fn new(hashes: Vec<String>, file_ids: Vec<u64>) -> Self {
Self {
hashes,
file_ids,
file_service_key: None,
file_service_name: None,
reason: None,
}
}
/// Sets the service to delete from. If none is given it deletes
/// from all files.
pub fn set_service(&mut self, service: ServiceIdentifier) {
match service {
ServiceIdentifier::Name(name) => self.file_service_name = Some(name),
ServiceIdentifier::Key(key) => self.file_service_key = Some(key),
}
}
/// Sets the reason for deletion
pub fn set_reason<S: ToString>(&mut self, reason: S) {
self.reason = Some(reason.to_string());
}
}
pub struct DeleteFiles;

@ -106,9 +106,8 @@ impl Client {
/// Moves files with matching hashes to the trash
#[tracing::instrument(skip(self), level = "debug")]
pub async fn delete_files(&self, hashes: Vec<String>) -> Result<()> {
self.post::<DeleteFiles>(DeleteFilesRequest { hashes })
.await?;
pub async fn delete_files(&self, request: DeleteFilesRequest) -> Result<()> {
self.post::<DeleteFiles>(request).await?;
Ok(())
}
@ -180,7 +179,7 @@ impl Client {
) -> Result<SearchFileHashesResponse> {
let mut args = options.into_query_args();
args.push(("tags", Self::serialize_query_object(query)?));
args.push(("return_hashes", String::from("true")));
args.push(("return_hashes", Self::serialize_query_object(true)?));
self.get_and_parse::<SearchFileHashes, [(&str, String)]>(&args)
.await
}
@ -432,11 +431,13 @@ impl Client {
fn serialize_query_object<S: Serialize>(obj: S) -> Result<String> {
#[cfg(feature = "json")]
{
tracing::trace!("Serializing query to JSON");
serde_json::ser::to_string(&obj).map_err(|e| Error::Serialization(e.to_string()))
}
#[cfg(feature = "cbor")]
{
tracing::trace!("Serializing query to CBOR");
let mut buf = Vec::new();
ciborium::ser::into_writer(&obj, &mut buf)
.map_err(|e| Error::Serialization(e.to_string()))?;
@ -471,11 +472,19 @@ impl Client {
#[tracing::instrument(skip(body), level = "trace")]
fn serialize_body<S: Serialize>(body: S) -> Result<Vec<u8>> {
let mut buf = Vec::new();
#[cfg(feature = "cbor")]
ciborium::ser::into_writer(&body, &mut buf)
.map_err(|e| Error::Serialization(e.to_string()))?;
#[cfg(feature = "json")]
serde_json::to_writer(&mut buf, &body).map_err(|e| Error::Serialization(e.to_string()))?;
{
tracing::trace!("Serializing body to JSON");
serde_json::to_writer(&mut buf, &body)
.map_err(|e| Error::Serialization(e.to_string()))?;
}
#[cfg(feature = "cbor")]
{
tracing::trace!("Serializing body to CBOR");
ciborium::ser::into_writer(&body, &mut buf)
.map_err(|e| Error::Serialization(e.to_string()))?;
}
Ok(buf)
}
@ -525,11 +534,16 @@ impl Client {
let bytes = response.bytes().await?;
let reader = bytes.reader();
#[cfg(feature = "json")]
let content = serde_json::from_reader::<_, T>(reader)
.map_err(|e| Error::Deserialization(e.to_string()))?;
let content = {
tracing::trace!("Deserializing content from JSON");
serde_json::from_reader::<_, T>(reader)
.map_err(|e| Error::Deserialization(e.to_string()))?
};
#[cfg(feature = "cbor")]
let content =
ciborium::de::from_reader(reader).map_err(|e| Error::Deserialization(e.to_string()))?;
let content = {
tracing::trace!("Deserializing content from CBOR");
ciborium::de::from_reader(reader).map_err(|e| Error::Deserialization(e.to_string()))?
};
tracing::trace!("response content: {:?}", content);
Ok(content)

@ -0,0 +1,72 @@
use crate::api_core::adding_files::DeleteFilesRequest;
use crate::api_core::common::{FileIdentifier, ServiceIdentifier};
use crate::error::Result;
use crate::Client;
pub struct DeleteFilesBuilder {
client: Client,
hashes: Vec<String>,
ids: Vec<u64>,
reason: Option<String>,
service: Option<ServiceIdentifier>,
}
impl DeleteFilesBuilder {
pub(crate) fn new(client: Client) -> Self {
Self {
client,
hashes: Vec::new(),
ids: Vec::new(),
reason: None,
service: None,
}
}
/// Adds a file to be deleted
pub fn add_file(mut self, identifier: FileIdentifier) -> Self {
match identifier {
FileIdentifier::ID(id) => self.ids.push(id),
FileIdentifier::Hash(hash) => self.hashes.push(hash),
}
self
}
/// Adds multiple files to be deleted
pub fn add_files(self, ids: Vec<FileIdentifier>) -> Self {
ids.into_iter().fold(self, |acc, id| acc.add_file(id))
}
/// Restricts deletion to a single file service
pub fn service(mut self, service: ServiceIdentifier) -> Self {
self.service = Some(service);
self
}
/// Adds a reason for why the file was deleted
pub fn reason<S: ToString>(mut self, reason: S) -> Self {
self.reason = Some(reason.to_string());
self
}
/// Deletes all files specified in this builder
pub async fn run(self) -> Result<()> {
let mut request = DeleteFilesRequest {
reason: self.reason,
hashes: self.hashes,
file_ids: self.ids,
file_service_key: None,
file_service_name: None,
};
if let Some(service) = self.service {
match service {
ServiceIdentifier::Name(name) => request.file_service_name = Some(name),
ServiceIdentifier::Key(key) => request.file_service_key = Some(key),
}
}
self.client.delete_files(request).await
}
}

@ -1,6 +1,7 @@
pub mod delete_files_builder;
pub mod import_builder;
pub mod notes_builder;
pub mod or_chain_builder;
pub mod search_builder;
pub mod tag_builder;
pub mod tagging_builder;
pub mod notes_builder;

@ -1,6 +1,7 @@
use crate::api_core::common::FileIdentifier;
use crate::error::Result;
use crate::wrapper::address::Address;
use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder;
use crate::wrapper::builders::import_builder::ImportBuilder;
use crate::wrapper::builders::search_builder::SearchBuilder;
use crate::wrapper::builders::tagging_builder::TaggingBuilder;
@ -77,6 +78,11 @@ impl Hydrus {
Ok(HydrusFile::from_metadata(self.client.clone(), metadata))
}
/// Creates a builder to delete files
pub async fn delete(&self) -> DeleteFilesBuilder {
DeleteFilesBuilder::new(self.client.clone())
}
/// Starts a request to bulk add tags to files
pub fn tagging(&self) -> TaggingBuilder {
TaggingBuilder::new(self.client.clone())

@ -2,6 +2,7 @@ use crate::api_core::adding_tags::{AddTagsRequestBuilder, TagAction};
use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord, ServiceIdentifier};
use crate::error::{Error, Result};
use crate::utils::tag_list_to_string_list;
use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder;
use crate::wrapper::builders::notes_builder::AddNotesBuilder;
use crate::wrapper::service::ServiceName;
use crate::wrapper::tag::Tag;
@ -229,6 +230,19 @@ impl HydrusFile {
Ok(naive_time_deleted)
}
/// Creates a request builder to delete the file
pub fn delete(&mut self) -> DeleteFilesBuilder {
self.metadata = None;
DeleteFilesBuilder::new(self.client.clone()).add_file(self.id.clone())
}
/// Undeletes the file
pub async fn undelete(&mut self) -> Result<()> {
let hash = self.hash().await?;
self.metadata = None;
self.client.undelete_files(vec![hash]).await
}
/// Associates the file with a list of urls
pub async fn associate_urls(&mut self, urls: Vec<String>) -> Result<()> {
let hash = self.hash().await?;

@ -1,6 +1,8 @@
use crate::common;
use crate::common::create_testdata;
use crate::common::test_data::get_test_hashes;
use hydrus_api::api_core::adding_files::DeleteFilesRequest;
use hydrus_api::wrapper::service::ServiceName;
#[tokio::test]
async fn it_adds_files() {
@ -22,7 +24,11 @@ async fn it_adds_binary_files() {
#[tokio::test]
async fn it_deletes_files() {
let client = common::get_client();
client.delete_files(get_test_hashes()).await.unwrap();
create_testdata(&client).await;
let mut delete_request = DeleteFilesRequest::new(get_test_hashes(), vec![]);
delete_request.set_reason("Testing");
delete_request.set_service(ServiceName::my_files().into());
client.delete_files(delete_request).await.unwrap();
}
#[tokio::test]

@ -1,14 +1,18 @@
use super::super::common;
use crate::common::test_data::TEST_HASH_2;
use crate::common::{create_testdata, get_client};
use hydrus_api::api_core::adding_tags::TagAction;
use hydrus_api::api_core::common::FileIdentifier;
use hydrus_api::wrapper::hydrus_file::HydrusFile;
use hydrus_api::wrapper::service::ServiceName;
async fn get_file() -> HydrusFile {
let client = get_client();
create_testdata(&client).await;
let hydrus = common::get_hydrus();
hydrus
.file(FileIdentifier::hash(
"277a138cd1ee79fc1fdb2869c321b848d4861e45b82184487139ef66dd40b62d", // needs to exist
TEST_HASH_2, // needs to exist
))
.await
.unwrap()
@ -102,9 +106,20 @@ async fn it_retrieves_content() {
async fn it_retrieves_metadata() {
let mut file = get_file().await;
assert!(file.dimensions().await.unwrap().is_some());
assert!(file.stored_locally().await.unwrap());
assert!(file.duration().await.unwrap().is_none());
assert!(file.time_modified().await.is_ok());
assert!(file.time_deleted("000").await.is_ok());
assert!(file.time_imported("000").await.is_ok());
}
#[tokio::test]
async fn it_deletes() {
let mut file = get_file().await;
file.delete()
.reason("I just don't like that file")
.service(ServiceName::all_local_files().into())
.run()
.await
.unwrap();
file.undelete().await.unwrap();
}

Loading…
Cancel
Save