diff --git a/Cargo.lock b/Cargo.lock index 9365c0a..cbf3020 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,7 +358,6 @@ dependencies = [ "derive_builder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "directories 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "ffsend-api 0.0.1", "fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ROADMAP.md b/ROADMAP.md index e0d925c..fc9eedd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -3,8 +3,6 @@ The first release used for gathering feedback on the application by selected people. Features: -- Allow unarchiving on download -- Use clipboard through `xclip` on Linux if available for persistence - Write complete README - Polish command outputs, make it consistent (format, color) - Automated releases through CI diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 76443aa..8e770dc 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -27,12 +27,10 @@ no-color = ["colored/no-color"] [dependencies] chrono = "0.4" clap = "2.31" -clipboard = { version = "0.4", optional = true } colored = "1.6" derive_builder = "0.5" directories = "0.10" failure = "0.1" -failure_derive = "0.1" ffsend-api = { version = "*", path = "../api" } fs2 = "0.4" lazy_static = "1.0" @@ -46,3 +44,6 @@ tar = { version = "0.4", optional = true } tempfile = "3" toml = "0.4" version-compare = "0.0.6" + +[target.'cfg(not(target_os = "linux"))'.dependencies] +clipboard = { version = "0.4", optional = true } diff --git a/cli/src/action/upload.rs b/cli/src/action/upload.rs index fedc6ec..9a82a04 100644 --- a/cli/src/action/upload.rs +++ b/cli/src/action/upload.rs @@ -248,8 +248,10 @@ impl<'a> Upload<'a> { // Copy the URL in the user's clipboard #[cfg(feature = "clipboard")] { - if matcher_upload.copy() && set_clipboard(url.as_str().to_owned()).is_err() { - print_error_msg("failed to copy the URL to the clipboard"); + if matcher_upload.copy() { + if let Err(err) = set_clipboard(url.as_str().to_owned()) { + print_error(err.context("failed to copy the URL to the clipboard, ignoring")); + } } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 77ea8b9..75e5b8b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,9 +3,8 @@ extern crate chrono; extern crate clap; #[macro_use] extern crate derive_builder; -extern crate failure; #[macro_use] -extern crate failure_derive; +extern crate failure; extern crate ffsend_api; #[cfg(feature = "history")] #[macro_use] diff --git a/cli/src/util.rs b/cli/src/util.rs index 2adb026..d520b58 100644 --- a/cli/src/util.rs +++ b/cli/src/util.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "clipboard")] +#[cfg(all(feature = "clipboard", not(target_os = "linux")))] extern crate clipboard; extern crate colored; extern crate directories; @@ -7,8 +7,6 @@ extern crate open; use std::borrow::Borrow; use std::env::{current_exe, var_os}; -#[cfg(feature = "clipboard")] -use std::error::Error as StdError; use std::ffi::OsStr; use std::fmt::{Debug, Display}; use std::io::{ @@ -17,16 +15,22 @@ use std::io::{ stderr, Write, }; +#[cfg(feature = "clipboard")] +use std::io::ErrorKind as IoErrorKind; use std::path::Path; #[cfg(feature = "history")] use std::path::PathBuf; use std::process::{exit, ExitStatus}; +#[cfg(all(feature = "clipboard", target_os = "linux"))] +use std::process::{Command, Stdio}; use chrono::Duration; use failure::{err_msg, Fail}; +#[cfg(all(feature = "clipboard", not(target_os = "linux")))] +use failure::{Compat, Error}; use ffsend_api::url::Url; use rpassword::prompt_password_stderr; -#[cfg(feature = "clipboard")] +#[cfg(all(feature = "clipboard", not(target_os = "linux")))] use self::clipboard::{ClipboardContext, ClipboardProvider}; use self::colored::*; #[cfg(feature = "history")] @@ -262,9 +266,69 @@ pub fn open_path(path: &str) -> Result { /// Set the clipboard of the user to the given `content` string. #[cfg(feature = "clipboard")] -pub fn set_clipboard(content: String) -> Result<(), Box> { - let mut context: ClipboardContext = ClipboardProvider::new()?; - context.set_contents(content) +pub fn set_clipboard(content: String) -> Result<(), ClipboardError> { + #[cfg(not(target_os = "linux"))] { + ClipboardProvider::new() + .and_then(|mut context: ClipboardContext| context.set_contents(content)) + .map_err(|err| format_err!("{}", err).compat()) + .map_err(ClipboardError::Generic) + } + + #[cfg(target_os = "linux")] { + // Open an xclip process + let mut process = match Command::new("xclip") + .arg("-sel") + .arg("clip") + .stdin(Stdio::piped()) + .spawn() + { + Ok(process) => process, + Err(err) => return Err(match err.kind() { + IoErrorKind::NotFound => ClipboardError::NoXclip, + _ => ClipboardError::Xclip(err), + }), + }; + + // Write the contents to the xclip process + process.stdin.as_mut().unwrap() + .write_all(content.as_bytes()) + .map_err(ClipboardError::Xclip)?; + + // Wait for xclip to exit + let status = process.wait() + .map_err(ClipboardError::Xclip)?; + if !status.success() { + return Err(ClipboardError::XclipStatus(status.code().unwrap_or(0))); + } + + Ok(()) + } +} + +#[cfg(feature = "clipboard")] +#[derive(Debug, Fail)] +pub enum ClipboardError { + /// A generic error occurred while setting the clipboard contents + #[cfg(not(target_os = "linux"))] + #[fail(display = "failed to access clipboard")] + Generic(#[cause] Compat), + + /// Xclip is not installed on the system, which is required for clipboard support. + #[cfg(target_os = "linux")] + #[fail(display = "failed to access clipboard, xclip is not installed")] + NoXclip, + + /// An error occurred while using xclip to set the clipboard contents. + /// This problem probably occurred when stargin the xclip process, or while piping the + /// clipboard contents to the process. + #[cfg(target_os = "linux")] + #[fail(display = "failed to access clipboard using xclip")] + Xclip(#[cause] IoError), + + /// Xclip unexpectetly exited with a non-successful status code. + #[cfg(target_os = "linux")] + #[fail(display = "failed to use clipboard, xclip exited with status code {}", _0)] + XclipStatus(i32), } /// Check for an emtpy password in the given `password`.