Import pipeline: probe, hardlink, rename, layout (#82)
This commit was merged in pull request #82.
This commit is contained in:
@@ -70,6 +70,24 @@ pub struct Torrent {
|
||||
pub labels: Vec<String>,
|
||||
}
|
||||
|
||||
/// One file inside a torrent, as Transmission reports it.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TorrentFile {
|
||||
/// Path relative to the torrent's download directory, torrent folder
|
||||
/// included.
|
||||
pub path: PathBuf,
|
||||
/// Size in bytes.
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
/// Where one torrent's data lives on disk.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TorrentContent {
|
||||
pub hash: String,
|
||||
pub download_dir: PathBuf,
|
||||
pub files: Vec<TorrentFile>,
|
||||
}
|
||||
|
||||
/// A Transmission RPC failure.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
@@ -218,6 +236,46 @@ impl TransmissionClient {
|
||||
Ok(response.torrents.into_iter().map(Torrent::from).collect())
|
||||
}
|
||||
|
||||
/// The file list and download directory of one torrent, by infohash.
|
||||
///
|
||||
/// `None` when Transmission no longer knows the hash — the operator may
|
||||
/// have removed the torrent by hand.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error for transport failures or rejected/malformed RPC
|
||||
/// responses.
|
||||
pub async fn torrent_content(&self, hash: &str) -> Result<Option<TorrentContent>, Error> {
|
||||
let arguments = self
|
||||
.call(
|
||||
"torrent-get",
|
||||
json!({
|
||||
"ids": [hash],
|
||||
"fields": ["hashString", "downloadDir", "files"]
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let response: RpcContentList = serde_json::from_value(arguments)
|
||||
.map_err(|error| Error::InvalidResponse(error.to_string()))?;
|
||||
|
||||
Ok(response
|
||||
.torrents
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|torrent| TorrentContent {
|
||||
hash: torrent.hash,
|
||||
download_dir: torrent.download_dir,
|
||||
files: torrent
|
||||
.files
|
||||
.into_iter()
|
||||
.map(|file| TorrentFile {
|
||||
path: file.name,
|
||||
size: file.length,
|
||||
})
|
||||
.collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Remove one torrent, optionally deleting its downloaded data.
|
||||
///
|
||||
/// # Errors
|
||||
@@ -291,6 +349,27 @@ struct RpcTorrentList {
|
||||
torrents: Vec<RpcTorrent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcContentList {
|
||||
torrents: Vec<RpcContent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcContent {
|
||||
#[serde(rename = "hashString")]
|
||||
hash: String,
|
||||
#[serde(rename = "downloadDir")]
|
||||
download_dir: PathBuf,
|
||||
#[serde(default)]
|
||||
files: Vec<RpcFile>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcFile {
|
||||
name: PathBuf,
|
||||
length: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RpcTorrent {
|
||||
id: i64,
|
||||
@@ -459,6 +538,61 @@ mod tests {
|
||||
assert_eq!(torrents[0].state, TorrentState::Unknown(99));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn torrent_content_lists_files_and_download_dir() {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("POST"))
|
||||
.and(body_partial_json(json!({
|
||||
"method": "torrent-get",
|
||||
"arguments": {"ids": ["abc"]}
|
||||
})))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"result": "success",
|
||||
"arguments": {"torrents": [{
|
||||
"hashString": "abc",
|
||||
"downloadDir": "/downloads",
|
||||
"files": [
|
||||
{"name": "Movie/Movie.mkv", "length": 100, "bytesCompleted": 100},
|
||||
{"name": "Movie/Movie.nfo", "length": 5, "bytesCompleted": 5}
|
||||
]
|
||||
}]}
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = TransmissionClient::new(&server.uri()).expect("client");
|
||||
let content = client
|
||||
.torrent_content("abc")
|
||||
.await
|
||||
.expect("content")
|
||||
.expect("torrent known");
|
||||
|
||||
assert_eq!(content.download_dir, PathBuf::from("/downloads"));
|
||||
assert_eq!(content.files.len(), 2);
|
||||
assert_eq!(content.files[0].path, PathBuf::from("Movie/Movie.mkv"));
|
||||
assert_eq!(content.files[0].size, 100);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_torrent_transmission_forgot_is_none_not_an_error() {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("POST"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"result": "success",
|
||||
"arguments": {"torrents": []}
|
||||
})))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = TransmissionClient::new(&server.uri()).expect("client");
|
||||
assert!(client
|
||||
.torrent_content("gone")
|
||||
.await
|
||||
.expect("call succeeds")
|
||||
.is_none());
|
||||
}
|
||||
|
||||
fn success() -> ResponseTemplate {
|
||||
ResponseTemplate::new(200).set_body_json(json!({"result": "success", "arguments": {}}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user