@@ -111,6 +111,132 @@ def search_media_with_availability(self, query: str, app_type: str, instance_nam
111111 except Exception as e :
112112 logger .error (f"Error searching TMDB: { e } " )
113113 return []
114+
115+ def search_media_with_availability_stream (self , query : str , app_type : str , instance_name : str ):
116+ """Stream search results as they become available"""
117+ api_key = self .get_tmdb_api_key ()
118+
119+ # Determine search type based on app
120+ media_type = "movie" if app_type == "radarr" else "tv" if app_type == "sonarr" else "multi"
121+
122+ try :
123+ # Use search to get movies or TV shows
124+ url = f"{ self .tmdb_base_url } /search/{ media_type } "
125+ params = {
126+ 'api_key' : api_key ,
127+ 'query' : query ,
128+ 'include_adult' : False
129+ }
130+
131+ response = requests .get (url , params = params , timeout = 10 )
132+ response .raise_for_status ()
133+
134+ data = response .json ()
135+
136+ # Get instance configuration for availability checking
137+ app_config = self .db .get_app_config (app_type )
138+ target_instance = None
139+ if app_config and app_config .get ('instances' ):
140+ for instance in app_config ['instances' ]:
141+ if instance .get ('name' ) == instance_name :
142+ target_instance = instance
143+ break
144+
145+ # Process items and yield results as they become available
146+ processed_items = []
147+
148+ for item in data .get ('results' , []):
149+ # Skip person results in multi search
150+ if item .get ('media_type' ) == 'person' :
151+ continue
152+
153+ # Determine media type
154+ item_type = item .get ('media_type' )
155+ if not item_type :
156+ # For single-type searches
157+ item_type = 'movie' if media_type == 'movie' else 'tv'
158+
159+ # Skip if media type doesn't match app type
160+ if app_type == "radarr" and item_type != "movie" :
161+ continue
162+ if app_type == "sonarr" and item_type != "tv" :
163+ continue
164+
165+ # Get title and year
166+ title = item .get ('title' ) or item .get ('name' , '' )
167+ release_date = item .get ('release_date' ) or item .get ('first_air_date' , '' )
168+ year = None
169+ if release_date :
170+ try :
171+ year = int (release_date .split ('-' )[0 ])
172+ except (ValueError , IndexError ):
173+ pass
174+
175+ # Build poster URL
176+ poster_path = item .get ('poster_path' )
177+ poster_url = f"{ self .tmdb_image_base_url } { poster_path } " if poster_path else None
178+
179+ # Build backdrop URL
180+ backdrop_path = item .get ('backdrop_path' )
181+ backdrop_url = f"{ self .tmdb_image_base_url } { backdrop_path } " if backdrop_path else None
182+
183+ # Create basic result first (without availability check)
184+ basic_result = {
185+ 'tmdb_id' : item .get ('id' ),
186+ 'media_type' : item_type ,
187+ 'title' : title ,
188+ 'year' : year ,
189+ 'overview' : item .get ('overview' , '' ),
190+ 'poster_path' : poster_url ,
191+ 'backdrop_path' : backdrop_url ,
192+ 'vote_average' : item .get ('vote_average' , 0 ),
193+ 'popularity' : item .get ('popularity' , 0 ),
194+ 'availability' : {
195+ 'status' : 'checking' ,
196+ 'message' : 'Checking availability...' ,
197+ 'in_app' : False ,
198+ 'already_requested' : False
199+ }
200+ }
201+
202+ processed_items .append ((basic_result , item .get ('id' ), item_type ))
203+
204+ # Sort by popularity before streaming
205+ processed_items .sort (key = lambda x : x [0 ]['popularity' ], reverse = True )
206+ processed_items = processed_items [:20 ] # Limit to top 20 results
207+
208+ # Yield basic results first
209+ for basic_result , tmdb_id , item_type in processed_items :
210+ yield basic_result
211+
212+ # Now check availability for each item and yield updates
213+ for basic_result , tmdb_id , item_type in processed_items :
214+ try :
215+ availability_status = self ._get_availability_status (tmdb_id , item_type , target_instance , app_type )
216+
217+ # Yield updated result with availability
218+ updated_result = basic_result .copy ()
219+ updated_result ['availability' ] = availability_status
220+ updated_result ['_update' ] = True # Flag to indicate this is an update
221+
222+ yield updated_result
223+
224+ except Exception as e :
225+ logger .error (f"Error checking availability for { tmdb_id } : { e } " )
226+ # Yield error status
227+ error_result = basic_result .copy ()
228+ error_result ['availability' ] = {
229+ 'status' : 'error' ,
230+ 'message' : 'Error checking availability' ,
231+ 'in_app' : False ,
232+ 'already_requested' : False
233+ }
234+ error_result ['_update' ] = True
235+ yield error_result
236+
237+ except Exception as e :
238+ logger .error (f"Error in streaming search: { e } " )
239+ yield {'error' : str (e )}
114240
115241 def _get_availability_status (self , tmdb_id : int , media_type : str , instance : Dict [str , str ], app_type : str ) -> Dict [str , Any ]:
116242 """Get availability status for media item"""
0 commit comments