add download url settings
Signed-off-by: Xe <me@christine.website>
This commit is contained in:
parent
11a333bef4
commit
8933661b03
|
@ -338,6 +338,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lena"
|
name = "lena"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"transmission_rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
|
|
|
@ -6,3 +6,5 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
|
transmission_rs = { path = "../transmission-rs" }
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
|
@ -16,13 +16,12 @@ mod list;
|
||||||
use list::FieldList;
|
use list::FieldList;
|
||||||
use list::TorrentList;
|
use list::TorrentList;
|
||||||
pub mod torrent;
|
pub mod torrent;
|
||||||
use torrent::Torrent;
|
|
||||||
use torrent::TorrentFile;
|
|
||||||
use torrent::AddTorrent;
|
use torrent::AddTorrent;
|
||||||
use torrent::AddTorrentResponse;
|
use torrent::AddTorrentResponse;
|
||||||
use torrent::GetTorrentFilesRequest;
|
use torrent::GetTorrentFilesRequest;
|
||||||
use torrent::GetTorrentFilesResponse;
|
use torrent::GetTorrentFilesResponse;
|
||||||
//use torrent::AddedTorrent;
|
use torrent::Torrent;
|
||||||
|
use torrent::TorrentFile;
|
||||||
|
|
||||||
/// Interface into the major functions of Transmission
|
/// Interface into the major functions of Transmission
|
||||||
/// including adding, and removing torrents.
|
/// including adding, and removing torrents.
|
||||||
|
@ -43,19 +42,34 @@ pub struct Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
// TODO: Take Option<(&str, &str)> for auth
|
pub fn new<T, U>(
|
||||||
pub fn new(host: impl ToString, port: u16, tls: bool, auth: Option<(String, String)>) -> Result<Self, Error> {
|
host: impl ToString,
|
||||||
|
port: u16,
|
||||||
|
tls: bool,
|
||||||
|
auth: Option<(T, U)>,
|
||||||
|
) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
U: Into<String>,
|
||||||
|
{
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
headers.insert(reqwest::header::USER_AGENT, "transmission-rs/0.1".try_into()?);
|
headers.insert(
|
||||||
let this = Self{
|
reqwest::header::USER_AGENT,
|
||||||
|
"transmission-rs/0.1".try_into()?,
|
||||||
|
);
|
||||||
|
let auth = match auth {
|
||||||
|
Some((u, p)) => Some((u.into(), p.into())),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let this = Self {
|
||||||
host: host.to_string(),
|
host: host.to_string(),
|
||||||
port: port,
|
port,
|
||||||
tls: tls,
|
tls,
|
||||||
auth: auth,
|
auth,
|
||||||
base_url: {
|
base_url: {
|
||||||
let protocol = match tls {
|
let protocol = match tls {
|
||||||
true => "https",
|
true => "https",
|
||||||
false => "http"
|
false => "http",
|
||||||
};
|
};
|
||||||
format!("{}://{}:{}", protocol, host.to_string(), port)
|
format!("{}://{}:{}", protocol, host.to_string(), port)
|
||||||
},
|
},
|
||||||
|
@ -63,7 +77,7 @@ impl Client {
|
||||||
http: reqwest::Client::builder()
|
http: reqwest::Client::builder()
|
||||||
.gzip(true)
|
.gzip(true)
|
||||||
.default_headers(headers)
|
.default_headers(headers)
|
||||||
.build()?
|
.build()?,
|
||||||
};
|
};
|
||||||
Ok(this)
|
Ok(this)
|
||||||
}
|
}
|
||||||
|
@ -74,13 +88,14 @@ impl Client {
|
||||||
|
|
||||||
pub async fn authenticate(&mut self) -> Result<(), Error> {
|
pub async fn authenticate(&mut self) -> Result<(), Error> {
|
||||||
self.session_id = None;
|
self.session_id = None;
|
||||||
let response = self.post("/transmission/rpc/")
|
let response = self
|
||||||
|
.post("/transmission/rpc/")
|
||||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
self.session_id = match response.headers().get("X-Transmission-Session-Id") {
|
self.session_id = match response.headers().get("X-Transmission-Session-Id") {
|
||||||
Some(v) => Some(v.to_str()?.to_string()),
|
Some(v) => Some(v.to_str()?.to_string()),
|
||||||
None => None // TODO: Return an error
|
None => None, // TODO: Return an error
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -91,28 +106,35 @@ impl Client {
|
||||||
let mut request = self.http.post(&url);
|
let mut request = self.http.post(&url);
|
||||||
request = match &self.auth {
|
request = match &self.auth {
|
||||||
Some(auth) => request.basic_auth(&auth.0, Some(&auth.1)),
|
Some(auth) => request.basic_auth(&auth.0, Some(&auth.1)),
|
||||||
None => request
|
None => request,
|
||||||
};
|
};
|
||||||
request = match &self.session_id {
|
request = match &self.session_id {
|
||||||
Some(token) => request.header("X-Transmission-Session-Id", token),
|
Some(token) => request.header("X-Transmission-Session-Id", token),
|
||||||
None => request
|
None => request,
|
||||||
};
|
};
|
||||||
request
|
request
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn rpc(&mut self, method: impl ToString, tag: u8, body: impl Serialize) -> Result<reqwest::Response, Error> {
|
pub async fn rpc(
|
||||||
|
&mut self,
|
||||||
|
method: impl ToString,
|
||||||
|
tag: u8,
|
||||||
|
body: impl Serialize,
|
||||||
|
) -> Result<reqwest::Response, Error> {
|
||||||
let request = Request::new(method, tag, body);
|
let request = Request::new(method, tag, body);
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
for i in 0..5 {
|
for i in 0..5 {
|
||||||
sleep(Duration::from_secs(i)).await;
|
sleep(Duration::from_secs(i)).await;
|
||||||
let result = self.post("/transmission/rpc/")
|
let result = self
|
||||||
|
.post("/transmission/rpc/")
|
||||||
// Content-Type doesn't actually appear to be necessary, and is
|
// Content-Type doesn't actually appear to be necessary, and is
|
||||||
// technically a lie, since there's no key/value being passed,
|
// technically a lie, since there's no key/value being passed,
|
||||||
// just some JSON, but the official client sends this, so we
|
// just some JSON, but the official client sends this, so we
|
||||||
// will too.
|
// will too.
|
||||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
.body(serde_json::to_string(&request)?)
|
.body(serde_json::to_string(&request)?)
|
||||||
.send().await?
|
.send()
|
||||||
|
.await?
|
||||||
.error_for_status();
|
.error_for_status();
|
||||||
match result {
|
match result {
|
||||||
Ok(r) => return Ok(r),
|
Ok(r) => return Ok(r),
|
||||||
|
@ -121,15 +143,15 @@ impl Client {
|
||||||
self.authenticate().await?;
|
self.authenticate().await?;
|
||||||
error = Some(e);
|
error = Some(e);
|
||||||
continue;
|
continue;
|
||||||
},
|
|
||||||
_ => return Err(e.into())
|
|
||||||
}
|
}
|
||||||
|
_ => return Err(e.into()),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
match error {
|
match error {
|
||||||
Some(e) => Err(e.into()),
|
Some(e) => Err(e.into()),
|
||||||
// Should be unreachable
|
// Should be unreachable
|
||||||
None => Err(Error::HTTPUnknown)
|
None => Err(Error::HTTPUnknown),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,32 +170,51 @@ impl Client {
|
||||||
"rateUpload",
|
"rateUpload",
|
||||||
"sizeWhenDone",
|
"sizeWhenDone",
|
||||||
"status",
|
"status",
|
||||||
"uploadRatio"
|
"uploadRatio",
|
||||||
]);
|
]);
|
||||||
let response: Response<TorrentList> = self.rpc("torrent-get", 4, field_list).await?.error_for_status()?.json().await?;
|
let response: Response<TorrentList> = self
|
||||||
|
.rpc("torrent-get", 4, field_list)
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
Ok(response.arguments.torrents)
|
Ok(response.arguments.torrents)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Borrow key from value
|
// TODO: Borrow key from value
|
||||||
pub async fn list_by_name(&mut self) -> Result<BTreeMap<String, Torrent>, Error> {
|
pub async fn list_by_name(&mut self) -> Result<BTreeMap<String, Torrent>, Error> {
|
||||||
Ok(self.list().await?.into_iter().map(|v| (v.name.clone(), v)).collect::<BTreeMap<_, _>>())
|
Ok(self
|
||||||
|
.list()
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| (v.name.clone(), v))
|
||||||
|
.collect::<BTreeMap<_, _>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_torrent_from_link(&mut self, url: impl ToString) -> Result<AddTorrentResponse, Error> {
|
pub async fn add_torrent(&mut self, data: AddTorrent) -> Result<AddTorrentResponse, Error> {
|
||||||
let response: Response<AddTorrentResponse> = self.rpc("torrent-add", 8, AddTorrent{filename: url.to_string()}).await?.error_for_status()?.json().await?;
|
let response: Response<AddTorrentResponse> = self
|
||||||
|
.rpc("torrent-add", 8, data)
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
Ok(response.arguments)
|
Ok(response.arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_files_by_id(&mut self, id: u16) -> Result<Vec<TorrentFile>, Error> {
|
pub async fn get_files_by_id(&mut self, id: u16) -> Result<Vec<TorrentFile>, Error> {
|
||||||
let response: Response<GetTorrentFilesResponse> = self.rpc("torrent-get", 3, GetTorrentFilesRequest::new(id)).await?.error_for_status()?.json().await?;
|
let response: Response<GetTorrentFilesResponse> = self
|
||||||
if(response.arguments.torrents.len() == 0) {
|
.rpc("torrent-get", 3, GetTorrentFilesRequest::new(id))
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
if (response.arguments.torrents.len() == 0) {
|
||||||
// TODO: Maybe make the Ok result an Option? Or maybe return a 404 error?
|
// TODO: Maybe make the Ok result an Option? Or maybe return a 404 error?
|
||||||
return Ok(vec![]);
|
return Ok(vec![]);
|
||||||
} else if(response.arguments.torrents.len() > 1) {
|
} else if (response.arguments.torrents.len() > 1) {
|
||||||
return Err(Error::WeirdGetFilesResponse);
|
return Err(Error::WeirdGetFilesResponse);
|
||||||
}
|
}
|
||||||
// TODO: Figure out how to move files out of this structure, since we're discarding the rest anyway, instead of cloning
|
// TODO: Figure out how to move files out of this structure, since we're discarding the rest anyway, instead of cloning
|
||||||
Ok(response.arguments.torrents[0].files.clone())
|
Ok(response.arguments.torrents[0].files.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,14 @@ pub struct Torrent {
|
||||||
pub rate_upload: u32,
|
pub rate_upload: u32,
|
||||||
pub size_when_done: u64,
|
pub size_when_done: u64,
|
||||||
pub status: u8,
|
pub status: u8,
|
||||||
pub upload_ratio: f32
|
pub upload_ratio: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct AddTorrent {
|
pub struct AddTorrent {
|
||||||
pub filename: String
|
pub filename: String,
|
||||||
|
#[serde(rename = "download-dir")]
|
||||||
|
pub download_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
@ -32,28 +34,28 @@ pub struct AddTorrent {
|
||||||
pub struct AddedTorrent {
|
pub struct AddedTorrent {
|
||||||
pub id: u16,
|
pub id: u16,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub hash_string: String
|
pub hash_string: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum AddTorrentResponse {
|
pub enum AddTorrentResponse {
|
||||||
TorrentAdded(AddedTorrent),
|
TorrentAdded(AddedTorrent),
|
||||||
TorrentDuplicate(AddedTorrent)
|
TorrentDuplicate(AddedTorrent),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct GetTorrentFilesRequest {
|
pub struct GetTorrentFilesRequest {
|
||||||
fields: Vec<String>,
|
fields: Vec<String>,
|
||||||
ids: u16
|
ids: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetTorrentFilesRequest {
|
impl GetTorrentFilesRequest {
|
||||||
pub fn new(id: u16) -> Self {
|
pub fn new(id: u16) -> Self {
|
||||||
Self{
|
Self {
|
||||||
fields: vec!["files".to_string()],
|
fields: vec!["files".to_string()],
|
||||||
ids: id
|
ids: id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +65,7 @@ impl GetTorrentFilesRequest {
|
||||||
pub struct TorrentFile {
|
pub struct TorrentFile {
|
||||||
pub name: PathBuf,
|
pub name: PathBuf,
|
||||||
pub bytes_completed: u64,
|
pub bytes_completed: u64,
|
||||||
pub length: u64
|
pub length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TorrentFile {
|
impl TorrentFile {
|
||||||
|
@ -74,11 +76,10 @@ impl TorrentFile {
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct TorrentFileWrapper {
|
pub struct TorrentFileWrapper {
|
||||||
pub files: Vec<TorrentFile>
|
pub files: Vec<TorrentFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct GetTorrentFilesResponse {
|
pub struct GetTorrentFilesResponse {
|
||||||
pub torrents: Vec<TorrentFileWrapper>
|
pub torrents: Vec<TorrentFileWrapper>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
rustc cargo rustfmt rls rust-analyzer
|
rustc cargo rustfmt rls rust-analyzer
|
||||||
pkg-config sqlite openssl
|
pkg-config sqlite openssl diesel-cli
|
||||||
|
|
||||||
# keep this line if you use bash
|
# keep this line if you use bash
|
||||||
pkgs.bashInteractive
|
pkgs.bashInteractive
|
||||||
];
|
];
|
||||||
|
DATABASE_URL = "./var/lena.db";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue