diff --git a/Cargo.lock b/Cargo.lock index c7ddf6d..a08a4be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,6 +1160,7 @@ dependencies = [ name = "iamb" version = "0.0.4" dependencies = [ + "bitflags", "chrono", "clap", "css-color-parser", @@ -1172,6 +1173,7 @@ dependencies = [ "mime", "mime_guess", "modalkit", + "open", "regex", "rpassword", "serde", @@ -1765,6 +1767,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "open" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" +dependencies = [ + "pathdiff", + "windows-sys", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -1842,6 +1854,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pbkdf2" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index b9e0199..03a45a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ categories = ["command-line-utilities"] rust-version = "1.66" [dependencies] +bitflags = "1.3.2" chrono = "0.4" clap = {version = "4.0", features = ["derive"]} css-color-parser = "0.1.2" @@ -23,6 +24,7 @@ html5ever = "0.26.0" markup5ever_rcdom = "0.2.0" mime = "^0.3.16" mime_guess = "^2.0.4" +open = "3.2.0" regex = "^1.5" rpassword = "^7.2" serde = "^1.0" diff --git a/src/base.rs b/src/base.rs index ee74bb2..313728f 100644 --- a/src/base.rs +++ b/src/base.rs @@ -81,9 +81,9 @@ pub enum MessageAction { /// Download an attachment to the given path. /// - /// The [bool] argument controls whether to overwrite any already existing file at the - /// destination path. - Download(Option, bool), + /// The second argument controls whether to overwrite any already existing file at the + /// destination path, or to open the attachment after downloading. + Download(Option, DownloadFlags), /// Edit a sent message. Edit, @@ -95,6 +95,18 @@ pub enum MessageAction { Reply, } +bitflags::bitflags! { + pub struct DownloadFlags: u32 { + const NONE = 0b00000000; + + /// Overwrite file if it already exists. + const FORCE = 0b00000001; + + /// Open file after downloading. + const OPEN = 0b00000010; + } +} + #[derive(Clone, Debug, Eq, PartialEq)] pub enum RoomField { Name, diff --git a/src/commands.rs b/src/commands.rs index f0373cc..363dd88 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -10,6 +10,7 @@ use modalkit::{ }; use crate::base::{ + DownloadFlags, IambAction, IambId, MessageAction, @@ -317,7 +318,29 @@ fn iamb_download(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult return Result::Err(CommandError::InvalidArgument); } - let mact = MessageAction::Download(args.pop(), desc.bang); + let mut flags = DownloadFlags::NONE; + if desc.bang { + flags |= DownloadFlags::FORCE; + }; + let mact = MessageAction::Download(args.pop(), flags); + let iact = IambAction::from(mact); + let step = CommandStep::Continue(iact.into(), ctx.context.take()); + + return Ok(step); +} + +fn iamb_open(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { + let mut args = desc.arg.strings()?; + + if args.len() > 1 { + return Result::Err(CommandError::InvalidArgument); + } + + let mut flags = DownloadFlags::OPEN; + if desc.bang { + flags |= DownloadFlags::FORCE; + }; + let mact = MessageAction::Download(args.pop(), flags); let iact = IambAction::from(mact); let step = CommandStep::Continue(iact.into(), ctx.context.take()); @@ -328,6 +351,7 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) { cmds.add_command(ProgramCommand { names: vec!["cancel".into()], f: iamb_cancel }); cmds.add_command(ProgramCommand { names: vec!["dms".into()], f: iamb_dms }); cmds.add_command(ProgramCommand { names: vec!["download".into()], f: iamb_download }); + cmds.add_command(ProgramCommand { names: vec!["open".into()], f: iamb_open }); cmds.add_command(ProgramCommand { names: vec!["edit".into()], f: iamb_edit }); cmds.add_command(ProgramCommand { names: vec!["invite".into()], f: iamb_invite }); cmds.add_command(ProgramCommand { names: vec!["join".into()], f: iamb_join }); diff --git a/src/windows/room/chat.rs b/src/windows/room/chat.rs index 680b669..8e41a01 100644 --- a/src/windows/room/chat.rs +++ b/src/windows/room/chat.rs @@ -4,6 +4,8 @@ use std::fs; use std::ops::Deref; use std::path::{Path, PathBuf}; +use tokio; + use matrix_sdk::{ attachment::AttachmentConfig, media::{MediaFormat, MediaRequest}, @@ -55,6 +57,7 @@ use modalkit::editing::{ }; use crate::base::{ + DownloadFlags, IambAction, IambBufferId, IambError, @@ -155,7 +158,7 @@ impl ChatState { Ok(None) }, - MessageAction::Download(filename, force) => { + MessageAction::Download(filename, flags) => { if let MessageEvent::Original(ev) = &msg.event { let media = client.media(); @@ -202,29 +205,42 @@ impl ChatState { }, }; - if !force && filename.exists() { - let msg = format!( - "The file {} already exists; use :download! to overwrite it.", - filename.display() - ); - let err = UIError::Failure(msg); + if filename.exists() { + if !flags.contains(DownloadFlags::FORCE) { + let msg = format!( + "The file {} already exists; add ! to end of command to overwrite it.", + filename.display() + ); + let err = UIError::Failure(msg); - return Err(err); + return Err(err); + } + + let req = MediaRequest { source, format: MediaFormat::File }; + + let bytes = + media.get_media_content(&req, true).await.map_err(IambError::from)?; + + fs::write(filename.as_path(), bytes.as_slice())?; + + msg.downloaded = true; } - let req = MediaRequest { source, format: MediaFormat::File }; + let info = if flags.contains(DownloadFlags::OPEN) { + // open::that may not return until the spawned program closes. + let target = filename.clone().into_os_string(); + tokio::task::spawn_blocking(move || open::that(target)); - let bytes = - media.get_media_content(&req, true).await.map_err(IambError::from)?; - - fs::write(filename.as_path(), bytes.as_slice())?; - - msg.downloaded = true; - - let info = InfoMessage::from(format!( - "Attachment downloaded to {}", - filename.display() - )); + InfoMessage::from(format!( + "Attachment downloaded to {} and opened", + filename.display() + )) + } else { + InfoMessage::from(format!( + "Attachment downloaded to {}", + filename.display() + )) + }; return Ok(info.into()); }