@@ -25,6 +25,7 @@ use pizza_engine::store::{DiskStore, DiskStoreSnapshot};
25
25
use pizza_engine:: writer:: Writer ;
26
26
use pizza_engine:: { doc, Engine , EngineBuilder } ;
27
27
use serde_json:: Value as Json ;
28
+ use std:: path:: Path ;
28
29
use std:: path:: PathBuf ;
29
30
use tauri:: { async_runtime, AppHandle , Manager , Runtime } ;
30
31
use tauri_plugin_fs_pro:: { icon, metadata, name, IconOptions } ;
@@ -47,6 +48,8 @@ const TAURI_STORE_APP_ALIAS: &str = "app_alias";
47
48
const TAURI_STORE_KEY_SEARCH_PATH : & str = "search_path" ;
48
49
const TAURI_STORE_KEY_DISABLED_APP_LIST : & str = "disabled_app_list" ;
49
50
51
+ const INDEX_DIR : & str = "local_application_index" ;
52
+
50
53
/// We use this as:
51
54
///
52
55
/// 1. querysource ID
@@ -209,6 +212,86 @@ impl SearchSourceState for ApplicationSearchSourceState {
209
212
}
210
213
}
211
214
215
+ /// Index applications if they have not been indexed (by checking if `app_index_dir` exists).
216
+ async fn index_applications_if_not_indexed < R : Runtime > (
217
+ tauri_app_handle : & AppHandle < R > ,
218
+ app_index_dir : & Path ,
219
+ ) -> anyhow:: Result < ApplicationSearchSourceState > {
220
+ let index_exists = app_index_dir. exists ( ) ;
221
+
222
+ let mut pizza_engine_builder = EngineBuilder :: new ( ) ;
223
+ let disk_store = DiskStore :: new ( & app_index_dir) ?;
224
+ pizza_engine_builder. set_data_store ( disk_store) ;
225
+
226
+ let mut schema = Schema :: new ( ) ;
227
+ let field_app_name = Property :: builder ( FieldType :: Text ) . build ( ) ;
228
+ schema
229
+ . add_property ( FIELD_APP_NAME , field_app_name)
230
+ . expect ( "no collision could happen" ) ;
231
+ let property_icon = Property :: builder ( FieldType :: Text ) . index ( false ) . build ( ) ;
232
+ schema
233
+ . add_property ( FIELD_ICON_PATH , property_icon)
234
+ . expect ( "no collision could happen" ) ;
235
+ schema
236
+ . add_property ( FIELD_APP_ALIAS , Property :: as_text ( None ) )
237
+ . expect ( "no collision could happen" ) ;
238
+ schema. freeze ( ) ;
239
+ pizza_engine_builder. set_schema ( schema) ;
240
+
241
+ let pizza_engine = pizza_engine_builder
242
+ . build ( )
243
+ . unwrap_or_else ( |e| panic ! ( "failed to build Pizza engine due to [{}]" , e) ) ;
244
+ pizza_engine. start ( ) ;
245
+ let mut writer = pizza_engine. acquire_writer ( ) ;
246
+
247
+ if !index_exists {
248
+ let default_search_path = get_default_search_paths ( ) ;
249
+ let apps = list_app_in ( default_search_path) . map_err ( |str| anyhow:: anyhow!( str ) ) ?;
250
+
251
+ for app in apps. iter ( ) {
252
+ let app_path = get_app_path ( app) ;
253
+ let app_name = get_app_name ( app) . await ;
254
+ let app_icon_path = get_app_icon_path ( & tauri_app_handle, app)
255
+ . await
256
+ . map_err ( |str| anyhow:: anyhow!( str ) ) ?;
257
+ let app_alias = get_app_alias ( & tauri_app_handle, & app_path) . unwrap_or ( String :: new ( ) ) ;
258
+
259
+ if app_name. is_empty ( ) || app_name. eq ( & tauri_app_handle. package_info ( ) . name ) {
260
+ continue ;
261
+ }
262
+
263
+ // You cannot write `app_name.clone()` within the `doc!()` macro, we should fix this.
264
+ let app_name_clone = app_name. clone ( ) ;
265
+ let app_path_clone = app_path. clone ( ) ;
266
+ let document = doc ! ( app_path_clone, {
267
+ FIELD_APP_NAME => app_name_clone,
268
+ FIELD_ICON_PATH => app_icon_path,
269
+ FIELD_APP_ALIAS => app_alias,
270
+ }
271
+ ) ;
272
+
273
+ // We don't error out because one failure won't break the whole thing
274
+ if let Err ( e) = writer. create_document ( document) . await {
275
+ warn ! (
276
+ "failed to index application [app name: '{}', app path: '{}'] due to error [{}]" , app_name, app_path, e
277
+ )
278
+ }
279
+ }
280
+
281
+ writer. commit ( ) ?;
282
+ }
283
+
284
+ let snapshot = pizza_engine. create_snapshot ( ) ;
285
+ let searcher = pizza_engine. acquire_searcher ( ) ;
286
+
287
+ Ok ( ApplicationSearchSourceState {
288
+ searcher,
289
+ snapshot,
290
+ engine : pizza_engine,
291
+ writer,
292
+ } )
293
+ }
294
+
212
295
/// Upon application start, index all the applications found in the `get_default_search_paths()`.
213
296
struct IndexAllApplicationsTask < R : Runtime > {
214
297
tauri_app_handle : AppHandle < R > ,
@@ -228,87 +311,47 @@ impl<R: Runtime> Task for IndexAllApplicationsTask<R> {
228
311
. path ( )
229
312
. app_data_dir ( )
230
313
. expect ( "failed to find the local dir" ) ;
231
- app_index_dir. push ( "local_application_index" ) ;
232
-
233
- let index_exists = app_index_dir. exists ( ) ;
234
-
235
- let mut pizza_engine_builder = EngineBuilder :: new ( ) ;
236
- let disk_store = task_exec_try ! ( DiskStore :: new( & app_index_dir) , callback) ;
237
- pizza_engine_builder. set_data_store ( disk_store) ;
238
-
239
- let mut schema = Schema :: new ( ) ;
240
- let field_app_name = Property :: builder ( FieldType :: Text ) . build ( ) ;
241
- schema
242
- . add_property ( FIELD_APP_NAME , field_app_name)
243
- . expect ( "no collision could happen" ) ;
244
- let property_icon = Property :: builder ( FieldType :: Text ) . index ( false ) . build ( ) ;
245
- schema
246
- . add_property ( FIELD_ICON_PATH , property_icon)
247
- . expect ( "no collision could happen" ) ;
248
- schema
249
- . add_property ( FIELD_APP_ALIAS , Property :: as_text ( None ) )
250
- . expect ( "no collision could happen" ) ;
251
- schema. freeze ( ) ;
252
- pizza_engine_builder. set_schema ( schema) ;
253
-
254
- let pizza_engine = pizza_engine_builder
255
- . build ( )
256
- . unwrap_or_else ( |e| panic ! ( "failed to build Pizza engine due to [{}]" , e) ) ;
257
- pizza_engine. start ( ) ;
258
- let mut writer = pizza_engine. acquire_writer ( ) ;
259
-
260
- if !index_exists {
261
- let default_search_path = get_default_search_paths ( ) ;
262
- let apps = task_exec_try ! ( list_app_in( default_search_path) , callback) ;
263
-
264
- for app in apps. iter ( ) {
265
- let app_path = get_app_path ( app) ;
266
- let app_name = get_app_name ( app) . await ;
267
- let app_icon_path = task_exec_try ! (
268
- get_app_icon_path( & self . tauri_app_handle, app) . await ,
269
- callback
270
- ) ;
271
- let app_alias =
272
- get_app_alias ( & self . tauri_app_handle , & app_path) . unwrap_or ( String :: new ( ) ) ;
273
-
274
- if app_name. is_empty ( ) || app_name. eq ( & self . tauri_app_handle . package_info ( ) . name ) {
275
- continue ;
276
- }
277
-
278
- // You cannot write `app_name.clone()` within the `doc!()` macro, we should fix this.
279
- let app_name_clone = app_name. clone ( ) ;
280
- let app_path_clone = app_path. clone ( ) ;
281
- let document = doc ! ( app_path_clone, {
282
- FIELD_APP_NAME => app_name_clone,
283
- FIELD_ICON_PATH => app_icon_path,
284
- FIELD_APP_ALIAS => app_alias,
285
- }
286
- ) ;
287
-
288
- // We don't error out because one failure won't break the whole thing
289
- if let Err ( e) = writer. create_document ( document) . await {
290
- warn ! (
291
- "failed to index application [app name: '{}', app path: '{}'] due to error [{}]" , app_name, app_path, e
292
- )
293
- }
294
- }
314
+ app_index_dir. push ( INDEX_DIR ) ;
315
+ let app_search_source_state = task_exec_try ! (
316
+ index_applications_if_not_indexed( & self . tauri_app_handle, & app_index_dir) . await ,
317
+ callback
318
+ ) ;
319
+ * state = Some ( Box :: new ( app_search_source_state) ) ;
320
+ callback. send ( Ok ( ( ) ) ) . expect ( "rx dropped" ) ;
321
+ }
322
+ }
295
323
296
- task_exec_try ! ( writer. commit( ) , callback) ;
297
- }
324
+ struct ReindexAllApplicationsTask < R : Runtime > {
325
+ tauri_app_handle : AppHandle < R > ,
326
+ callback : Option < tokio:: sync:: oneshot:: Sender < Result < ( ) , String > > > ,
327
+ }
298
328
299
- let snapshot = pizza_engine. create_snapshot ( ) ;
300
- let searcher = pizza_engine. acquire_searcher ( ) ;
329
+ #[ async_trait:: async_trait( ?Send ) ]
330
+ impl < R : Runtime > Task for ReindexAllApplicationsTask < R > {
331
+ fn search_source_id ( & self ) -> & ' static str {
332
+ APPLICATION_SEARCH_SOURCE_ID
333
+ }
301
334
302
- let state_to_store = Box :: new ( ApplicationSearchSourceState {
303
- searcher,
304
- snapshot,
305
- engine : pizza_engine,
306
- writer,
307
- } ) as Box < dyn SearchSourceState > ;
335
+ async fn exec ( & mut self , state : & mut Option < Box < dyn SearchSourceState > > ) {
336
+ let callback = self . callback . take ( ) . unwrap ( ) ;
308
337
309
- * state = Some ( state_to_store) ;
338
+ // Clear the state
339
+ * state = None ;
340
+ let mut app_index_dir = self
341
+ . tauri_app_handle
342
+ . path ( )
343
+ . app_data_dir ( )
344
+ . expect ( "failed to find the local dir" ) ;
345
+ app_index_dir. push ( INDEX_DIR ) ;
346
+ task_exec_try ! ( tokio:: fs:: remove_dir_all( & app_index_dir) . await , callback) ;
310
347
311
- callback. send ( Ok ( ( ) ) ) . unwrap ( ) ;
348
+ // Then re-index the apps
349
+ let app_search_source_state = task_exec_try ! (
350
+ index_applications_if_not_indexed( & self . tauri_app_handle, & app_index_dir) . await ,
351
+ callback
352
+ ) ;
353
+ * state = Some ( Box :: new ( app_search_source_state) ) ;
354
+ callback. send ( Ok ( ( ) ) ) . expect ( "rx dropped" ) ;
312
355
}
313
356
}
314
357
@@ -326,6 +369,23 @@ impl<R: Runtime> Task for SearchApplicationsTask<R> {
326
369
327
370
async fn exec ( & mut self , state : & mut Option < Box < dyn SearchSourceState > > ) {
328
371
let callback = self . callback . take ( ) . unwrap ( ) ;
372
+
373
+ let Some ( state) = state. as_mut ( ) else {
374
+ let empty_hits = SearchResult {
375
+ tracing_id : String :: new ( ) ,
376
+ explains : None ,
377
+ total_hits : 0 ,
378
+ hits : None ,
379
+ } ;
380
+
381
+ let rx_dropped_error = callback. send ( Ok ( empty_hits) ) . is_err ( ) ;
382
+ if rx_dropped_error {
383
+ warn ! ( "failed to send local app search result back because the corresponding channel receiver end has been unexpected dropped, which could happen due to a low query timeout" )
384
+ }
385
+
386
+ return ;
387
+ } ;
388
+
329
389
let disabled_app_list = get_disabled_app_list ( & self . tauri_app_handle ) ;
330
390
331
391
// TODO: search via alias, implement this when Pizza engine supports update
@@ -344,8 +404,6 @@ impl<R: Runtime> Task for SearchApplicationsTask<R> {
344
404
"{{ \" query\" : {{ \" bool\" : {{ \" should\" : [ {{ \" match\" : {{ \" {FIELD_APP_NAME}\" : {:?} }} }}, {{ \" prefix\" : {{ \" {FIELD_APP_NAME}\" : {:?} }} }} ] }} }} }}" , self . query_string, self . query_string) ;
345
405
346
406
let state = state
347
- . as_mut ( )
348
- . expect ( "should be set before" )
349
407
. as_mut_any ( )
350
408
. downcast_mut :: < ApplicationSearchSourceState > ( )
351
409
. unwrap ( ) ;
@@ -1105,3 +1163,30 @@ pub async fn get_app_metadata(app_name: String, app_path: String) -> Result<AppM
1105
1163
last_opened,
1106
1164
} )
1107
1165
}
1166
+
1167
+ #[ tauri:: command]
1168
+ pub async fn reindex_applications < R : Runtime > (
1169
+ tauri_app_handle : AppHandle < R > ,
1170
+ ) -> Result < ( ) , String > {
1171
+ let ( tx, rx) = tokio:: sync:: oneshot:: channel ( ) ;
1172
+ let reindex_applications_task = ReindexAllApplicationsTask {
1173
+ tauri_app_handle : tauri_app_handle. clone ( ) ,
1174
+ callback : Some ( tx) ,
1175
+ } ;
1176
+
1177
+ RUNTIME_TX
1178
+ . get ( )
1179
+ . unwrap ( )
1180
+ . send ( Box :: new ( reindex_applications_task) )
1181
+ . unwrap ( ) ;
1182
+
1183
+ let reindexing_applications_result = rx. await . unwrap ( ) ;
1184
+ if let Err ( ref e) = reindexing_applications_result {
1185
+ error ! (
1186
+ "re-indexing local applications failed, app search won't work, error [{}]" ,
1187
+ e
1188
+ )
1189
+ }
1190
+
1191
+ reindexing_applications_result
1192
+ }
0 commit comments