Skip to content

Commit 9a69f7e

Browse files
committed
Fix blocking code
Add more tests
1 parent d2be2ed commit 9a69f7e

File tree

9 files changed

+581
-294
lines changed

9 files changed

+581
-294
lines changed

check_queue_schema.py

Lines changed: 0 additions & 117 deletions
This file was deleted.

routes/queue.py

Lines changed: 85 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Queue management routes.
33
"""
44

5+
import asyncio
56
import logging
67
import threading
78
from typing import List
@@ -70,18 +71,7 @@ def get_current_queue():
7071
"""Get the current queue."""
7172
try:
7273
queue = get_queue()
73-
queue_dicts = [item.to_dict() for item in queue]
74-
75-
# Log queue items for debugging
76-
for item_dict in queue_dicts:
77-
logger.info(
78-
f"GET /queue returning item: id={item_dict['id']}, "
79-
f"type={item_dict.get('type', 'MISSING')}, "
80-
f"week_year={item_dict.get('week_year', 'MISSING')}, "
81-
f"title={item_dict['title'][:50]}"
82-
)
83-
84-
return JSONResponse({"queue": queue_dicts})
74+
return JSONResponse({"queue": [item.to_dict() for item in queue]})
8575
except Exception as e:
8676
logger.error(f"Error fetching queue: {e}")
8777
raise HTTPException(status_code=500, detail=str(e))
@@ -136,14 +126,8 @@ def play_next_in_queue():
136126
# Add type-specific fields
137127
if next_item.type == "summary":
138128
response["week_year"] = next_item.week_year
139-
logger.info(
140-
f"Playing next item (summary): {next_item.title} - week_year: {next_item.week_year}"
141-
)
142129
else:
143130
response["youtube_id"] = next_item.youtube_id
144-
logger.info(
145-
f"Playing next item (youtube): {next_item.title} - video_id: {next_item.youtube_id}"
146-
)
147131

148132
return JSONResponse(response)
149133
except Exception as e:
@@ -225,11 +209,92 @@ def _prefetch_worker():
225209
return JSONResponse({"status": "started", "video_id": video_id})
226210

227211

212+
def _run_suggestions_sync() -> dict:
213+
"""
214+
Run the entire suggestion pipeline synchronously.
215+
216+
This is designed to be called via asyncio.to_thread() so it doesn't
217+
block the event loop. All operations here (LLM calls, subprocess calls,
218+
database writes) are synchronous.
219+
"""
220+
from services.book_suggestions import get_video_suggestions
221+
222+
logger.info("Generating video suggestions based on recently watched content...")
223+
224+
suggestions = get_video_suggestions()
225+
226+
if not suggestions:
227+
return {
228+
"status": "no_suggestions",
229+
"message": "No suggestions could be generated. Check logs for details.",
230+
"added": [],
231+
}
232+
233+
added = []
234+
failed = []
235+
236+
for suggestion in suggestions:
237+
try:
238+
video_id = suggestion["video_id"]
239+
metadata = get_video_metadata(video_id)
240+
241+
if metadata:
242+
queue_id = add_to_queue(
243+
video_id,
244+
metadata["title"],
245+
metadata.get("channel"),
246+
metadata.get("thumbnail_url"),
247+
)
248+
added.append(
249+
{
250+
"queue_id": queue_id,
251+
"video_id": video_id,
252+
"title": metadata["title"],
253+
"channel": suggestion.get("channel", "Unknown"),
254+
}
255+
)
256+
logger.info(f"Added suggestion to queue: {metadata['title']}")
257+
else:
258+
queue_id = add_to_queue(
259+
video_id,
260+
suggestion["title"],
261+
suggestion.get("channel"),
262+
)
263+
added.append(
264+
{
265+
"queue_id": queue_id,
266+
"video_id": video_id,
267+
"title": suggestion["title"],
268+
"channel": suggestion.get("channel", "Unknown"),
269+
}
270+
)
271+
logger.warning(
272+
f"Could not fetch YouTube metadata for {video_id}, using search result"
273+
)
274+
275+
except Exception as e:
276+
logger.error(f"Failed to add suggestion to queue: {e}")
277+
failed.append(
278+
{"title": suggestion.get("title", "Unknown"), "error": str(e)}
279+
)
280+
281+
return {
282+
"status": "success",
283+
"message": f"Added {len(added)} video suggestions to queue",
284+
"added": added,
285+
"failed": failed,
286+
"total_suggestions": len(suggestions),
287+
}
288+
289+
228290
@router.post("/queue/suggestions")
229291
async def generate_and_queue_suggestions():
230292
"""
231293
Generate video suggestions based on recently watched content
232294
and automatically add them to the queue.
295+
296+
Runs the blocking suggestion pipeline in a thread so the event loop
297+
stays free to handle other requests (queue removal, playback, etc.).
233298
"""
234299
if not config.book_suggestions_enabled:
235300
raise HTTPException(
@@ -238,83 +303,8 @@ async def generate_and_queue_suggestions():
238303
)
239304

240305
try:
241-
from services.book_suggestions import get_video_suggestions
242-
243-
logger.info("Generating video suggestions based on recently watched content...")
244-
245-
# Get suggestions
246-
suggestions = await get_video_suggestions()
247-
248-
if not suggestions:
249-
return JSONResponse(
250-
{
251-
"status": "no_suggestions",
252-
"message": "No suggestions could be generated. Check logs for details.",
253-
"added": [],
254-
}
255-
)
256-
257-
# Add suggestions to queue
258-
added = []
259-
failed = []
260-
261-
for suggestion in suggestions:
262-
try:
263-
video_id = suggestion["video_id"]
264-
265-
# Try to get metadata from YouTube (validate the URL)
266-
metadata = get_video_metadata(video_id)
267-
268-
if metadata:
269-
queue_id = add_to_queue(
270-
video_id,
271-
metadata["title"],
272-
metadata.get("channel"),
273-
metadata.get("thumbnail_url"),
274-
)
275-
added.append(
276-
{
277-
"queue_id": queue_id,
278-
"video_id": video_id,
279-
"title": metadata["title"],
280-
"channel": suggestion.get("channel", "Unknown"),
281-
}
282-
)
283-
logger.info(f"Added suggestion to queue: {metadata['title']}")
284-
else:
285-
# Fall back to suggestion-provided title
286-
queue_id = add_to_queue(
287-
video_id,
288-
suggestion["title"],
289-
suggestion.get("channel"),
290-
)
291-
added.append(
292-
{
293-
"queue_id": queue_id,
294-
"video_id": video_id,
295-
"title": suggestion["title"],
296-
"channel": suggestion.get("channel", "Unknown"),
297-
}
298-
)
299-
logger.warning(
300-
f"Could not fetch YouTube metadata for {video_id}, using search result"
301-
)
302-
303-
except Exception as e:
304-
logger.error(f"Failed to add suggestion to queue: {e}")
305-
failed.append(
306-
{"title": suggestion.get("title", "Unknown"), "error": str(e)}
307-
)
308-
309-
return JSONResponse(
310-
{
311-
"status": "success",
312-
"message": f"Added {len(added)} video suggestions to queue",
313-
"added": added,
314-
"failed": failed,
315-
"total_suggestions": len(suggestions),
316-
}
317-
)
306+
result = await asyncio.to_thread(_run_suggestions_sync)
307+
return JSONResponse(result)
318308

319309
except Exception as e:
320310
logger.error(f"Error generating suggestions: {e}", exc_info=True)

services/book_suggestions.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def _fetch_summary_for_video(item: PlayHistoryItem) -> Optional[VideoSummary]:
9090
)
9191

9292

93-
async def get_recent_summaries(limit: int) -> List[VideoSummary]:
93+
def get_recent_summaries(limit: int) -> List[VideoSummary]:
9494
"""
9595
Get summaries from recently watched videos (fetched from Trilium).
9696
@@ -339,7 +339,7 @@ def search_youtube_by_theme(theme: str, count: int) -> List[Dict[str, str]]:
339339
return []
340340

341341

342-
async def filter_already_played(videos: List[Dict[str, str]]) -> List[Dict[str, str]]:
342+
def filter_already_played(videos: List[Dict[str, str]]) -> List[Dict[str, str]]:
343343
"""
344344
Filter out videos that have already been played.
345345
@@ -368,7 +368,7 @@ async def filter_already_played(videos: List[Dict[str, str]]) -> List[Dict[str,
368368
return videos # Return unfiltered on error
369369

370370

371-
async def get_video_suggestions() -> List[Dict[str, str]]:
371+
def get_video_suggestions() -> List[Dict[str, str]]:
372372
"""
373373
Get video suggestions based on recently watched content.
374374
@@ -387,7 +387,7 @@ async def get_video_suggestions() -> List[Dict[str, str]]:
387387
return []
388388

389389
# Step 1: Get recent summaries
390-
summaries = await get_recent_summaries(config.books_to_analyze)
390+
summaries = get_recent_summaries(config.books_to_analyze)
391391

392392
if not summaries:
393393
logger.warning("No summaries found from recent videos")
@@ -418,7 +418,7 @@ async def get_video_suggestions() -> List[Dict[str, str]]:
418418
return []
419419

420420
# Step 4: Filter out already played videos
421-
filtered_videos = await filter_already_played(videos)
421+
filtered_videos = filter_already_played(videos)
422422

423423
logger.info(f"Generated {len(filtered_videos)} new video suggestions")
424424
return filtered_videos

0 commit comments

Comments
 (0)