@@ -42,6 +42,8 @@ namespace SmartImage.Lib;
4242public  sealed  class  SearchClient  :  IDisposable 
4343{ 
4444
45+ 	public  SearchQuery  Query  {  get ;  set ;  } 
46+ 
4547	public  SearchConfig  Config  {  get ;  init ;  } 
4648
4749	public  bool  IsComplete  {  get ;  private  set ;  } 
@@ -54,16 +56,31 @@ public sealed class SearchClient : IDisposable
5456
5557	private  static   readonly  ILogger  s_logger  =  AppSupport . Factory . CreateLogger ( nameof ( SearchClient ) ) ; 
5658
57- 	public  SearchClient ( SearchConfig  cfg ) 
59+ 	private  static   readonly  Lock  m_lock  =  new  Lock ( ) ; 
60+ 
61+ 	public  SearchClient ( SearchConfig  cfg ,  SearchQuery  query ) 
5862	{ 
63+ 		Query          =  query ; 
5964		Config         =  cfg ; 
6065		ConfigApplied  =  false ; 
6166		IsRunning      =  false ; 
6267
68+ 		Config . PropertyChanged  +=  ( sender ,  args )  => 
69+ 		{ 
70+ 			if  ( args . PropertyName  ==  nameof ( SearchConfig . SearchEngines ) )  { 
71+ 				lock  ( m_lock )  { 
72+ 					Engines  =  BaseSearchEngine . GetSelectedEngines ( Config . SearchEngines ) . ToArray ( ) ; 
73+ 
74+ 				} 
75+ 			} 
76+ 		} ; 
77+ 
6378		// GetSelectedEngines(); 
6479
6580	} 
6681
82+ 	public  SearchClient ( SearchConfig  cfg )  :  this ( cfg ,  SearchQuery . Null )  {  } 
83+ 
6784	static   SearchClient ( )  {  } 
6885
6986	[ ModuleInitializer ] 
@@ -106,141 +123,43 @@ public void OpenChannel()
106123			// ... 
107124		} 
108125
109- 		ResultChannel  =  Channel . CreateUnbounded < SearchResult > ( new  UnboundedChannelOptions ( ) 
126+ 		ResultChannel  =  Channel . CreateBounded < SearchResult > ( new  BoundedChannelOptions ( Engines . Length ) 
110127		{ 
111128			SingleWriter  =  true , 
112- 
113129		} ) ; 
114130	} 
115131
116- #if OLD 
117- 	/// <summary> 
118- 	/// Runs a search of <paramref name="query"/>. 
119- 	/// </summary> 
120- 	/// <param name="query">Search query</param> 
121- 	/// <param name="scheduler"></param> 
122- 	/// <param name="token">Cancellation token passed to <see cref="WebSearchEngine{T}.GetResultAsync(SmartImage.Lib.SearchQuery,System.Threading.CancellationToken)"/></param> 
123- 	public  async  Task < SearchResult [ ] >  RunSearchAsync1 ( SearchQuery  query , 
124- 	                                                  TaskScheduler  scheduler  =  default , 
125- 	                                                  CancellationToken  token  =  default ) 
126- 	{ 
127- 		scheduler  ??=  TaskScheduler . Default ; 
128- 
129- 		// Requires.NotNull(ResultChannel); 
130- 		if  ( ResultChannel  ==  null  ||  ( IsComplete  &&  ! IsRunning ) )  { 
131- 			OpenChannel ( ) ; 
132- 		} 
133- 
134- 		if  ( ! query . IsUploaded )  { 
135- 			throw  new  ArgumentException ( $ "Query was not uploaded",  nameof ( query ) ) ; 
136- 		} 
137- 
138- 		IsRunning  =  true ; 
139- 
140- 		if  ( ! ConfigApplied )  { 
141- 			await  LoadEnginesAsync ( token ) ;  // todo 
142- 
143- 		} 
144- 		else  { 
145- 			Debug . WriteLine ( "Not reloading engines" ) ; 
146- 		} 
147- 
148- 		Debug . WriteLine ( $ "Config: { Config }  | { Engines . QuickJoin ( ) } ") ; 
149- 
150- 		var  tasks  =  GetSearchTasks ( query ,  scheduler ,  token ) ; 
151- 
152- 		// var results = new SearchResult[tasks.Count]; 
153- 		int  i  =  0 ; 
154- 
155- 		/*while (tasks.Count > 0) { 
156- 			if (token.IsCancellationRequested) { 
157- 
158- 				Debugger.Break(); 
159- 				s_logger.LogWarning("Cancellation requested"); 
160- 				CompleteSearchAsync(); 
161- 				return results; 
162- 			} 
163- 
164- 			Task<SearchResult> task = await Task.WhenAny(tasks); 
165- 			tasks.Remove(task); 
166- 
167- 			if (task.IsFaulted) { 
168- 				Trace.WriteLine($"{task} faulted!",LogCategories.C_ERROR); 
169- 			} 
170- 			SearchResult result = await task; 
171- 
172- 			results[i] = result; 
173- 			i++; 
174- 		}*/ 
175- 
176- 
177- 		await  foreach  ( var  task  in  Task . WhenEach ( tasks ) . WithCancellation ( token ) )  { 
178- 			if  ( token . IsCancellationRequested )  { 
179- 
180- 				Debugger . Break ( ) ; 
181- 				s_logger . LogWarning ( "Cancellation requested" ) ; 
182- 				CompleteSearchAsync ( ) ; 
183- 			} 
184- 
185- 			var  result  =  await  task ; 
186- 			ProcessResult ( result ) ; 
187- 		} 
188- 
189- 		CompleteSearchAsync ( ) ; 
190- 		OnSearchComplete ? . Invoke ( this ,  results ) ; 
191- 
192- 		if  ( Config . PriorityEngines  ==  SearchEngineOptions . Auto )  { 
193- 
194- 			try  { 
195- 
196- 				SearchResultItem  item  =  GetBest ( results ) ; 
197- 
198- 				if  ( item  !=  null )  { 
199- 					OpenResult ( item . Url ) ; 
200- 				} 
201- 			} 
202- 			catch  ( Exception  e )  { 
203- 				Debug . WriteLine ( $ "{ e . Message } ") ; 
204- 
205- 				Debugger . Break ( ) ; 
206- 			} 
207- 
208- 		} 
209- 
210- 		IsRunning  =  false ; 
211- 
212- 		return  results ; 
213- 	} 
214- #endif
215- 
216- 	public  async  IAsyncEnumerable < SearchResult >  RunSearchAsync ( SearchQuery  query , 
217- 	                                                           TaskScheduler  scheduler  =  default , 
132+ 	public  async  IAsyncEnumerable < SearchResult >  RunSearchAsync ( TaskScheduler  scheduler  =  default , 
218133	                                                           [ EC ]  CancellationToken  token  =  default ) 
219134	{ 
220- 		await  RunSearchAsync2 ( query ,   ResultChannel . Writer ,   token ) ; 
135+ 		await  RunSearchAsync2 ( token ) ; 
221136
222- 		await  foreach  ( var  result  in  ResultChannel . Reader . ReadAllAsync ( token ) )  { 
223- 			yield  return  result ; 
137+ 		while  ( await  ResultChannel . Reader . WaitToReadAsync ( token ) )  { 
138+ 			while  ( ResultChannel . Reader . TryRead ( out  var  result ) )  { 
139+ 				yield  return  result ; 
140+ 			} 
224141		} 
225142	} 
226143
144+ 	public  ValueTask < bool >  RunSearchAsync2 ( CancellationToken  token  =  default ) 
145+ 		=>  RunSearchAsync2 ( ResultChannel . Writer ,  token ) ; 
227146
228- 	/// <summary> 
229- 	/// Runs a search of <paramref name="query"/>. 
230- 	/// </summary> 
231- 	public  async  ValueTask < bool >  RunSearchAsync2 ( SearchQuery  query , 
232- 	                                             ChannelWriter < SearchResult >  cw , 
147+ 	public  async  ValueTask < bool >  RunSearchAsync2 ( ChannelWriter < SearchResult >  cw , 
233148	                                             CancellationToken  token  =  default ) 
234149	{ 
235- 		if  ( ! query . IsUploaded )  { 
236- 			throw  new  SmartImageException ( $ "{ query }  was not uploaded") ; 
150+ 		if  ( ! Query . IsUploaded )  { 
151+ 			throw  new  SmartImageException ( $ "{ Query }  was not uploaded") ; 
237152		} 
238153
239- 		var  tasks  =  Engines . Select ( e => 
240- 		{ 
241- 			var  task  =  e . GetResultAsync ( query ,  token ) ; 
242- 			return  task ; 
243- 		} ) ; 
154+ 		IEnumerable < Task < SearchResult > >  tasks ; 
155+ 
156+ 		lock  ( m_lock )  { 
157+ 			tasks  =  Engines . Select ( e => 
158+ 			{ 
159+ 				var  task  =  e . GetResultAsync ( Query ,  token ) ; 
160+ 				return  task ; 
161+ 			} ) ; 
162+ 		} 
244163
245164		await  foreach  ( var  task  in  Task . WhenEach ( tasks ) . WithCancellation ( token ) )  { 
246165
@@ -369,40 +288,16 @@ public async ValueTask LoadEnginesAsync(CancellationToken token = default)
369288
370289		Trace . WriteLine ( "Loading engines" ) ; 
371290
372- 		Engines  =  BaseSearchEngine . GetSelectedEngines ( Config . SearchEngines ) . ToArray ( ) ; 
373291
374292		if  ( Config . ReadCookies )  { 
375293
376- 			try  { 
377- 				await  ( ( DefaultCookiesProvider )  DefaultCookiesProvider . Instance ) . OpenAsync ( ) ; 
378- 			} 
379- 			catch  ( Exception  e )  { 
380- 				Trace . WriteLine ( $ "{ e } ") ; 
381- 				Config . ReadCookies  =  false ; 
382- 				DefaultCookiesProvider . Instance . Dispose ( ) ; 
383- 			} 
294+ 			await  InitCookiesProvider ( ) ; 
384295		} 
385296
386297		if  ( Config . FlareSolverr  &&  ! FlareSolverrClient . Value . IsInitialized )  { 
387298
388299
389- 			var  ok  =  FlareSolverrClient . Value . Configure ( Config . FlareSolverrApiUrl ) ; 
390- 
391- 			if  ( ! ok )  { 
392- 				Debugger . Break ( ) ; 
393- 			} 
394- 			else  { 
395- 				// Ensure FlareSolverr 
396- 
397- 				try  { 
398- 					var  idx  =  await  FlareSolverrClient . Value . Clearance . Solverr . GetIndexAsync ( ) ; 
399- 				} 
400- 				catch  ( Exception  e )  { 
401- 					Trace . WriteLine ( $ "{ nameof ( FlareSolverrClient ) } : { e . Message } ") ; 
402- 					Config . FlareSolverr  =  false ; 
403- 					FlareSolverrClient . Value . Dispose ( ) ; 
404- 				} 
405- 			} 
300+ 			await  InitFlareSolverr ( ) ; 
406301		} 
407302
408303		foreach  ( BaseSearchEngine  bse  in  Engines )  { 
@@ -424,6 +319,39 @@ public async ValueTask LoadEnginesAsync(CancellationToken token = default)
424319		ConfigApplied  =  true ; 
425320	} 
426321
322+ 	private  async  Task  InitFlareSolverr ( ) 
323+ 	{ 
324+ 		var  ok  =  FlareSolverrClient . Value . Configure ( Config . FlareSolverrApiUrl ) ; 
325+ 
326+ 		if  ( ! ok )  { 
327+ 			Debugger . Break ( ) ; 
328+ 		} 
329+ 		else  { 
330+ 			// Ensure FlareSolverr 
331+ 
332+ 			try  { 
333+ 				var  idx  =  await  FlareSolverrClient . Value . Clearance . Solverr . GetIndexAsync ( ) ; 
334+ 			} 
335+ 			catch  ( Exception  e )  { 
336+ 				Trace . WriteLine ( $ "{ nameof ( FlareSolverrClient ) } : { e . Message } ") ; 
337+ 				Config . FlareSolverr  =  false ; 
338+ 				FlareSolverrClient . Value . Dispose ( ) ; 
339+ 			} 
340+ 		} 
341+ 	} 
342+ 
343+ 	private  async  Task  InitCookiesProvider ( ) 
344+ 	{ 
345+ 		try  { 
346+ 			await  ( ( DefaultCookiesProvider )  DefaultCookiesProvider . Instance ) . OpenAsync ( ) ; 
347+ 		} 
348+ 		catch  ( Exception  e )  { 
349+ 			Trace . WriteLine ( $ "{ e } ") ; 
350+ 			Config . ReadCookies  =  false ; 
351+ 			DefaultCookiesProvider . Instance . Dispose ( ) ; 
352+ 		} 
353+ 	} 
354+ 
427355	public  void  Dispose ( ) 
428356	{ 
429357		Debug . WriteLine ( $ "Disposing { nameof ( SearchClient ) } ") ; 
0 commit comments