Skip to main content

wekan_cli/commands/
attachments.rs

1mod delete;
2mod download;
3mod get;
4mod list;
5#[cfg(test)]
6mod tests;
7mod transfer;
8mod upload;
9
10use crate::{
11    client::WekanClientFactory, command_result::CommandSuccess, credentials::CredentialStore,
12    error::AppError, input::ConfirmationProvider,
13};
14use clap::{Args, Subcommand};
15
16#[derive(Debug, Args)]
17pub struct AttachmentArgs {
18    #[command(subcommand)]
19    pub command: AttachmentCommand,
20}
21
22#[derive(Debug, Subcommand)]
23pub enum AttachmentCommand {
24    /// List attachments. Wekan v11.06 ignores deeper selectors and returns all board attachments.
25    List(list::ListArgs),
26    /// Read attachment metadata and versions.
27    Get(get::GetArgs),
28    /// Upload a file or stdin (-). Pinned Wekan JSON uploads return HTTP 408.
29    Upload(upload::UploadArgs),
30    /// Download an attachment to a file or raw stdout (-).
31    Download(download::DownloadArgs),
32    /// Copy an attachment to a card. Pinned Wekan JSON copies return HTTP 408.
33    Copy(transfer::TransferArgs),
34    /// Move an attachment to a card. Pinned Wekan JSON moves return HTTP 408.
35    Move(transfer::TransferArgs),
36    /// Permanently delete an attachment; requires confirmation or --yes.
37    Delete(delete::DeleteArgs),
38    /// Upload and select a board background. Pinned Wekan JSON uploads return HTTP 408.
39    UploadBackground(upload::BackgroundArgs),
40    /// Download the current board background to a file or raw stdout (-).
41    DownloadBackground(download::BackgroundArgs),
42}
43
44impl AttachmentCommand {
45    pub(crate) fn validate_input(&self) -> Result<(), AppError> {
46        match self {
47            Self::Upload(a) => {
48                a.source.name()?;
49            }
50            Self::UploadBackground(a) => {
51                a.source.name()?;
52            }
53            _ => {}
54        }
55        Ok(())
56    }
57    pub(crate) fn download_destination(&self) -> Option<&download::DestinationArgs> {
58        match self {
59            Self::Download(a) => Some(&a.destination),
60            Self::DownloadBackground(a) => Some(&a.destination),
61            _ => None,
62        }
63    }
64}
65
66pub(crate) async fn dispatch(
67    command: AttachmentCommand,
68    factory: &WekanClientFactory,
69    store: &dyn CredentialStore,
70    confirmation: &dyn ConfirmationProvider,
71) -> Result<CommandSuccess, AppError> {
72    match command {
73        AttachmentCommand::List(a) => list::execute(a, factory, store).await,
74        AttachmentCommand::Get(a) => get::execute(a, factory, store).await,
75        AttachmentCommand::Upload(a) => upload::execute(a, factory, store).await,
76        AttachmentCommand::UploadBackground(a) => upload::background(a, factory, store).await,
77        AttachmentCommand::Download(a) => {
78            download::execute(Some(a.attachment_id), None, a.destination, factory, store).await
79        }
80        AttachmentCommand::DownloadBackground(a) => {
81            download::execute(None, Some(a.board_id), a.destination, factory, store).await
82        }
83        AttachmentCommand::Copy(a) => transfer::execute(a, false, factory, store).await,
84        AttachmentCommand::Move(a) => transfer::execute(a, true, factory, store).await,
85        AttachmentCommand::Delete(a) => delete::execute(a, factory, store, confirmation).await,
86    }
87}
88
89fn identifier(value: &str) -> Result<String, String> {
90    let value = crate::client::trim_javascript_whitespace(value);
91    if value.is_empty() || matches!(value, "." | "..") {
92        Err("ID must be nonblank and cannot be . or ..".to_owned())
93    } else {
94        Ok(value.to_owned())
95    }
96}
97
98fn file_error(operation: &str, path: &std::path::Path, error: std::io::Error) -> AppError {
99    AppError::new(
100        crate::error::ErrorCode::FileIoError,
101        format!(
102            "could not {operation} attachment file `{}`: {}",
103            crate::input::escape_terminal_text(&path.display().to_string()),
104            crate::input::escape_terminal_text(&error.to_string())
105        ),
106        crate::exit_code::StableExitCode::Transport,
107    )
108}