Skip to content

Commit

Permalink
Use open crate to open files and URLs
Browse files Browse the repository at this point in the history
Opening URLs on Windows is very complex, so delegate to the
`open` crate.

type: changed
  • Loading branch information
casey committed Apr 8, 2020
1 parent 35d90ad commit 6328118
Show file tree
Hide file tree
Showing 8 changed files with 44 additions and 225 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lazy_static = "1.4.0"
libc = "0.2.0"
log = "0.4.8"
md5 = "0.7.0"
open = "1.4.0"
pretty_assertions = "0.6.0"
pretty_env_logger = "0.4.0"
regex = "1.0.0"
Expand Down
4 changes: 2 additions & 2 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub(crate) use std::{
num::{ParseFloatError, ParseIntError, TryFromIntError},
ops::{AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
path::{self, Path, PathBuf},
process::{self, Command, ExitStatus},
process::{self, ExitStatus},
str::{self, FromStr},
sync::Once,
time::{SystemTime, SystemTimeError},
Expand Down Expand Up @@ -79,8 +79,8 @@ mod test {
cell::RefCell,
io::Cursor,
ops::{Deref, DerefMut},
process::Command,
rc::Rc,
time::{Duration, Instant},
};

// test dependencies
Expand Down
44 changes: 23 additions & 21 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,6 @@ use structopt::clap;
pub(crate) enum Error {
#[snafu(display("Failed to parse announce URL: {}", source))]
AnnounceUrlParse { source: url::ParseError },
#[snafu(display("Failed to deserialize torrent metainfo from {}: {}", input, source))]
MetainfoDeserialize {
source: bendy::serde::Error,
input: InputTarget,
},
#[snafu(display("Failed to serialize torrent metainfo: {}", source))]
MetainfoSerialize { source: bendy::serde::Error },
#[snafu(display("Failed to decode torrent metainfo from {}: {}", input, error))]
MetainfoDecode {
input: InputTarget,
error: bendy::decoding::Error,
},
#[snafu(display("Metainfo from {} failed to validate: {}", input, source))]
MetainfoValidate {
input: InputTarget,
source: MetainfoError,
},
#[snafu(display("Failed to parse byte count `{}`: {}", text, source))]
ByteParse {
text: String,
Expand All @@ -45,8 +28,6 @@ pub(crate) enum Error {
Filesystem { source: io::Error, path: PathBuf },
#[snafu(display("Invalid glob: {}", source))]
GlobParse { source: globset::Error },
#[snafu(display("Unknown lint: {}", text))]
LintUnknown { text: String },
#[snafu(display("Failed to serialize torrent info dictionary: {}", source))]
InfoSerialize { source: bendy::serde::Error },
#[snafu(display(
Expand All @@ -55,8 +36,29 @@ pub(crate) enum Error {
message,
))]
Internal { message: String },
#[snafu(display("Failed to find opener utility, please install one of {}", tried.join(",")))]
OpenerMissing { tried: &'static [&'static str] },
#[snafu(display("Unknown lint: {}", text))]
LintUnknown { text: String },
#[snafu(display("Failed to deserialize torrent metainfo from {}: {}", input, source))]
MetainfoDeserialize {
source: bendy::serde::Error,
input: InputTarget,
},
#[snafu(display("Failed to serialize torrent metainfo: {}", source))]
MetainfoSerialize { source: bendy::serde::Error },
#[snafu(display("Failed to decode torrent metainfo from {}: {}", input, error))]
MetainfoDecode {
input: InputTarget,
error: bendy::decoding::Error,
},
#[snafu(display("Metainfo from {} failed to validate: {}", input, source))]
MetainfoValidate {
input: InputTarget,
source: MetainfoError,
},
#[snafu(display("Failed to invoke opener: {}", source))]
OpenerInvoke { source: io::Error },
#[snafu(display("Opener failed: {}", exit_status))]
OpenerExitStatus { exit_status: ExitStatus },
#[snafu(display("Output path already exists: `{}`", path.display()))]
OutputExists { path: PathBuf },
#[snafu(display(
Expand Down
27 changes: 0 additions & 27 deletions src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ pub(crate) struct Platform;

#[cfg(target_os = "windows")]
impl PlatformInterface for Platform {
fn opener() -> Result<Vec<OsString>, Error> {
let exe = if cfg!(test) { "open.bat" } else { "cmd" };
Ok(vec![
OsString::from(exe),
OsString::from("/C"),
OsString::from("start"),
])
}

fn hidden(path: &Path) -> Result<bool, Error> {
use std::os::windows::fs::MetadataExt;

Expand All @@ -25,10 +16,6 @@ impl PlatformInterface for Platform {

#[cfg(target_os = "macos")]
impl PlatformInterface for Platform {
fn opener() -> Result<Vec<OsString>, Error> {
Ok(vec![OsString::from("open")])
}

fn hidden(path: &Path) -> Result<bool, Error> {
use std::{ffi::CString, mem, os::unix::ffi::OsStrExt};

Expand All @@ -53,20 +40,6 @@ impl PlatformInterface for Platform {

#[cfg(not(any(target_os = "windows", target_os = "macos")))]
impl PlatformInterface for Platform {
fn opener() -> Result<Vec<OsString>, Error> {
const OPENERS: &[&str] = &["xdg-open", "gnome-open", "kde-open"];

for opener in OPENERS {
if let Ok(output) = Command::new(opener).arg("--version").output() {
if output.status.success() {
return Ok(vec![OsString::from(opener)]);
}
}
}

Err(Error::OpenerMissing { tried: OPENERS })
}

fn hidden(_path: &Path) -> Result<bool, Error> {
Ok(false)
}
Expand Down
43 changes: 8 additions & 35 deletions src/platform_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,22 @@ use crate::common::*;

pub(crate) trait PlatformInterface {
fn open_file(path: &Path) -> Result<(), Error> {
Self::open_raw(path.as_os_str())
Self::open_target(path.as_ref())
}

fn open_url(url: &Url) -> Result<(), Error> {
if cfg!(windows) {
let escaped = format!("\"{}\"", url);
Self::open_raw(escaped.as_str().as_ref())
} else {
Self::open_raw(url.as_str().as_ref())
}
Self::open_target(url.as_str().as_ref())
}

fn open_raw(target: &OsStr) -> Result<(), Error> {
let mut command = Self::opener()?;
command.push(OsString::from(target));

let command_string = || {
command
.iter()
.map(|arg| arg.to_string_lossy().into_owned())
.collect::<Vec<String>>()
.join(",")
};

let status = Command::new(&command[0])
.args(&command[1..])
.status()
.map_err(|source| Error::CommandInvoke {
source,
command: command_string(),
})?;
fn open_target(target: &OsStr) -> Result<(), Error> {
let exit_status = open::that(target).context(error::OpenerInvoke)?;

if status.success() {
Ok(())
} else {
Err(Error::CommandStatus {
command: command_string(),
status,
})
if !exit_status.success() {
return Err(Error::OpenerExitStatus { exit_status });
}
}

fn opener() -> Result<Vec<OsString>, Error>;
Ok(())
}

fn hidden(path: &Path) -> Result<bool, Error>;
}
63 changes: 0 additions & 63 deletions src/subcommand/torrent/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1352,69 +1352,6 @@ mod tests {
}
}

#[test]
fn open() {
let mut env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
"--announce",
"http://bar",
"--open",
],
tree: {},
};

let opened = env.resolve("opened.txt");
let torrent = env.resolve("foo.torrent");

let expected = if cfg!(target_os = "windows") {
let script = env.resolve("open.bat");
fs::write(&script, format!("echo %3 > {}", opened.display())).unwrap();
format!("{} \r\n", torrent.display())
} else {
let script = env.resolve(&Platform::opener().unwrap()[0]);
fs::write(
&script,
format!("#!/usr/bin/env sh\necho $1 > {}", opened.display()),
)
.unwrap();

Command::new("chmod")
.arg("+x")
.arg(&script)
.status()
.unwrap();

format!("{}\n", torrent.display())
};

const KEY: &str = "PATH";
let path = env::var_os(KEY).unwrap();
let mut split = env::split_paths(&path)
.into_iter()
.collect::<Vec<PathBuf>>();
split.insert(0, env.dir().to_owned());
let new = env::join_paths(split).unwrap();
env::set_var(KEY, new);

fs::write(env.resolve("foo"), "").unwrap();
env.run().unwrap();

let start = Instant::now();

while start.elapsed() < Duration::new(2, 0) {
if let Ok(text) = fs::read_to_string(&opened) {
assert_eq!(text, expected);
return;
}
}

panic!("Failed to read `opened.txt`.");
}

#[test]
fn uneven_piece_length() {
let mut env = test_env! {
Expand Down
77 changes: 0 additions & 77 deletions src/subcommand/torrent/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,81 +239,4 @@ mod tests {
if input == "foo.torrent"
);
}

#[test]
fn open() {
let mut create_env = test_env! {
args: [
"torrent",
"create",
"--input",
"foo",
],
tree: {
foo: "",
},
};

assert_matches!(create_env.run(), Ok(()));

let torrent = create_env.resolve("foo.torrent");

let mut env = test_env! {
args: [
"torrent",
"link",
"--input",
&torrent,
"--open",
],
tree: {},
};

let opened = env.resolve("opened.txt");

let link = "magnet:?xt=urn:btih:516735f4b80f2b5487eed5f226075bdcde33a54e&dn=foo";

let expected = if cfg!(target_os = "windows") {
let script = env.resolve("open.bat");
fs::write(&script, format!("echo > {}", opened.display())).unwrap();
format!("ECHO is on.\r\n")
} else {
let script = env.resolve(&Platform::opener().unwrap()[0]);
fs::write(
&script,
format!("#!/usr/bin/env sh\necho $1 > {}", opened.display()),
)
.unwrap();

Command::new("chmod")
.arg("+x")
.arg(&script)
.status()
.unwrap();

format!("{}\n", link)
};

const KEY: &str = "PATH";
let path = env::var_os(KEY).unwrap();
let mut split = env::split_paths(&path)
.into_iter()
.collect::<Vec<PathBuf>>();
split.insert(0, env.dir().to_owned());
let new = env::join_paths(split).unwrap();
env::set_var(KEY, new);

assert_matches!(env.run(), Ok(()));

let start = Instant::now();

while start.elapsed() < Duration::new(2, 0) {
if let Ok(text) = fs::read_to_string(&opened) {
assert_eq!(text, expected);
return;
}
}

panic!("Failed to read `opened.txt`.");
}
}

0 comments on commit 6328118

Please sign in to comment.