Hash-based API in addition to integer based
This commit is contained in:
parent
ec7d761d2c
commit
473edf28dd
5 changed files with 168 additions and 37 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashSet, net::SocketAddr, sync::Arc};
|
use std::{collections::HashSet, marker::PhantomData, net::SocketAddr, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use buffers::ByteBufOwned;
|
use buffers::ByteBufOwned;
|
||||||
|
|
@ -36,6 +36,93 @@ pub struct Api {
|
||||||
line_broadcast: Option<LineBroadcast>,
|
line_broadcast: Option<LineBroadcast>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum TorrentIdOrHash {
|
||||||
|
Id(TorrentId),
|
||||||
|
Hash(Id20),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for TorrentIdOrHash {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
TorrentIdOrHash::Id(id) => id.serialize(serializer),
|
||||||
|
TorrentIdOrHash::Hash(h) => h.as_string().serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for TorrentIdOrHash {
|
||||||
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Default)]
|
||||||
|
struct V<'de> {
|
||||||
|
p: PhantomData<&'de ()>,
|
||||||
|
}
|
||||||
|
impl<'de> serde::de::Visitor<'de> for V<'de> {
|
||||||
|
type Value = TorrentIdOrHash;
|
||||||
|
|
||||||
|
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
f.write_str("integer or 40 byte info hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
TorrentIdOrHash::parse(v)
|
||||||
|
.map_err(|_| E::custom("expected integer or 40 byte info hash"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(V::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TorrentIdOrHash {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TorrentIdOrHash::Id(id) => write!(f, "{}", id),
|
||||||
|
TorrentIdOrHash::Hash(h) => write!(f, "{:?}", h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TorrentId> for TorrentIdOrHash {
|
||||||
|
fn from(value: TorrentId) -> Self {
|
||||||
|
TorrentIdOrHash::Id(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Id20> for TorrentIdOrHash {
|
||||||
|
fn from(value: Id20) -> Self {
|
||||||
|
TorrentIdOrHash::Hash(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for TorrentIdOrHash {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &'a str) -> std::result::Result<Self, Self::Error> {
|
||||||
|
Self::parse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TorrentIdOrHash {
|
||||||
|
pub fn parse(s: &str) -> anyhow::Result<Self> {
|
||||||
|
if s.len() == 40 {
|
||||||
|
let id = Id20::from_str(s)?;
|
||||||
|
return Ok(id.into());
|
||||||
|
}
|
||||||
|
let id: TorrentId = s.parse()?;
|
||||||
|
Ok(id.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Api {
|
impl Api {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
session: Arc<Session>,
|
session: Arc<Session>,
|
||||||
|
|
@ -53,7 +140,7 @@ impl Api {
|
||||||
&self.session
|
&self.session
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mgr_handle(&self, idx: TorrentId) -> Result<ManagedTorrentHandle> {
|
pub fn mgr_handle(&self, idx: TorrentIdOrHash) -> Result<ManagedTorrentHandle> {
|
||||||
self.session
|
self.session
|
||||||
.get(idx)
|
.get(idx)
|
||||||
.ok_or(ApiError::torrent_not_found(idx))
|
.ok_or(ApiError::torrent_not_found(idx))
|
||||||
|
|
@ -71,14 +158,18 @@ impl Api {
|
||||||
TorrentListResponse { torrents: items }
|
TorrentListResponse { torrents: items }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api_torrent_details(&self, idx: TorrentId) -> Result<TorrentDetailsResponse> {
|
pub fn api_torrent_details(&self, idx: TorrentIdOrHash) -> Result<TorrentDetailsResponse> {
|
||||||
let handle = self.mgr_handle(idx)?;
|
let handle = self.mgr_handle(idx)?;
|
||||||
let info_hash = handle.info().info_hash;
|
let info_hash = handle.info().info_hash;
|
||||||
let only_files = handle.only_files();
|
let only_files = handle.only_files();
|
||||||
make_torrent_details(&info_hash, &handle.info().info, only_files.as_deref())
|
make_torrent_details(&info_hash, &handle.info().info, only_files.as_deref())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn torrent_file_mime_type(&self, idx: TorrentId, file_idx: usize) -> Result<&'static str> {
|
pub fn torrent_file_mime_type(
|
||||||
|
&self,
|
||||||
|
idx: TorrentIdOrHash,
|
||||||
|
file_idx: usize,
|
||||||
|
) -> Result<&'static str> {
|
||||||
let handle = self.mgr_handle(idx)?;
|
let handle = self.mgr_handle(idx)?;
|
||||||
let info = &handle.info().info;
|
let info = &handle.info().info;
|
||||||
torrent_file_mime_type(info, file_idx)
|
torrent_file_mime_type(info, file_idx)
|
||||||
|
|
@ -86,7 +177,7 @@ impl Api {
|
||||||
|
|
||||||
pub fn api_peer_stats(
|
pub fn api_peer_stats(
|
||||||
&self,
|
&self,
|
||||||
idx: TorrentId,
|
idx: TorrentIdOrHash,
|
||||||
filter: PeerStatsFilter,
|
filter: PeerStatsFilter,
|
||||||
) -> Result<PeerStatsSnapshot> {
|
) -> Result<PeerStatsSnapshot> {
|
||||||
let handle = self.mgr_handle(idx)?;
|
let handle = self.mgr_handle(idx)?;
|
||||||
|
|
@ -96,7 +187,10 @@ impl Api {
|
||||||
.per_peer_stats_snapshot(filter))
|
.per_peer_stats_snapshot(filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn api_torrent_action_pause(&self, idx: TorrentId) -> Result<EmptyJsonResponse> {
|
pub async fn api_torrent_action_pause(
|
||||||
|
&self,
|
||||||
|
idx: TorrentIdOrHash,
|
||||||
|
) -> Result<EmptyJsonResponse> {
|
||||||
let handle = self.mgr_handle(idx)?;
|
let handle = self.mgr_handle(idx)?;
|
||||||
self.session()
|
self.session()
|
||||||
.pause(&handle)
|
.pause(&handle)
|
||||||
|
|
@ -106,7 +200,10 @@ impl Api {
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn api_torrent_action_start(&self, idx: TorrentId) -> Result<EmptyJsonResponse> {
|
pub async fn api_torrent_action_start(
|
||||||
|
&self,
|
||||||
|
idx: TorrentIdOrHash,
|
||||||
|
) -> Result<EmptyJsonResponse> {
|
||||||
let handle = self.mgr_handle(idx)?;
|
let handle = self.mgr_handle(idx)?;
|
||||||
self.session
|
self.session
|
||||||
.unpause(&handle)
|
.unpause(&handle)
|
||||||
|
|
@ -116,7 +213,10 @@ impl Api {
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn api_torrent_action_forget(&self, idx: TorrentId) -> Result<EmptyJsonResponse> {
|
pub async fn api_torrent_action_forget(
|
||||||
|
&self,
|
||||||
|
idx: TorrentIdOrHash,
|
||||||
|
) -> Result<EmptyJsonResponse> {
|
||||||
self.session
|
self.session
|
||||||
.delete(idx, false)
|
.delete(idx, false)
|
||||||
.await
|
.await
|
||||||
|
|
@ -124,7 +224,10 @@ impl Api {
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn api_torrent_action_delete(&self, idx: TorrentId) -> Result<EmptyJsonResponse> {
|
pub async fn api_torrent_action_delete(
|
||||||
|
&self,
|
||||||
|
idx: TorrentIdOrHash,
|
||||||
|
) -> Result<EmptyJsonResponse> {
|
||||||
self.session
|
self.session
|
||||||
.delete(idx, true)
|
.delete(idx, true)
|
||||||
.await
|
.await
|
||||||
|
|
@ -134,7 +237,7 @@ impl Api {
|
||||||
|
|
||||||
pub async fn api_torrent_action_update_only_files(
|
pub async fn api_torrent_action_update_only_files(
|
||||||
&self,
|
&self,
|
||||||
idx: TorrentId,
|
idx: TorrentIdOrHash,
|
||||||
only_files: &HashSet<usize>,
|
only_files: &HashSet<usize>,
|
||||||
) -> Result<EmptyJsonResponse> {
|
) -> Result<EmptyJsonResponse> {
|
||||||
let handle = self.mgr_handle(idx)?;
|
let handle = self.mgr_handle(idx)?;
|
||||||
|
|
@ -240,23 +343,23 @@ impl Api {
|
||||||
Ok(dht.with_routing_table(|r| r.clone()))
|
Ok(dht.with_routing_table(|r| r.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api_stats_v0(&self, idx: TorrentId) -> Result<LiveStats> {
|
pub fn api_stats_v0(&self, idx: TorrentIdOrHash) -> Result<LiveStats> {
|
||||||
let mgr = self.mgr_handle(idx)?;
|
let mgr = self.mgr_handle(idx)?;
|
||||||
let live = mgr.live().context("torrent not live")?;
|
let live = mgr.live().context("torrent not live")?;
|
||||||
Ok(LiveStats::from(&*live))
|
Ok(LiveStats::from(&*live))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api_stats_v1(&self, idx: TorrentId) -> Result<TorrentStats> {
|
pub fn api_stats_v1(&self, idx: TorrentIdOrHash) -> Result<TorrentStats> {
|
||||||
let mgr = self.mgr_handle(idx)?;
|
let mgr = self.mgr_handle(idx)?;
|
||||||
Ok(mgr.stats())
|
Ok(mgr.stats())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api_dump_haves(&self, idx: usize) -> Result<String> {
|
pub fn api_dump_haves(&self, idx: TorrentIdOrHash) -> Result<String> {
|
||||||
let mgr = self.mgr_handle(idx)?;
|
let mgr = self.mgr_handle(idx)?;
|
||||||
Ok(mgr.with_chunk_tracker(|chunks| format!("{:?}", chunks.get_have_pieces()))?)
|
Ok(mgr.with_chunk_tracker(|chunks| format!("{:?}", chunks.get_have_pieces()))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn api_stream(&self, idx: TorrentId, file_id: usize) -> Result<FileStream> {
|
pub fn api_stream(&self, idx: TorrentIdOrHash, file_id: usize) -> Result<FileStream> {
|
||||||
let mgr = self.mgr_handle(idx)?;
|
let mgr = self.mgr_handle(idx)?;
|
||||||
Ok(mgr.stream(file_id)?)
|
Ok(mgr.stream(file_id)?)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ use axum::response::{IntoResponse, Response};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
|
|
||||||
|
use crate::api::TorrentIdOrHash;
|
||||||
|
|
||||||
// Convenience error type.
|
// Convenience error type.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ApiError {
|
pub struct ApiError {
|
||||||
|
|
@ -19,7 +21,7 @@ impl ApiError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn torrent_not_found(torrent_id: usize) -> Self {
|
pub const fn torrent_not_found(torrent_id: TorrentIdOrHash) -> Self {
|
||||||
Self {
|
Self {
|
||||||
status: Some(StatusCode::NOT_FOUND),
|
status: Some(StatusCode::NOT_FOUND),
|
||||||
kind: ApiErrorKind::TorrentNotFound(torrent_id),
|
kind: ApiErrorKind::TorrentNotFound(torrent_id),
|
||||||
|
|
@ -75,7 +77,7 @@ impl ApiError {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ApiErrorKind {
|
enum ApiErrorKind {
|
||||||
TorrentNotFound(usize),
|
TorrentNotFound(TorrentIdOrHash),
|
||||||
DhtDisabled,
|
DhtDisabled,
|
||||||
Text(&'static str),
|
Text(&'static str),
|
||||||
Other(anyhow::Error),
|
Other(anyhow::Error),
|
||||||
|
|
@ -93,7 +95,7 @@ impl Serialize for ApiError {
|
||||||
status: u16,
|
status: u16,
|
||||||
status_text: String,
|
status_text: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
id: Option<usize>,
|
id: Option<TorrentIdOrHash>,
|
||||||
}
|
}
|
||||||
let mut serr: SerializedError = SerializedError {
|
let mut serr: SerializedError = SerializedError {
|
||||||
error_kind: match self.kind {
|
error_kind: match self.kind {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use tracing::{debug, info, trace};
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
|
||||||
use crate::api::Api;
|
use crate::api::{Api, TorrentIdOrHash};
|
||||||
use crate::peer_connection::PeerConnectionOptions;
|
use crate::peer_connection::PeerConnectionOptions;
|
||||||
use crate::session::{AddTorrent, AddTorrentOptions, SUPPORTED_SCHEMES};
|
use crate::session::{AddTorrent, AddTorrentOptions, SUPPORTED_SCHEMES};
|
||||||
use crate::torrent_state::peer::stats::snapshot::PeerStatsFilter;
|
use crate::torrent_state::peer::stats::snapshot::PeerStatsFilter;
|
||||||
|
|
@ -124,7 +124,7 @@ impl HttpApi {
|
||||||
|
|
||||||
async fn torrent_details(
|
async fn torrent_details(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_torrent_details(idx).map(axum::Json)
|
state.api_torrent_details(idx).map(axum::Json)
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +168,7 @@ impl HttpApi {
|
||||||
|
|
||||||
fn build_playlist_content(
|
fn build_playlist_content(
|
||||||
host: &str,
|
host: &str,
|
||||||
it: impl IntoIterator<Item = (usize, usize, String)>,
|
it: impl IntoIterator<Item = (TorrentIdOrHash, usize, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let body = it
|
let body = it
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -240,7 +240,7 @@ impl HttpApi {
|
||||||
async fn torrent_playlist(
|
async fn torrent_playlist(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
let host = get_host(&headers)?;
|
let host = get_host(&headers)?;
|
||||||
let playlist_items = torrent_playlist_items(&*state.mgr_handle(idx)?)?;
|
let playlist_items = torrent_playlist_items(&*state.mgr_handle(idx)?)?;
|
||||||
|
|
@ -263,7 +263,7 @@ impl HttpApi {
|
||||||
torrent_playlist_items(handle)
|
torrent_playlist_items(handle)
|
||||||
.map(move |items| {
|
.map(move |items| {
|
||||||
items.into_iter().map(move |(file_idx, filename)| {
|
items.into_iter().map(move |(file_idx, filename)| {
|
||||||
(torrent_idx, file_idx, filename)
|
(torrent_idx.into(), file_idx, filename)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
|
|
@ -276,28 +276,28 @@ impl HttpApi {
|
||||||
|
|
||||||
async fn torrent_haves(
|
async fn torrent_haves(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_dump_haves(idx)
|
state.api_dump_haves(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn torrent_stats_v0(
|
async fn torrent_stats_v0(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_stats_v0(idx).map(axum::Json)
|
state.api_stats_v0(idx).map(axum::Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn torrent_stats_v1(
|
async fn torrent_stats_v1(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_stats_v1(idx).map(axum::Json)
|
state.api_stats_v1(idx).map(axum::Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn peer_stats(
|
async fn peer_stats(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
Query(filter): Query<PeerStatsFilter>,
|
Query(filter): Query<PeerStatsFilter>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_peer_stats(idx, filter).map(axum::Json)
|
state.api_peer_stats(idx, filter).map(axum::Json)
|
||||||
|
|
@ -305,7 +305,7 @@ impl HttpApi {
|
||||||
|
|
||||||
async fn torrent_stream_file(
|
async fn torrent_stream_file(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path((idx, file_id)): Path<(usize, usize)>,
|
Path((idx, file_id)): Path<(TorrentIdOrHash, usize)>,
|
||||||
headers: http::HeaderMap,
|
headers: http::HeaderMap,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
let mut stream = state.api_stream(idx, file_id)?;
|
let mut stream = state.api_stream(idx, file_id)?;
|
||||||
|
|
@ -321,7 +321,7 @@ impl HttpApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
let range_header = headers.get(http::header::RANGE);
|
let range_header = headers.get(http::header::RANGE);
|
||||||
trace!(torrent_id=idx, file_id=file_id, range=?range_header, "request for HTTP stream");
|
trace!(torrent_id=%idx, file_id=file_id, range=?range_header, "request for HTTP stream");
|
||||||
|
|
||||||
if let Some(range) = range_header {
|
if let Some(range) = range_header {
|
||||||
let offset: Option<u64> = range
|
let offset: Option<u64> = range
|
||||||
|
|
@ -366,28 +366,28 @@ impl HttpApi {
|
||||||
|
|
||||||
async fn torrent_action_pause(
|
async fn torrent_action_pause(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_torrent_action_pause(idx).await.map(axum::Json)
|
state.api_torrent_action_pause(idx).await.map(axum::Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn torrent_action_start(
|
async fn torrent_action_start(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_torrent_action_start(idx).await.map(axum::Json)
|
state.api_torrent_action_start(idx).await.map(axum::Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn torrent_action_forget(
|
async fn torrent_action_forget(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_torrent_action_forget(idx).await.map(axum::Json)
|
state.api_torrent_action_forget(idx).await.map(axum::Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn torrent_action_delete(
|
async fn torrent_action_delete(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state.api_torrent_action_delete(idx).await.map(axum::Json)
|
state.api_torrent_action_delete(idx).await.map(axum::Json)
|
||||||
}
|
}
|
||||||
|
|
@ -399,7 +399,7 @@ impl HttpApi {
|
||||||
|
|
||||||
async fn torrent_action_update_only_files(
|
async fn torrent_action_update_only_files(
|
||||||
State(state): State<ApiState>,
|
State(state): State<ApiState>,
|
||||||
Path(idx): Path<usize>,
|
Path(idx): Path<TorrentIdOrHash>,
|
||||||
axum::Json(req): axum::Json<UpdateOnlyFilesRequest>,
|
axum::Json(req): axum::Json<UpdateOnlyFilesRequest>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
state
|
state
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
api::TorrentIdOrHash,
|
||||||
dht_utils::{read_metainfo_from_peer_receiver, ReadMetainfoResult},
|
dht_utils::{read_metainfo_from_peer_receiver, ReadMetainfoResult},
|
||||||
merge_streams::merge_streams,
|
merge_streams::merge_streams,
|
||||||
peer_connection::PeerConnectionOptions,
|
peer_connection::PeerConnectionOptions,
|
||||||
|
|
@ -1091,11 +1092,36 @@ impl Session {
|
||||||
Ok(AddTorrentResponse::Added(id, managed_torrent))
|
Ok(AddTorrentResponse::Added(id, managed_torrent))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, id: TorrentId) -> Option<ManagedTorrentHandle> {
|
pub fn get(&self, id: TorrentIdOrHash) -> Option<ManagedTorrentHandle> {
|
||||||
self.db.read().torrents.get(&id).cloned()
|
match id {
|
||||||
|
TorrentIdOrHash::Id(id) => self.db.read().torrents.get(&id).cloned(),
|
||||||
|
TorrentIdOrHash::Hash(id) => self.db.read().torrents.iter().find_map(|(_, v)| {
|
||||||
|
if v.info_hash() == id {
|
||||||
|
Some(v.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, id: TorrentId, delete_files: bool) -> anyhow::Result<()> {
|
pub async fn delete(&self, id: TorrentIdOrHash, delete_files: bool) -> anyhow::Result<()> {
|
||||||
|
let id = match id {
|
||||||
|
TorrentIdOrHash::Id(id) => id,
|
||||||
|
TorrentIdOrHash::Hash(h) => self
|
||||||
|
.db
|
||||||
|
.read()
|
||||||
|
.torrents
|
||||||
|
.values()
|
||||||
|
.find_map(|v| {
|
||||||
|
if v.info_hash() == h {
|
||||||
|
Some(v.id())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.context("no such torrent in db")?,
|
||||||
|
};
|
||||||
let removed = self
|
let removed = self
|
||||||
.db
|
.db
|
||||||
.write()
|
.write()
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@ async fn test_e2e_download() {
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("handle is completed");
|
info!("handle is completed");
|
||||||
session.delete(id, false).await.unwrap();
|
session.delete(id.into(), false).await.unwrap();
|
||||||
|
|
||||||
info!("deleted handle");
|
info!("deleted handle");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue