diff --git a/Cargo.lock b/Cargo.lock index 730eae8..f69e4e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -971,9 +971,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] diff --git a/src/common/file_helper.rs b/src/common/file_helper.rs index 259283a..15d38df 100644 --- a/src/common/file_helper.rs +++ b/src/common/file_helper.rs @@ -1,7 +1,7 @@ -use std::io; +use mktemp::Temp; use std::fs::File; +use std::io; use std::path::PathBuf; -use mktemp::Temp; pub fn stdin_to_file() -> Result { let tmp_file = Temp::new_file()?; @@ -16,7 +16,7 @@ pub fn open_file(path: &Option) -> Result<(File, PathBuf), io::Error> { Some(path) => { let file = File::open(path)?; Ok((file, path.clone())) - }, + } None => { let tmp_file = stdin_to_file()?; let path = tmp_file.as_ref().to_path_buf(); diff --git a/src/common/mod.rs b/src/common/mod.rs index 41a8fc8..1fdb131 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -2,6 +2,7 @@ pub mod account_archive; pub mod delegate; pub mod drive_file; pub mod empty_file; +pub mod file_helper; pub mod file_info; pub mod file_tree; pub mod file_tree_drive; @@ -10,4 +11,3 @@ pub mod id_gen; pub mod md5_writer; pub mod permission; pub mod table; -pub mod file_helper; diff --git a/src/files.rs b/src/files.rs index 89c968b..230262a 100644 --- a/src/files.rs +++ b/src/files.rs @@ -9,6 +9,8 @@ pub mod list; pub mod mkdir; pub mod mv; pub mod rename; +pub mod trash; +pub mod untrash; pub mod update; pub mod upload; @@ -23,5 +25,7 @@ pub use list::list; pub use mkdir::mkdir; pub use mv::mv; pub use rename::rename; +pub use trash::trash; +pub use untrash::untrash; pub use update::update; pub use upload::upload; diff --git a/src/files/info.rs b/src/files/info.rs index 7be77a2..8bc5352 100644 --- a/src/files/info.rs +++ b/src/files/info.rs @@ -39,7 +39,7 @@ pub async fn get_file( let (_, file) = hub .files() .get(file_id) - .param("fields", "id,name,size,createdTime,modifiedTime,md5Checksum,mimeType,parents,shared,description,webContentLink,webViewLink,shortcutDetails(targetId,targetMimeType)") + .param("fields", "id,name,size,createdTime,modifiedTime,md5Checksum,mimeType,parents,shared,description,webContentLink,webViewLink,shortcutDetails(targetId,targetMimeType),trashed,trashedTime") .supports_all_drives(true) .add_scope(google_drive3::api::Scope::Full) .doit() @@ -108,6 +108,18 @@ pub fn prepare_fields(file: &google_drive3::api::File, config: &DisplayConfig) - name: String::from("ViewUrl"), value: file.web_view_link.clone(), }, + Field { + name: String::from("Trashed"), + value: if file.trashed.is_some() && file.trashed.unwrap() {Some(format_bool(file.trashed.unwrap()))} else {None}, + }, + Field { + name: String::from("TrashedTime"), + value: file.trashed_time.map(format_date_time), + }, + Field { + name: String::from("TrashingUser"), + value: file.trashing_user.clone().map(|user| user.display_name.unwrap_or_default()), + }, ] } diff --git a/src/files/list.rs b/src/files/list.rs index aaa1c28..8646b68 100644 --- a/src/files/list.rs +++ b/src/files/list.rs @@ -22,6 +22,7 @@ pub struct Config { pub skip_header: bool, pub truncate_name: bool, pub field_separator: String, + pub skip_trashed: bool, } pub async fn list(config: Config) -> Result<(), Error> { @@ -42,6 +43,12 @@ pub async fn list(config: Config) -> Result<(), Error> { let file_type = simplified_file_type(&file); let file_name = format_file_name(&config, &file); + if config.skip_trashed{ + if file.trashed.is_some_and(|trashed| trashed == true){ + continue; + } + } + values.push([ file.id.unwrap_or_default(), file_name, diff --git a/src/files/trash.rs b/src/files/trash.rs new file mode 100644 index 0000000..58a0f6d --- /dev/null +++ b/src/files/trash.rs @@ -0,0 +1,125 @@ +use crate::common::{drive_file, hub_helper}; +use crate::files::info; +use crate::hub::Hub; +use std::error; +use std::fmt::Display; +use std::fmt::Formatter; + +pub struct Config { + pub file_id: String, + pub trash_directories: bool, +} + +pub async fn trash(config: Config) -> Result<(), Error> { + let hub = hub_helper::get_hub().await.map_err(Error::Hub)?; + + let exists = info::get_file(&hub, &config.file_id) + .await + .map_err(Error::GetFile)?; + + err_if_directory(&exists, &config)?; + + if exists.trashed.is_some_and(|trashed| trashed == true) { + println!("File is already trashed, exiting"); + return Ok(()); + } + + println!("Trashing {}", config.file_id); + + trash_file(&hub, &config.file_id) + .await + .map_err(Error::Update)?; + + println!("File successfully updated"); + + Ok(()) +} + +pub async fn trash_file(hub: &Hub, file_id: &str) -> Result<(), google_drive3::Error> { + let dst_file = google_drive3::api::File { + trashed: Some(true), + ..google_drive3::api::File::default() + }; + + let req = hub + .files() + .update(dst_file, &file_id) + .param("fields", "id,name,size,createdTime,modifiedTime,md5Checksum,mimeType,parents,shared,description,webContentLink,webViewLink") + .add_scope(google_drive3::api::Scope::Full) + .supports_all_drives(true); + + req.doit_without_upload().await?; + + Ok(()) +} + +#[derive(Debug)] +pub enum Error { + Hub(hub_helper::Error), + GetFile(google_drive3::Error), + Update(google_drive3::Error), + IsDirectory(String), +} + +impl error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::Hub(err) => write!(f, "{}", err), + Error::GetFile(err) => write!(f, "Failed to get file: {}", err), + Error::Update(err) => write!(f, "Failed to trash file: {}", err), + Error::IsDirectory(name) => write!( + f, + "'{}' is a directory, use --recursive to trash directories", + name + ), + } + } +} + +#[derive(Debug, Clone)] +pub struct PatchFile { + id: String, + file: google_drive3::api::File, +} + +impl PatchFile { + pub fn new(id: String) -> Self { + Self { + id, + file: google_drive3::api::File::default(), + } + } + + pub fn with_name(&self, name: &str) -> Self { + Self { + file: google_drive3::api::File { + name: Some(name.to_string()), + ..self.file.clone() + }, + ..self.clone() + } + } + + pub fn id(&self) -> String { + self.id.clone() + } + + pub fn file(&self) -> google_drive3::api::File { + self.file.clone() + } +} + +fn err_if_directory(file: &google_drive3::api::File, config: &Config) -> Result<(), Error> { + if drive_file::is_directory(file) && !config.trash_directories { + let name = file + .name + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_default(); + Err(Error::IsDirectory(name)) + } else { + Ok(()) + } +} \ No newline at end of file diff --git a/src/files/untrash.rs b/src/files/untrash.rs new file mode 100644 index 0000000..427ab7e --- /dev/null +++ b/src/files/untrash.rs @@ -0,0 +1,125 @@ +use crate::common::{drive_file, hub_helper}; +use crate::files::info; +use crate::hub::Hub; +use std::error; +use std::fmt::Display; +use std::fmt::Formatter; + +pub struct Config { + pub file_id: String, + pub untrash_directories: bool, +} + +pub async fn untrash(config: Config) -> Result<(), Error> { + let hub = hub_helper::get_hub().await.map_err(Error::Hub)?; + + let exists = info::get_file(&hub, &config.file_id) + .await + .map_err(Error::GetFile)?; + + err_if_directory(&exists, &config)?; + + if exists.trashed.is_some_and(|trashed| trashed == false) { + println!("File is not trashed, exiting"); + return Ok(()); + } + + println!("Untrashing {}", config.file_id); + + untrash_file(&hub, &config.file_id) + .await + .map_err(Error::Update)?; + + println!("File successfully updated"); + + Ok(()) +} + +pub async fn untrash_file(hub: &Hub, file_id: &str) -> Result<(), google_drive3::Error> { + let dst_file = google_drive3::api::File { + trashed: Some(false), + ..google_drive3::api::File::default() + }; + + let req = hub + .files() + .update(dst_file, &file_id) + .param("fields", "id,name,size,createdTime,modifiedTime,md5Checksum,mimeType,parents,shared,description,webContentLink,webViewLink") + .add_scope(google_drive3::api::Scope::Full) + .supports_all_drives(true); + + req.doit_without_upload().await?; + + Ok(()) +} + +#[derive(Debug)] +pub enum Error { + Hub(hub_helper::Error), + GetFile(google_drive3::Error), + Update(google_drive3::Error), + IsDirectory(String), +} + +impl error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Error::Hub(err) => write!(f, "{}", err), + Error::GetFile(err) => write!(f, "Failed to get file: {}", err), + Error::Update(err) => write!(f, "Failed to update file: {}", err), + Error::IsDirectory(name) => write!( + f, + "'{}' is a directory, use --recursive to trash directories", + name + ), + } + } +} + +#[derive(Debug, Clone)] +pub struct PatchFile { + id: String, + file: google_drive3::api::File, +} + +impl PatchFile { + pub fn new(id: String) -> Self { + Self { + id, + file: google_drive3::api::File::default(), + } + } + + pub fn with_name(&self, name: &str) -> Self { + Self { + file: google_drive3::api::File { + name: Some(name.to_string()), + ..self.file.clone() + }, + ..self.clone() + } + } + + pub fn id(&self) -> String { + self.id.clone() + } + + pub fn file(&self) -> google_drive3::api::File { + self.file.clone() + } +} + +fn err_if_directory(file: &google_drive3::api::File, config: &Config) -> Result<(), Error> { + if drive_file::is_directory(file) && !config.untrash_directories { + let name = file + .name + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_default(); + Err(Error::IsDirectory(name)) + } else { + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index b309066..62e21e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -152,6 +152,10 @@ enum FileCommand { /// Field separator #[arg(long, default_value_t = String::from("\t"))] field_separator: String, + + /// Skip trashed files + #[arg(long)] + skip_trashed: bool, }, /// Download file @@ -249,6 +253,26 @@ enum FileCommand { recursive: bool, }, + /// Trash file + Trash { + /// File id + file_id: String, + + /// Trash directory and all it's content + #[arg(long)] + recursive: bool, + }, + + /// Untrash file + Untrash { + /// File id + file_id: String, + + /// Untrash directory and all it's content + #[arg(long)] + recursive: bool, + }, + /// Create directory Mkdir { /// Name @@ -471,6 +495,7 @@ async fn main() { skip_header, full_name, field_separator, + skip_trashed, } => { let parent_query = parent.map(|folder_id| ListQuery::FilesInFolder { folder_id }); @@ -486,6 +511,7 @@ async fn main() { skip_header, truncate_name: !full_name, field_separator, + skip_trashed, }) .await .unwrap_or_else(handle_error) @@ -579,6 +605,25 @@ async fn main() { .await .unwrap_or_else(handle_error) } + FileCommand::Trash { file_id, recursive } => { + // fmt + files::trash(files::trash::Config { + file_id, + trash_directories: recursive, + }) + .await + .unwrap_or_else(handle_error) + } + + FileCommand::Untrash { file_id, recursive } => { + // fmt + files::untrash(files::untrash::Config { + file_id, + untrash_directories: recursive + }) + .await + .unwrap_or_else(handle_error) + } FileCommand::Mkdir { name,