Optionally print torrent details as JSON
Adds `--json` flag to `imdl torrent show` to print torrent details as JSON. type: added
This commit is contained in:
parent
70c1f4e57c
commit
0c17c3da49
20
.github/workflows/build.yaml
vendored
20
.github/workflows/build.yaml
vendored
|
@ -10,6 +10,12 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Increment to invalidate github actions caches if they become corrupt.
|
||||||
|
# Errors of the form "can't find crate for `snafu_derive` which `snafu` depends on"
|
||||||
|
# can usually be fixed by incrementing this value.
|
||||||
|
CACHE_KEY_PREFIX: 2
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
all:
|
all:
|
||||||
name: All
|
name: All
|
||||||
|
@ -45,23 +51,31 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# An issue with BSD Tar causes sporadic failures on macOS.
|
||||||
|
# c.f https://github.com/actions/cache/issues/403
|
||||||
|
- name: Install GNU Tar
|
||||||
|
if: matrix.build == 'macos'
|
||||||
|
run: |
|
||||||
|
brew install gnu-tar
|
||||||
|
echo "::add-path::/usr/local/opt/gnu-tar/libexec/gnubin"
|
||||||
|
|
||||||
- name: Cache cargo registry
|
- name: Cache cargo registry
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/registry
|
path: ~/.cargo/registry
|
||||||
key: v1-${{ runner.os }}-cargo-registry
|
key: ${{ env.CACHE_KEY_PREFIX }}-${{ runner.os }}-cargo-registry
|
||||||
|
|
||||||
- name: Cache cargo index
|
- name: Cache cargo index
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/git
|
path: ~/.cargo/git
|
||||||
key: v1-${{ runner.os }}-cargo-index
|
key: ${{ env.CACHE_KEY_PREFIX }}-${{ runner.os }}-cargo-index
|
||||||
|
|
||||||
- name: Cache cargo build
|
- name: Cache cargo build
|
||||||
uses: actions/cache@v1
|
uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: target
|
path: target
|
||||||
key: v1-${{ runner.os }}-cargo-build-target
|
key: ${{ env.CACHE_KEY_PREFIX }}-${{ runner.os }}-cargo-build-target
|
||||||
|
|
||||||
- name: Install Stable
|
- name: Install Stable
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -626,6 +626,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde-hex",
|
"serde-hex",
|
||||||
"serde_bytes",
|
"serde_bytes",
|
||||||
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"sha1",
|
"sha1",
|
||||||
"snafu",
|
"snafu",
|
||||||
|
|
|
@ -34,6 +34,7 @@ pretty_env_logger = "0.4.0"
|
||||||
regex = "1.0.0"
|
regex = "1.0.0"
|
||||||
serde-hex = "0.1.0"
|
serde-hex = "0.1.0"
|
||||||
serde_bytes = "0.11.0"
|
serde_bytes = "0.11.0"
|
||||||
|
serde_json = "1.0.57"
|
||||||
serde_with = "1.4.0"
|
serde_with = "1.4.0"
|
||||||
sha1 = "0.6.0"
|
sha1 = "0.6.0"
|
||||||
snafu = "0.6.0"
|
snafu = "0.6.0"
|
||||||
|
|
|
@ -136,6 +136,8 @@ pub(crate) enum Error {
|
||||||
Unstable { feature: &'static str },
|
Unstable { feature: &'static str },
|
||||||
#[snafu(display("Torrent verification failed."))]
|
#[snafu(display("Torrent verification failed."))]
|
||||||
Verify,
|
Verify,
|
||||||
|
#[snafu(display("Failed to serialize JSON: {}", source))]
|
||||||
|
JsonSerialize { source: serde_json::Error },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|
|
@ -9,6 +9,10 @@ const INPUT_POSITIONAL: &str = "<INPUT>";
|
||||||
|
|
||||||
const INPUT_VALUE: &str = "INPUT";
|
const INPUT_VALUE: &str = "INPUT";
|
||||||
|
|
||||||
|
const JSON_OUTPUT: &str = "json";
|
||||||
|
|
||||||
|
const JSON_HELP: &str = "Output data as JSON instead of the default format.";
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
#[structopt(
|
#[structopt(
|
||||||
help_message(consts::HELP_MESSAGE),
|
help_message(consts::HELP_MESSAGE),
|
||||||
|
@ -36,6 +40,13 @@ pub(crate) struct Show {
|
||||||
help = INPUT_HELP,
|
help = INPUT_HELP,
|
||||||
)]
|
)]
|
||||||
input_positional: Option<InputTarget>,
|
input_positional: Option<InputTarget>,
|
||||||
|
#[structopt(
|
||||||
|
name = JSON_OUTPUT,
|
||||||
|
long = "json",
|
||||||
|
short = "j",
|
||||||
|
help = JSON_HELP,
|
||||||
|
)]
|
||||||
|
json: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show {
|
impl Show {
|
||||||
|
@ -49,7 +60,11 @@ impl Show {
|
||||||
|
|
||||||
let input = env.read(target)?;
|
let input = env.read(target)?;
|
||||||
let summary = TorrentSummary::from_input(&input)?;
|
let summary = TorrentSummary::from_input(&input)?;
|
||||||
summary.write(env)?;
|
if self.json {
|
||||||
|
summary.write_json(env)?;
|
||||||
|
} else {
|
||||||
|
summary.write(env)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -559,4 +574,63 @@ files\tNAME
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_json() {
|
||||||
|
{
|
||||||
|
let metainfo = Metainfo::test_value_single();
|
||||||
|
let mut want = r#"{"name":"NAME","comment":"COMMENT","creation_date":1,
|
||||||
|
"created_by":"CREATED BY","source":"SOURCE","info_hash":"5d6f53772b4c20536fcce0c4c364d764a6efa39c",
|
||||||
|
"torrent_size":509,"content_size":32768,"private":true,"tracker":
|
||||||
|
"udp://announce.example:1337","announce_list":[["http://a.example:4567",
|
||||||
|
"https://b.example:77"],["udp://c.example:88"]],"update_url":"https://update.example/",
|
||||||
|
"dht_nodes":["node.example:12","1.1.1.1:16","[2001:db8:85a3::8a2e:370]:7334"],
|
||||||
|
"piece_size":16384,"piece_count":2,"file_count":1,"files":["NAME"]}"#
|
||||||
|
.replace('\n', "");
|
||||||
|
want.push('\n');
|
||||||
|
let mut env = TestEnvBuilder::new()
|
||||||
|
.arg_slice(&[
|
||||||
|
"imdl",
|
||||||
|
"torrent",
|
||||||
|
"show",
|
||||||
|
"--input",
|
||||||
|
"foo.torrent",
|
||||||
|
"--json",
|
||||||
|
])
|
||||||
|
.out_is_term()
|
||||||
|
.build();
|
||||||
|
let path = env.resolve("foo.torrent").unwrap();
|
||||||
|
metainfo.dump(path).unwrap();
|
||||||
|
env.assert_ok();
|
||||||
|
let have = env.out();
|
||||||
|
assert_eq!(have, want);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let metainfo = Metainfo::test_value_single_unset();
|
||||||
|
let mut want = r#"{"name":"NAME","comment":null,"creation_date":null,
|
||||||
|
"created_by":null,"source":null,"info_hash":"a9105b0ff5f7cefeee5599ed7831749be21cc04e",
|
||||||
|
"torrent_size":85,"content_size":5,"private":false,"tracker":null,"announce_list":[],
|
||||||
|
"update_url":null,"dht_nodes":[],"piece_size":1024,"piece_count":1,"file_count":1,
|
||||||
|
"files":["NAME"]}"#
|
||||||
|
.replace('\n', "");
|
||||||
|
want.push('\n');
|
||||||
|
let mut env = TestEnvBuilder::new()
|
||||||
|
.arg_slice(&[
|
||||||
|
"imdl",
|
||||||
|
"torrent",
|
||||||
|
"show",
|
||||||
|
"--input",
|
||||||
|
"foo.torrent",
|
||||||
|
"--json",
|
||||||
|
])
|
||||||
|
.out_is_term()
|
||||||
|
.build();
|
||||||
|
let path = env.resolve("foo.torrent").unwrap();
|
||||||
|
metainfo.dump(path).unwrap();
|
||||||
|
env.assert_ok();
|
||||||
|
let have = env.out();
|
||||||
|
assert_eq!(have, want);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,27 @@ pub(crate) struct TorrentSummary {
|
||||||
size: Bytes,
|
size: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct TorrentSummaryJson {
|
||||||
|
name: String,
|
||||||
|
comment: Option<String>,
|
||||||
|
creation_date: Option<u64>,
|
||||||
|
created_by: Option<String>,
|
||||||
|
source: Option<String>,
|
||||||
|
info_hash: String,
|
||||||
|
torrent_size: u64,
|
||||||
|
content_size: u64,
|
||||||
|
private: bool,
|
||||||
|
tracker: Option<String>,
|
||||||
|
announce_list: Vec<Vec<String>>,
|
||||||
|
update_url: Option<String>,
|
||||||
|
dht_nodes: Vec<String>,
|
||||||
|
piece_size: u64,
|
||||||
|
piece_count: usize,
|
||||||
|
file_count: usize,
|
||||||
|
files: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl TorrentSummary {
|
impl TorrentSummary {
|
||||||
fn new(metainfo: Metainfo, infohash: Infohash, size: Bytes) -> Self {
|
fn new(metainfo: Metainfo, infohash: Infohash, size: Bytes) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -146,4 +167,70 @@ impl TorrentSummary {
|
||||||
|
|
||||||
table
|
table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write_json(&self, env: &mut Env) -> Result<()> {
|
||||||
|
let data = self.torrent_summary_data();
|
||||||
|
let json = serde_json::to_string(&data).context(error::JsonSerialize)?;
|
||||||
|
outln!(env, "{}", json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn torrent_summary_data(&self) -> TorrentSummaryJson {
|
||||||
|
let (file_count, files) = match &self.metainfo.info.mode {
|
||||||
|
Mode::Single { .. } => (1, vec![self.metainfo.info.name.to_string()]),
|
||||||
|
Mode::Multiple { files } => (
|
||||||
|
files.len(),
|
||||||
|
files
|
||||||
|
.iter()
|
||||||
|
.map(|file_info| {
|
||||||
|
format!(
|
||||||
|
"{}",
|
||||||
|
file_info
|
||||||
|
.path
|
||||||
|
.absolute(Path::new(&self.metainfo.info.name))
|
||||||
|
.as_path()
|
||||||
|
.display()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
TorrentSummaryJson {
|
||||||
|
name: self.metainfo.info.name.to_string(),
|
||||||
|
comment: self.metainfo.comment.clone(),
|
||||||
|
creation_date: self.metainfo.creation_date,
|
||||||
|
created_by: self.metainfo.created_by.clone(),
|
||||||
|
source: self.metainfo.info.source.clone(),
|
||||||
|
info_hash: self.infohash.to_string(),
|
||||||
|
torrent_size: self.size.count(),
|
||||||
|
content_size: self.metainfo.content_size().count(),
|
||||||
|
private: self.metainfo.info.private.unwrap_or_default(),
|
||||||
|
tracker: self.metainfo.announce.clone(),
|
||||||
|
announce_list: self
|
||||||
|
.metainfo
|
||||||
|
.announce_list
|
||||||
|
.as_ref()
|
||||||
|
.map(Clone::clone)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
update_url: self
|
||||||
|
.metainfo
|
||||||
|
.info
|
||||||
|
.update_url
|
||||||
|
.as_ref()
|
||||||
|
.map(ToString::to_string),
|
||||||
|
dht_nodes: self
|
||||||
|
.metainfo
|
||||||
|
.nodes
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&Vec::new())
|
||||||
|
.iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
piece_size: self.metainfo.info.piece_length.count(),
|
||||||
|
piece_count: self.metainfo.info.pieces.count(),
|
||||||
|
file_count,
|
||||||
|
files,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user