Skip to content

Commit e5733ad

Browse files
committed
fix trakt integration and support cancellation in search query
1 parent 700abbc commit e5733ad

File tree

2 files changed

+107
-23
lines changed

2 files changed

+107
-23
lines changed

src/tui/mod.rs

Lines changed: 104 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ fn spawn_torrent_search(
306306
tx: mpsc::Sender<UiMessage>,
307307
prowlarr_url: String,
308308
prowlarr_apikey: String,
309+
cancel_token: CancellationToken,
309310
) {
310311
use futures::stream::{self, StreamExt};
311312

@@ -322,6 +323,12 @@ fn spawn_torrent_search(
322323

323324
match prowlarr.get_usable_indexers().await {
324325
Ok(indexers) => {
326+
// Check if cancelled before proceeding
327+
if cancel_token.is_cancelled() {
328+
debug!(search_id, "search cancelled before indexer queries");
329+
return;
330+
}
331+
325332
if indexers.is_empty() {
326333
let _ = tx
327334
.send(UiMessage::SearchError(
@@ -363,6 +370,12 @@ fn spawn_torrent_search(
363370
stream::iter(search_futures).buffer_unordered(MAX_CONCURRENT_SEARCHES);
364371

365372
while let Some((indexer_name, result)) = results_stream.next().await {
373+
// Check for cancellation while collecting results
374+
if cancel_token.is_cancelled() {
375+
debug!(search_id, "search cancelled while collecting results");
376+
return;
377+
}
378+
366379
match result {
367380
Ok(results) => {
368381
debug!(
@@ -498,6 +511,8 @@ async fn run_app(
498511
let mut streaming_session: Option<std::sync::Arc<StreamingSession>> = None;
499512
// Cancellation token for streaming task
500513
let mut streaming_cancel: Option<CancellationToken> = None;
514+
// Cancellation token for search task
515+
let mut search_cancel: Option<CancellationToken> = None;
501516
// Stored torrent info for file selection
502517
let mut pending_torrent_info: Option<crate::streaming::TorrentInfo> = None;
503518

@@ -538,6 +553,27 @@ async fn run_app(
538553
// Check if auto-race is enabled
539554
let auto_race = config.streaming.auto_race as usize;
540555
if auto_race > 0 && !app.is_streaming {
556+
// Copy TMDB metadata to current_* fields (same as when manually selecting from Results)
557+
app.current_title = app
558+
.tmdb_info
559+
.as_ref()
560+
.map(|t| t.title.clone())
561+
.unwrap_or_else(|| app.search_input.clone());
562+
app.current_tmdb_id = app.tmdb_info.as_ref().and_then(|t| t.id);
563+
app.current_year = app.tmdb_info.as_ref().and_then(|t| t.year);
564+
app.current_media_type =
565+
app.tmdb_info.as_ref().and_then(|t| t.media_type.clone());
566+
app.current_poster_url =
567+
app.tmdb_info.as_ref().and_then(|t| t.poster_url.clone());
568+
569+
info!(
570+
title = %app.current_title,
571+
tmdb_id = ?app.current_tmdb_id,
572+
year = ?app.current_year,
573+
media_type = ?app.current_media_type,
574+
"auto-race: set metadata from TMDB"
575+
);
576+
541577
// Clean up any previous streaming session
542578
if let Some(cancel) = streaming_cancel.take() {
543579
cancel.cancel();
@@ -1259,6 +1295,14 @@ async fn run_app(
12591295
let is_movie = app.current_media_type.as_deref() == Some("movie");
12601296
let is_trakt_authenticated = config.providers.trakt.is_authenticated();
12611297

1298+
info!(
1299+
media_type = ?app.current_media_type,
1300+
is_movie = %is_movie,
1301+
is_trakt_authenticated = %is_trakt_authenticated,
1302+
watched_percent = %watched_percent,
1303+
"checking if should show rating prompt"
1304+
);
1305+
12621306
if is_movie && is_trakt_authenticated && watched_percent >= 50.0 {
12631307
info!("showing rating prompt for movie");
12641308
app.show_rating_prompt = true;
@@ -1282,11 +1326,18 @@ async fn run_app(
12821326
access_token.to_string(),
12831327
);
12841328

1329+
info!(title = %title, year = %year, tmdb_id = ?tmdb_id, "marking movie as watched on Trakt (no rating prompt)");
12851330
tokio::spawn(async move {
1286-
if let Err(e) =
1287-
provider.mark_movie_watched(title, year, tmdb_id).await
1331+
match provider
1332+
.mark_movie_watched(title.clone(), year, tmdb_id)
1333+
.await
12881334
{
1289-
error!(error = %e, "Failed to mark as watched on Trakt");
1335+
Ok(_) => {
1336+
info!(title = %title, "Successfully marked movie as watched on Trakt")
1337+
}
1338+
Err(e) => {
1339+
error!(error = %e, title = %title, "Failed to mark as watched on Trakt")
1340+
}
12901341
}
12911342
});
12921343
}
@@ -1474,12 +1525,23 @@ async fn run_app(
14741525
app.search_input = search_query.clone();
14751526
app.search_error = None;
14761527

1528+
// Cancel previous search if running
1529+
if let Some(cancel) = search_cancel.take() {
1530+
cancel.cancel();
1531+
debug!("cancelled previous search");
1532+
}
1533+
1534+
// Create new cancellation token for this search
1535+
let cancel_token = CancellationToken::new();
1536+
search_cancel = Some(cancel_token.clone());
1537+
14771538
spawn_torrent_search(
14781539
search_query,
14791540
app.search_id,
14801541
tx.clone(),
14811542
config.prowlarr.url.clone(),
14821543
config.prowlarr.apikey.clone(),
1544+
cancel_token,
14831545
);
14841546

14851547
// Navigate to Results view
@@ -1530,13 +1592,24 @@ async fn run_app(
15301592
app.is_searching = true;
15311593
app.search_error = None;
15321594
app.tmdb_info = None;
1595+
1596+
// Cancel previous search if running
1597+
if let Some(cancel) = search_cancel.take() {
1598+
cancel.cancel();
1599+
debug!("cancelled previous search");
1600+
}
1601+
15331602
let query = app.search_input.clone();
15341603
let current_search_id = app.search_id;
15351604
let tx = tx.clone();
15361605
let prowlarr_url = config.prowlarr.url.clone();
15371606
let prowlarr_apikey = config.prowlarr.apikey.clone();
15381607
let tmdb_apikey = config.tmdb.as_ref().map(|t| t.apikey.clone());
15391608

1609+
// Create new cancellation token for this search
1610+
let cancel_token = CancellationToken::new();
1611+
search_cancel = Some(cancel_token.clone());
1612+
15401613
// Spawn TMDB lookup task in parallel
15411614
let tmdb_tx = tx.clone();
15421615
let tmdb_query = query.clone();
@@ -1567,6 +1640,7 @@ async fn run_app(
15671640
tx.clone(),
15681641
prowlarr_url,
15691642
prowlarr_apikey,
1643+
cancel_token,
15701644
);
15711645
}
15721646
}
@@ -2162,18 +2236,30 @@ async fn run_app(
21622236
access_token.to_string(),
21632237
);
21642238

2239+
info!(title = %title, year = %year, rating = %rating, tmdb_id = ?tmdb_id, "submitting rating to Trakt");
21652240
tokio::spawn(async move {
21662241
// Rate and mark as watched
2167-
if let Err(e) = provider
2242+
match provider
21682243
.rate_movie(title.clone(), year, tmdb_id, rating)
21692244
.await
21702245
{
2171-
error!(error = %e, "Failed to rate on Trakt");
2246+
Ok(_) => {
2247+
info!(title = %title, rating = %rating, "Successfully rated movie on Trakt")
2248+
}
2249+
Err(e) => {
2250+
error!(error = %e, title = %title, "Failed to rate on Trakt")
2251+
}
21722252
}
2173-
if let Err(e) =
2174-
provider.mark_movie_watched(title, year, tmdb_id).await
2253+
match provider
2254+
.mark_movie_watched(title.clone(), year, tmdb_id)
2255+
.await
21752256
{
2176-
error!(error = %e, "Failed to mark as watched on Trakt");
2257+
Ok(_) => {
2258+
info!(title = %title, "Successfully marked as watched on Trakt")
2259+
}
2260+
Err(e) => {
2261+
error!(error = %e, title = %title, "Failed to mark as watched on Trakt")
2262+
}
21772263
}
21782264
});
21792265
}
@@ -2222,11 +2308,18 @@ async fn run_app(
22222308
access_token.to_string(),
22232309
);
22242310

2311+
info!(title = %title, year = %year, tmdb_id = ?tmdb_id, watched_percent = %watched_percent, "marking movie as watched on Trakt (skipped rating)");
22252312
tokio::spawn(async move {
2226-
if let Err(e) =
2227-
provider.mark_movie_watched(title, year, tmdb_id).await
2313+
match provider
2314+
.mark_movie_watched(title.clone(), year, tmdb_id)
2315+
.await
22282316
{
2229-
error!(error = %e, "Failed to mark as watched on Trakt");
2317+
Ok(_) => {
2318+
info!(title = %title, "Successfully marked movie as watched on Trakt")
2319+
}
2320+
Err(e) => {
2321+
error!(error = %e, title = %title, "Failed to mark as watched on Trakt")
2322+
}
22302323
}
22312324
});
22322325
}

tests/tmdb_integration.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,7 @@ async fn test_client_with_api_key() {
144144
async fn test_client_without_api_key() {
145145
// Test client creation without API key
146146
// Note: This may succeed if TMDB_API_KEY is embedded at compile time
147-
let client = TmdbClient::with_base_url(None, "http://example.com");
148-
149-
// We can't assert None because an embedded key might exist
150-
// But we can verify that the constructor doesn't panic
151-
// and that the returned value is consistent
152-
if client.is_some() {
153-
// Embedded key exists - this is expected behavior
154-
assert!(true);
155-
} else {
156-
// No embedded key - this is also expected behavior
157-
assert!(true);
158-
}
147+
// This test verifies the constructor doesn't panic - both Some and None are valid outcomes
148+
let _client = TmdbClient::with_base_url(None, "http://example.com");
149+
// Test passes as long as the constructor completes without panicking
159150
}

0 commit comments

Comments
 (0)