Skip to content

Commit

Permalink
feat: un-deprecate that_in_background().
Browse files Browse the repository at this point in the history
In some configurations launchers may block, thus it's better to provide an easy mechanism to unblock
an otherwise blocking call. This is alongside being able to use `command()` directly and simply spawn
the command without waiting for it.
  • Loading branch information
Byron committed Mar 6, 2023
2 parents 0d09f28 + 66e0d7c commit adf99e9
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 279 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]

name = "open"
version = "3.2.0"
version = "3.3.0"
authors = ["Sebastian Thiel <[email protected]>"]
license = "MIT"
edition = "2018"
Expand Down
373 changes: 212 additions & 161 deletions changelog.md

Large diffs are not rendered by default.

22 changes: 9 additions & 13 deletions src/haiku.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
use std::{ffi::OsStr, io, process::Command};
use std::{ffi::OsStr, process::Command};

use crate::{CommandExt, IntoResult};

pub fn that<T: AsRef<OsStr>>(path: T) -> io::Result<()> {
Command::new("/bin/open")
.arg(path.as_ref())
.status_without_output()
.into_result()
pub fn commands<T: AsRef<OsStr>>(path: T) -> Vec<Command> {
let mut cmd = Command::new("/bin/open");
cmd.arg(path.as_ref());
vec![cmd]
}

pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> io::Result<()> {
Command::new(app.into())
.arg(path.as_ref())
.status_without_output()
.into_result()
pub fn with_command<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Command {
let mut cmd = Command::new(app.into());
cmd.arg(path.as_ref());
cmd
}
25 changes: 10 additions & 15 deletions src/ios.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
use std::{ffi::OsStr, io, process::Command};
use std::{ffi::OsStr, process::Command};

use crate::{CommandExt, IntoResult};

pub fn that<T: AsRef<OsStr>>(path: T) -> io::Result<()> {
Command::new("uiopen")
.arg("--url")
.arg(path.as_ref())
.status_without_output()
.into_result()
pub fn commands<T: AsRef<OsStr>>(path: T) -> Vec<Command> {
let mut cmd = Command::new("uiopen");
cmd.arg("--url").arg(path.as_ref());
vec![cmd]
}

pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> io::Result<()> {
Command::new("uiopen")
.arg("--url")
pub fn with_command<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Command {
let mut cmd = Command::new("uiopen");
cmd.arg("--url")
.arg(path.as_ref())
.arg("--bundleid")
.arg(app.into())
.status_without_output()
.into_result()
.arg(app.into());
cmd
}
83 changes: 61 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
//! open::with("http://rust-lang.org", "firefox").unwrap();
//! ```
//!
//! Or obtain the commands to launch a file or path without running them.
//!
//! ```no_run
//! let cmd = open::commands("http://rust-lang.org");
//! ```
//!
//! # Notes
//!
//! ## Nonblocking operation
Expand Down Expand Up @@ -102,8 +108,22 @@ use std::{
///
/// A [`std::io::Error`] is returned on failure. Because different operating systems
/// handle errors differently it is recommend to not match on a certain error.
///
/// # Beware
///
/// Sometimes, depending on the platform and system configuration, launchers *can* block.
/// If you want to be sure they don't, use [`that_in_background()`] instead.
pub fn that<T: AsRef<OsStr>>(path: T) -> io::Result<()> {
os::that(path)
let mut last_err = None;
for mut cmd in commands(path) {
match cmd.status_without_output() {
Ok(status) => {
return Ok(status).into_result(&cmd);
}
Err(err) => last_err = Some(err),
}
}
Err(last_err.expect("no launcher worked, at least one error"))
}

/// Open path with the given application.
Expand All @@ -128,13 +148,45 @@ pub fn that<T: AsRef<OsStr>>(path: T) -> io::Result<()> {
/// A [`std::io::Error`] is returned on failure. Because different operating systems
/// handle errors differently it is recommend to not match on a certain error.
pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> io::Result<()> {
os::with(path, app)
let mut cmd = with_command(path, app);
cmd.status_without_output().into_result(&cmd)
}

/// Get multiple commands that open `path` with the default application.
///
/// Each command represents a launcher to try.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let path = "http://rust-lang.org";
/// assert!(open::commands(path)[0].status()?.success());
/// # Ok(())
/// # }
/// ```
pub fn commands<'a, T: AsRef<OsStr>>(path: T) -> Vec<Command> {
os::commands(path)
}

/// Get a command that uses `app` to open `path`.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let path = "http://rust-lang.org";
/// assert!(open::with_command(path, "app").status()?.success());
/// # Ok(())
/// # }
/// ```
pub fn with_command<'a, T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Command {
os::with_command(path, app)
}

/// Open path with the default application in a new thread.
/// Open path with the default application in a new thread to assure it's non-blocking.
///
/// See documentation of [`that()`] for more details.
#[deprecated = "Use `that()` as it is non-blocking while making error handling easy."]
pub fn that_in_background<T: AsRef<OsStr>>(path: T) -> thread::JoinHandle<io::Result<()>> {
let path = path.as_ref().to_os_string();
thread::spawn(|| that(path))
Expand All @@ -155,45 +207,32 @@ pub fn with_in_background<T: AsRef<OsStr>>(
}

trait IntoResult<T> {
fn into_result(self) -> T;
fn into_result(self, cmd: &Command) -> T;
}

impl IntoResult<io::Result<()>> for io::Result<std::process::ExitStatus> {
fn into_result(self) -> io::Result<()> {
fn into_result(self, cmd: &Command) -> io::Result<()> {
match self {
Ok(status) if status.success() => Ok(()),
Ok(status) => Err(io::Error::new(
io::ErrorKind::Other,
format!("Launcher failed with {:?}", status),
format!("Launcher {cmd:?} failed with {:?}", status),
)),
Err(err) => Err(err),
}
}
}

#[cfg(windows)]
impl IntoResult<io::Result<()>> for std::os::raw::c_int {
fn into_result(self) -> io::Result<()> {
match self {
i if i > 32 => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
}

trait CommandExt {
fn status_without_output(&mut self) -> io::Result<std::process::ExitStatus>;
}

impl CommandExt for Command {
fn status_without_output(&mut self) -> io::Result<std::process::ExitStatus> {
let mut process = self
.stdin(Stdio::null())
self.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;

process.wait()
.status()
}
}

Expand Down
24 changes: 9 additions & 15 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
use std::{ffi::OsStr, io, process::Command};
use std::{ffi::OsStr, process::Command};

use crate::{CommandExt, IntoResult};

pub fn that<T: AsRef<OsStr>>(path: T) -> io::Result<()> {
Command::new("/usr/bin/open")
.arg(path.as_ref())
.status_without_output()
.into_result()
pub fn commands<T: AsRef<OsStr>>(path: T) -> Vec<Command> {
let mut cmd = Command::new("/usr/bin/open");
cmd.arg(path.as_ref());
vec![cmd]
}

pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> io::Result<()> {
Command::new("/usr/bin/open")
.arg(path.as_ref())
.arg("-a")
.arg(app.into())
.status_without_output()
.into_result()
pub fn with_command<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Command {
let mut cmd = Command::new("/usr/bin/open");
cmd.arg(path.as_ref()).arg("-a").arg(app.into());
cmd
}
49 changes: 14 additions & 35 deletions src/unix.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,32 @@
use std::{
env,
ffi::{OsStr, OsString},
io,
path::{Path, PathBuf},
process::Command,
};

use crate::{CommandExt, IntoResult};

pub fn that<T: AsRef<OsStr>>(path: T) -> io::Result<()> {
pub fn commands<T: AsRef<OsStr>>(path: T) -> Vec<Command> {
let path = path.as_ref();
let open_handlers = [
[
("xdg-open", &[path] as &[_]),
("gio", &[OsStr::new("open"), path]),
("gnome-open", &[path]),
("kde-open", &[path]),
("wslview", &[&wsl_path(path)]),
];

let mut unsuccessful = None;
let mut io_error = None;

for (command, args) in &open_handlers {
let result = Command::new(command).args(*args).status_without_output();

match result {
Ok(status) if status.success() => return Ok(()),
Ok(status) => {
unsuccessful = unsuccessful.or_else(|| {
Some(std::io::Error::new(
std::io::ErrorKind::Other,
status.to_string(),
))
})
}
Err(err) => io_error = io_error.or(Some(err)),
}
}

Err(unsuccessful
.or(io_error)
.expect("successful cases don't get here"))
]
.iter()
.map(|(command, args)| {
let mut cmd = Command::new(command);
cmd.args(*args);
cmd
})
.collect()
}

pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> io::Result<()> {
Command::new(app.into())
.arg(path.as_ref())
.status_without_output()
.into_result()
pub fn with_command<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Command {
let mut cmd = Command::new(app.into());
cmd.arg(path.as_ref());
cmd
}

// Polyfill to workaround absolute path bug in wslu(wslview). In versions before
Expand Down
26 changes: 9 additions & 17 deletions src/windows.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
use std::{ffi::OsStr, io};
use std::{ffi::OsStr, process::Command};

use crate::{CommandExt, IntoResult};

pub fn that<T: AsRef<OsStr>>(path: T) -> io::Result<()> {
std::process::Command::new("cmd")
.arg("/c")
.arg("start")
.arg(path.as_ref())
.status_without_output()
.into_result()
pub fn commands<T: AsRef<OsStr>>(path: T) -> Vec<Command> {
let mut cmd = Command::new("cmd");
cmd.arg("/c").arg("start").arg(path.as_ref());
vec![cmd]
}

pub fn with<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> io::Result<()> {
std::process::Command::new("cmd")
.arg("/c")
.arg(app.into())
.arg(path.as_ref())
.status_without_output()
.into_result()
pub fn with_command<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> Command {
let mut cmd = Command::new("cmd");
cmd.arg("/c").arg(app.into()).arg(path.as_ref());
cmd
}

0 comments on commit adf99e9

Please sign in to comment.