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:
|
||||
- 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:
|
||||
all:
|
||||
name: All
|
||||
|
@ -45,23 +51,31 @@ jobs:
|
|||
with:
|
||||
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
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: v1-${{ runner.os }}-cargo-registry
|
||||
key: ${{ env.CACHE_KEY_PREFIX }}-${{ runner.os }}-cargo-registry
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: v1-${{ runner.os }}-cargo-index
|
||||
key: ${{ env.CACHE_KEY_PREFIX }}-${{ runner.os }}-cargo-index
|
||||
|
||||
- name: Cache cargo build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: target
|
||||
key: v1-${{ runner.os }}-cargo-build-target
|
||||
key: ${{ env.CACHE_KEY_PREFIX }}-${{ runner.os }}-cargo-build-target
|
||||
|
||||
- name: Install Stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -626,6 +626,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde-hex",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha1",
|
||||
"snafu",
|
||||
|
|
|
@ -34,6 +34,7 @@ pretty_env_logger = "0.4.0"
|
|||
regex = "1.0.0"
|
||||
serde-hex = "0.1.0"
|
||||
serde_bytes = "0.11.0"
|
||||
serde_json = "1.0.57"
|
||||
serde_with = "1.4.0"
|
||||
sha1 = "0.6.0"
|
||||
snafu = "0.6.0"
|
||||
|
|
|
@ -136,6 +136,8 @@ pub(crate) enum Error {
|
|||
Unstable { feature: &'static str },
|
||||
#[snafu(display("Torrent verification failed."))]
|
||||
Verify,
|
||||
#[snafu(display("Failed to serialize JSON: {}", source))]
|
||||
JsonSerialize { source: serde_json::Error },
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
|
|
@ -9,6 +9,10 @@ const INPUT_POSITIONAL: &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)]
|
||||
#[structopt(
|
||||
help_message(consts::HELP_MESSAGE),
|
||||
|
@ -36,6 +40,13 @@ pub(crate) struct Show {
|
|||
help = INPUT_HELP,
|
||||
)]
|
||||
input_positional: Option<InputTarget>,
|
||||
#[structopt(
|
||||
name = JSON_OUTPUT,
|
||||
long = "json",
|
||||
short = "j",
|
||||
help = JSON_HELP,
|
||||
)]
|
||||
json: bool,
|
||||
}
|
||||
|
||||
impl Show {
|
||||
|
@ -49,7 +60,11 @@ impl Show {
|
|||
|
||||
let input = env.read(target)?;
|
||||
let summary = TorrentSummary::from_input(&input)?;
|
||||
summary.write(env)?;
|
||||
if self.json {
|
||||
summary.write_json(env)?;
|
||||
} else {
|
||||
summary.write(env)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -559,4 +574,63 @@ files\tNAME
|
|||
|
||||
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,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
fn new(metainfo: Metainfo, infohash: Infohash, size: Bytes) -> Self {
|
||||
Self {
|
||||
|
@ -146,4 +167,70 @@ impl TorrentSummary {
|
|||
|
||||
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