@@ -10,12 +10,12 @@ public sealed class SearchService : ISearchService
1010{
1111 #region Fields
1212
13- private Cmdline ? _lastQuery ;
13+ private Cmdline ? _previousQuery ;
1414
1515 private readonly ILogger < SearchService > _logger ;
1616 private readonly IMacroAliasExpanderService _macroAliasExpanderService ;
1717 private readonly ISearchServiceOrchestrator _orchestrator ;
18- private readonly IEnumerable < IStoreService > _storeServices ;
18+ private readonly IReadOnlyCollection < IStoreService > _storeServices ;
1919
2020 #endregion
2121
@@ -31,7 +31,7 @@ ISearchServiceOrchestrator orchestrator
3131 ArgumentNullException . ThrowIfNull ( storeServices ) ;
3232
3333 _storeServices = storeServices . ToList ( ) ;
34- if ( ! _storeServices . Any ( ) )
34+ if ( _storeServices . Count == 0 )
3535 {
3636 throw new ArgumentException ( "There are no store activated for the search service" ) ;
3737 }
@@ -45,46 +45,55 @@ ISearchServiceOrchestrator orchestrator
4545
4646 #region Methods
4747
48- private async Task DispatchSearchAsync ( IList < QueryResult > destination , Cmdline query , bool doesReturnAllIfEmpty )
48+ private bool CanPrune ( Cmdline ? previousQuery , Cmdline currentQuery )
49+ {
50+ if ( previousQuery is null ) { return false ; }
51+
52+ if ( previousQuery . IsEmpty ( ) ) { return false ; }
53+
54+ return GetAliveStores ( currentQuery )
55+ . All ( store => store . CanPruneResult ( previousQuery , currentQuery ) ) ;
56+ }
57+
58+ private async Task DispatchSearchAsync ( IList < QueryResult > destination , Cmdline currentQuery , bool doesReturnAllIfEmpty )
4959 {
5060 using var measurement = _logger . WarnIfSlow ( this ) ;
51- if ( doesReturnAllIfEmpty && query . IsEmpty ( ) )
61+ if ( doesReturnAllIfEmpty && currentQuery . IsEmpty ( ) )
5262 {
5363 destination . ReplaceWith (
5464 await GetAllAsync ( )
5565 ) ;
5666 return ;
5767 }
5868
59- if ( query . IsEmpty ( ) )
69+ if ( currentQuery . IsEmpty ( ) )
6070 {
6171 destination . Clear ( ) ;
6272 return ;
6373 }
64-
65- if ( _lastQuery is not null
66- && ! _lastQuery . IsEmpty ( )
67- && query . ToString ( ) . StartsWith ( _lastQuery . ToString ( ) , StringComparison . CurrentCultureIgnoreCase ) )
74+
75+ if ( CanPrune ( _previousQuery , currentQuery ) )
6876 {
6977 _logger . LogTrace (
7078 "Query {NewQuery} refines {OldQuery}; pruning existing results instead of searching stores" ,
71- query . Parameters ,
72- _lastQuery . Parameters
79+ currentQuery . ToString ( ) ,
80+ _previousQuery ? . ToString ( ) ?? "<EMPTY>"
7381 ) ;
74- PruneResults ( destination , query ) ;
82+
83+ //CanPrune guarantees the _previousQuery is not null
84+ PruneResults ( destination , _previousQuery ! , currentQuery ) ;
7585 return ;
7686 }
7787
7888 _logger . LogTrace (
7989 "Execute a full search for query {Query}" ,
80- query
90+ currentQuery
8191 ) ;
82- await SearchInStoreAsync ( destination , query ) ;
92+ await SearchInStoreAsync ( destination , currentQuery ) ;
8393 }
8494
8595 private IEnumerable < QueryResult > FormatForDisplay ( QueryResult [ ] collection )
8696 {
87- collection ??= [ ] ;
8897 // Upgrade alias to executable macros (if any)
8998 var macros = collection . Length != 0
9099 ? _macroAliasExpanderService . Expand ( collection ) . ToList ( )
@@ -101,36 +110,43 @@ private IEnumerable<QueryResult> FormatForDisplay(QueryResult[] collection)
101110 : DisplayQueryResult . NoResultFound ;
102111 }
103112
104- private void PruneResults ( IList < QueryResult > destination , Cmdline query )
113+ private IStoreService [ ] GetAliveStores ( Cmdline query )
114+ => _storeServices . Where ( service => _orchestrator . IsAlive ( service , query ) )
115+ . ToArray ( ) ;
116+
117+ private void PruneResults ( IList < QueryResult > destination , Cmdline previousQuery , Cmdline currentQuery )
105118 {
106- var toDelete = destination . Where ( item =>
107- ! item . Name . StartsWith ( query . Parameters , StringComparison . InvariantCultureIgnoreCase )
108- ) . ToArray ( ) ;
119+ var stores = GetAliveStores ( currentQuery ) ;
120+
121+ var idle = stores . FirstOrDefault ( s => s . StoreOrchestration . IdleOthers ) ;
122+ if ( idle is not null )
123+ {
124+ stores = [ idle ] ;
125+ }
126+
127+ var deletedCount = stores . Sum ( store => store . PruneResult ( destination , previousQuery , currentQuery ) ) ;
109128
110129 _logger . LogTrace (
111- "Prune {ItemCount} result(s) for {Query}" ,
112- toDelete . Length ,
113- query
130+ "Pruned {ItemCount} result(s) for {Query}" ,
131+ deletedCount ,
132+ currentQuery
114133 ) ;
115-
116- destination . RemoveMultiple ( toDelete ) ;
117134 }
118135
119136 private async Task SearchInStoreAsync ( IList < QueryResult > destination , Cmdline query )
120137 {
121138 //Get the alive stores
122- var aliveStores = _storeServices . Where ( service => _orchestrator . IsAlive ( service , query ) )
123- . ToArray ( ) ;
139+ var aliveStores = GetAliveStores ( query ) ;
124140
125- // I've got a service that stunts all the others, then
141+ // I've got a service that idles all the others, then
126142 // I execute the search for this one only
127143 var tasks = new List < Task < IEnumerable < QueryResult > > > ( ) ;
128144 if ( aliveStores . Any ( x => x . StoreOrchestration . IdleOthers ) )
129145 {
130146 var store = aliveStores . First ( x => x . StoreOrchestration . IdleOthers ) ;
131147 tasks . Add ( Task . Run ( ( ) => store . Search ( query ) ) ) ;
132148 }
133- else // No store that stunt all the other stores, execute aggregated search
149+ else // No store that idles all the other stores, execute aggregated search
134150 {
135151 tasks = aliveStores . Select ( store => Task . Run ( ( ) => store . Search ( query ) ) )
136152 . ToList ( ) ;
@@ -139,7 +155,7 @@ private async Task SearchInStoreAsync(IList<QueryResult> destination, Cmdline qu
139155 _logger . LogTrace (
140156 "For the query {Query}, {IdleCount} store(s) IDLE and {ActiveCount} store(s) ALIVE" ,
141157 query ,
142- _storeServices . Count ( ) - tasks . Count ,
158+ _storeServices . Count - tasks . Count ,
143159 tasks . Count
144160 ) ;
145161
@@ -155,16 +171,17 @@ private async Task SearchInStoreAsync(IList<QueryResult> destination, Cmdline qu
155171
156172 // If there's an exact match, promote
157173 // it to the top of the list.
158- var match = orderedResults . FirstOrDefault ( r => r . Name == query . Name ) ;
174+ var match = orderedResults . FirstOrDefault ( r =>
175+ r . Name . Equals ( query . Name , StringComparison . InvariantCultureIgnoreCase )
176+ ) ;
177+
159178 if ( match is not null ) { orderedResults . Move ( match , 0 ) ; }
160179
161- if ( orderedResults . Count == 0 )
162- {
163- destination . ReplaceWith (
164- DisplayQueryResult . SingleFromResult ( "No result found" , iconKind : "AlertCircleOutline" )
165- ) ;
166- }
167- else { destination . ReplaceWith ( orderedResults ) ; }
180+ destination . ReplaceWith (
181+ orderedResults . Count == 0
182+ ? DisplayQueryResult . SingleFromResult ( "No result found" , iconKind : "AlertCircleOutline" )
183+ : orderedResults
184+ ) ;
168185 }
169186
170187 /// <inheritdoc />
@@ -184,7 +201,7 @@ public async Task<IEnumerable<QueryResult>> GetAllAsync()
184201 public async Task SearchAsync ( IList < QueryResult > destination , Cmdline query , bool doesReturnAllIfEmpty = false )
185202 {
186203 await DispatchSearchAsync ( destination , query , doesReturnAllIfEmpty ) ;
187- _lastQuery = query ;
204+ _previousQuery = query ;
188205 }
189206
190207 #endregion
0 commit comments