1 /* gbp-code-index-service.c
2  *
3  * Copyright 2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "gbp-code-index-service"
22 
23 #include "config.h"
24 
25 #include <glib/gi18n.h>
26 #include <gtksourceview/gtksource.h>
27 #include <libide-code.h>
28 #include <libide-foundry.h>
29 #include <libide-projects.h>
30 #include <libide-vcs.h>
31 #include <libpeas/peas.h>
32 
33 #include "gbp-code-index-executor.h"
34 #include "gbp-code-index-plan.h"
35 #include "gbp-code-index-service.h"
36 #include "indexer-info.h"
37 
38 #define DELAY_FOR_INDEXING_MSEC 500
39 
40 struct _GbpCodeIndexService
41 {
42   IdeObject          parent_instance;
43 
44   IdeNotification   *notif;
45   IdeCodeIndexIndex *index;
46   GCancellable      *cancellable;
47 
48   guint              queued_source;
49 
50   guint              build_inhibit : 1;
51   guint              needs_indexing : 1;
52   guint              indexing : 1;
53   guint              started : 1;
54   guint              paused : 1;
55 };
56 
57 typedef struct
58 {
59   IdeCodeIndexIndex *index;
60   GFile *workdir;
61   GFile *indexdir;
62 } LoadIndexes;
63 
64 enum {
65   PROP_0,
66   PROP_PAUSED,
67   N_PROPS
68 };
69 
70 G_DEFINE_FINAL_TYPE (GbpCodeIndexService, gbp_code_index_service, IDE_TYPE_OBJECT)
71 
72 static void     gbp_code_index_service_index_async    (GbpCodeIndexService  *self,
73                                                        GCancellable         *cancellable,
74                                                        GAsyncReadyCallback   callback,
75                                                        gpointer              user_data);
76 static gboolean gbp_code_index_service_index_finish   (GbpCodeIndexService   *self,
77                                                        GAsyncResult          *result,
78                                                        GError              **error);
79 static void     gbp_code_index_service_reload_indexes (GbpCodeIndexService  *self);
80 
81 static GParamSpec *properties [N_PROPS];
82 
83 static void
load_indexes_free(LoadIndexes * state)84 load_indexes_free (LoadIndexes *state)
85 {
86   g_clear_object (&state->index);
87   g_clear_object (&state->indexdir);
88   g_clear_object (&state->workdir);
89   g_slice_free (LoadIndexes, state);
90 }
91 
92 static void
update_notification(GbpCodeIndexService * self)93 update_notification (GbpCodeIndexService *self)
94 {
95   gboolean visible;
96 
97   g_assert (IDE_IS_MAIN_THREAD ());
98   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
99 
100   visible = self->indexing || self->paused;
101 
102   if (ide_object_is_root (IDE_OBJECT (self->notif)) && visible)
103     ide_notification_attach (self->notif, IDE_OBJECT (self));
104   else if (!ide_object_is_root (IDE_OBJECT (self->notif)) && !visible)
105     ide_notification_withdraw (self->notif);
106 }
107 
108 static gchar *
gbp_code_index_service_repr(IdeObject * object)109 gbp_code_index_service_repr (IdeObject *object)
110 {
111   GbpCodeIndexService *self = (GbpCodeIndexService *)object;
112 
113   return g_strdup_printf ("%s started=%d paused=%d",
114                           G_OBJECT_TYPE_NAME (self), self->started, self->paused);
115 }
116 
117 static void
gbp_code_index_service_destroy(IdeObject * object)118 gbp_code_index_service_destroy (IdeObject *object)
119 {
120   GbpCodeIndexService *self = (GbpCodeIndexService *)object;
121 
122   if (self->started)
123     gbp_code_index_service_stop (self);
124 
125   g_cancellable_cancel (self->cancellable);
126   g_clear_object (&self->cancellable);
127   g_clear_handle_id (&self->queued_source, g_source_remove);
128 
129   ide_clear_and_destroy_object (&self->index);
130 
131   if (self->notif)
132     {
133       ide_notification_withdraw (self->notif);
134       g_clear_object (&self->notif);
135     }
136 
137   IDE_OBJECT_CLASS (gbp_code_index_service_parent_class)->destroy (object);
138 }
139 
140 static void
gbp_code_index_service_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)141 gbp_code_index_service_get_property (GObject    *object,
142                                      guint       prop_id,
143                                      GValue     *value,
144                                      GParamSpec *pspec)
145 {
146   GbpCodeIndexService *self = GBP_CODE_INDEX_SERVICE (object);
147 
148   switch (prop_id)
149     {
150     case PROP_PAUSED:
151       g_value_set_boolean (value, gbp_code_index_service_get_paused (self));
152       break;
153 
154     default:
155       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
156     }
157 }
158 
159 static void
gbp_code_index_service_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)160 gbp_code_index_service_set_property (GObject      *object,
161                                      guint         prop_id,
162                                      const GValue *value,
163                                      GParamSpec   *pspec)
164 {
165   GbpCodeIndexService *self = GBP_CODE_INDEX_SERVICE (object);
166 
167   switch (prop_id)
168     {
169     case PROP_PAUSED:
170       gbp_code_index_service_set_paused (self, g_value_get_boolean (value));
171       break;
172 
173     default:
174       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
175     }
176 }
177 
178 static void
gbp_code_index_service_class_init(GbpCodeIndexServiceClass * klass)179 gbp_code_index_service_class_init (GbpCodeIndexServiceClass *klass)
180 {
181   GObjectClass *object_class = G_OBJECT_CLASS (klass);
182   IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
183 
184   object_class->get_property = gbp_code_index_service_get_property;
185   object_class->set_property = gbp_code_index_service_set_property;
186 
187   i_object_class->destroy = gbp_code_index_service_destroy;
188   i_object_class->repr = gbp_code_index_service_repr;
189 
190   properties [PROP_PAUSED] =
191     g_param_spec_boolean ("paused",
192                           "Paused",
193                           "If the service is paused",
194                           FALSE,
195                           (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
196 
197   g_object_class_install_properties (object_class, N_PROPS, properties);
198 }
199 
200 static void
gbp_code_index_service_init(GbpCodeIndexService * self)201 gbp_code_index_service_init (GbpCodeIndexService *self)
202 {
203   g_autoptr(GIcon) icon = NULL;
204 
205   icon = g_themed_icon_new ("media-playback-pause-symbolic");
206 
207   self->notif = ide_notification_new ();
208   ide_notification_set_id (self->notif, "org.gnome.builder.code-index");
209   ide_notification_set_title (self->notif, _("Indexing Source Code"));
210   ide_notification_set_body (self->notif, _("Search, diagnostics, and autocompletion may be limited until complete."));
211   ide_notification_set_has_progress (self->notif, TRUE);
212   ide_notification_set_progress (self->notif, 0);
213   ide_notification_add_button (self->notif, NULL, icon, "code-index.paused");
214 
215   self->index = ide_code_index_index_new (IDE_OBJECT (self));
216 }
217 
218 static void
gbp_code_index_service_index_cb(GObject * object,GAsyncResult * result,gpointer user_data)219 gbp_code_index_service_index_cb (GObject      *object,
220                                  GAsyncResult *result,
221                                  gpointer      user_data)
222 {
223   GbpCodeIndexService *self = (GbpCodeIndexService *)object;
224   g_autoptr(GError) error = NULL;
225 
226   g_assert (IDE_IS_MAIN_THREAD ());
227   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
228   g_assert (G_IS_ASYNC_RESULT (result));
229 
230   if (!gbp_code_index_service_index_finish (self, result, &error))
231     {
232       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
233         g_warning ("Code indexing failed: %s", error->message);
234     }
235 }
236 
237 static gboolean
gbp_code_index_service_queue_index_cb(gpointer user_data)238 gbp_code_index_service_queue_index_cb (gpointer user_data)
239 {
240   GbpCodeIndexService *self = user_data;
241   g_autoptr(IdeContext) context = NULL;
242   IdeBuildManager *build_manager;
243   IdePipeline *pipeline;
244 
245   IDE_ENTRY;
246 
247   g_assert (IDE_IS_MAIN_THREAD ());
248   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
249 
250   self->queued_source = 0;
251 
252   g_cancellable_cancel (self->cancellable);
253   g_clear_object (&self->cancellable);
254   self->cancellable = g_cancellable_new ();
255 
256   if (!ide_object_in_destruction (IDE_OBJECT (self)) &&
257       (context = ide_object_ref_context (IDE_OBJECT (self))) &&
258       ide_context_has_project (context) &&
259       (build_manager = ide_build_manager_from_context (context)) &&
260       (pipeline = ide_build_manager_get_pipeline (build_manager)) &&
261       ide_pipeline_has_configured (pipeline))
262     gbp_code_index_service_index_async (self,
263                                         self->cancellable,
264                                         gbp_code_index_service_index_cb,
265                                         NULL);
266 
267   IDE_RETURN (G_SOURCE_REMOVE);
268 }
269 
270 static void
gbp_code_index_service_queue_index(GbpCodeIndexService * self)271 gbp_code_index_service_queue_index (GbpCodeIndexService *self)
272 {
273   g_assert (IDE_IS_MAIN_THREAD ());
274   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
275 
276   self->needs_indexing = TRUE;
277 
278   if (self->indexing || self->paused)
279     return;
280 
281   g_clear_handle_id (&self->queued_source, g_source_remove);
282   self->queued_source = g_timeout_add (DELAY_FOR_INDEXING_MSEC,
283                                        gbp_code_index_service_queue_index_cb,
284                                        self);
285 }
286 
287 static void
gbp_code_index_service_pause(GbpCodeIndexService * self)288 gbp_code_index_service_pause (GbpCodeIndexService *self)
289 {
290   g_assert (IDE_IS_MAIN_THREAD ());
291   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
292 
293   self->paused = TRUE;
294 
295   g_cancellable_cancel (self->cancellable);
296   g_clear_object (&self->cancellable);
297   g_clear_handle_id (&self->queued_source, g_source_remove);
298 
299   if (!ide_object_in_destruction (IDE_OBJECT (self)))
300     update_notification (self);
301 
302   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PAUSED]);
303 }
304 
305 static void
gbp_code_index_service_unpause(GbpCodeIndexService * self)306 gbp_code_index_service_unpause (GbpCodeIndexService *self)
307 {
308   g_assert (IDE_IS_MAIN_THREAD ());
309   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
310 
311   self->paused = FALSE;
312 
313   if (!ide_object_in_destruction (IDE_OBJECT (self)))
314     {
315       gbp_code_index_service_queue_index (self);
316       update_notification (self);
317     }
318 
319   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PAUSED]);
320 }
321 
322 static void
gbp_code_index_service_execute_cb(GObject * object,GAsyncResult * result,gpointer user_data)323 gbp_code_index_service_execute_cb (GObject      *object,
324                                    GAsyncResult *result,
325                                    gpointer      user_data)
326 {
327   GbpCodeIndexExecutor *executor = (GbpCodeIndexExecutor *)object;
328   g_autoptr(IdeTask) task = user_data;
329   g_autoptr(GError) error = NULL;
330 
331   IDE_ENTRY;
332 
333   g_assert (IDE_IS_MAIN_THREAD ());
334   g_assert (GBP_IS_CODE_INDEX_EXECUTOR (executor));
335   g_assert (G_IS_ASYNC_RESULT (result));
336   g_assert (IDE_IS_TASK (task));
337 
338   if (!gbp_code_index_executor_execute_finish (executor, result, &error))
339     ide_task_return_error (task, g_steal_pointer (&error));
340   else
341     ide_task_return_boolean (task, TRUE);
342 
343   ide_object_destroy (IDE_OBJECT (executor));
344 
345   IDE_EXIT;
346 }
347 
348 static void
gbp_code_index_service_load_flags_cb(GObject * object,GAsyncResult * result,gpointer user_data)349 gbp_code_index_service_load_flags_cb (GObject      *object,
350                                       GAsyncResult *result,
351                                       gpointer      user_data)
352 {
353   GbpCodeIndexPlan *plan = (GbpCodeIndexPlan *)object;
354   g_autoptr(GbpCodeIndexExecutor) executor = NULL;
355   g_autoptr(IdeTask) task = user_data;
356   g_autoptr(GError) error = NULL;
357   GbpCodeIndexService *self;
358 
359   IDE_ENTRY;
360 
361   g_assert (IDE_IS_MAIN_THREAD ());
362   g_assert (GBP_IS_CODE_INDEX_PLAN (plan));
363   g_assert (G_IS_ASYNC_RESULT (result));
364   g_assert (IDE_IS_TASK (task));
365 
366   if (!gbp_code_index_plan_load_flags_finish (plan, result, &error))
367     {
368       ide_task_return_error (task, g_steal_pointer (&error));
369       IDE_EXIT;
370     }
371 
372   if (ide_task_return_error_if_cancelled (task))
373     IDE_EXIT;
374 
375   self = ide_task_get_source_object (task);
376   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
377 
378   executor = gbp_code_index_executor_new (plan);
379   ide_object_append (IDE_OBJECT (self), IDE_OBJECT (executor));
380 
381   gbp_code_index_executor_execute_async (executor,
382                                          self->notif,
383                                          ide_task_get_cancellable (task),
384                                          gbp_code_index_service_execute_cb,
385                                          g_object_ref (task));
386 
387   IDE_EXIT;
388 }
389 
390 static void
gbp_code_index_service_cull_index_cb(GObject * object,GAsyncResult * result,gpointer user_data)391 gbp_code_index_service_cull_index_cb (GObject      *object,
392                                       GAsyncResult *result,
393                                       gpointer      user_data)
394 {
395   GbpCodeIndexPlan *plan = (GbpCodeIndexPlan *)object;
396   g_autoptr(IdeTask) task = user_data;
397   g_autoptr(GError) error = NULL;
398   IdeContext *context;
399 
400   IDE_ENTRY;
401 
402   g_assert (IDE_IS_MAIN_THREAD ());
403   g_assert (GBP_IS_CODE_INDEX_PLAN (plan));
404   g_assert (G_IS_ASYNC_RESULT (result));
405   g_assert (IDE_IS_TASK (task));
406 
407   if (!gbp_code_index_plan_cull_indexed_finish (plan, result, &error))
408     {
409       ide_task_return_error (task, g_steal_pointer (&error));
410       IDE_EXIT;
411     }
412 
413   if (ide_task_return_error_if_cancelled (task))
414     IDE_EXIT;
415 
416   context = ide_task_get_task_data (task);
417   g_assert (IDE_IS_CONTEXT (context));
418 
419   gbp_code_index_plan_load_flags_async (plan,
420                                         context,
421                                         ide_task_get_cancellable (task),
422                                         gbp_code_index_service_load_flags_cb,
423                                         g_object_ref (task));
424 
425   IDE_EXIT;
426 }
427 
428 static void
gbp_code_index_service_populate_cb(GObject * object,GAsyncResult * result,gpointer user_data)429 gbp_code_index_service_populate_cb (GObject      *object,
430                                     GAsyncResult *result,
431                                     gpointer      user_data)
432 {
433   GbpCodeIndexPlan *plan = (GbpCodeIndexPlan *)object;
434   g_autoptr(IdeTask) task = user_data;
435   g_autoptr(GError) error = NULL;
436   IdeContext *context;
437 
438   IDE_ENTRY;
439 
440   g_assert (IDE_IS_MAIN_THREAD ());
441   g_assert (GBP_IS_CODE_INDEX_PLAN (plan));
442   g_assert (G_IS_ASYNC_RESULT (result));
443   g_assert (IDE_IS_TASK (task));
444 
445   if (!gbp_code_index_plan_populate_finish (plan, result, &error))
446     {
447       ide_task_return_error (task, g_steal_pointer (&error));
448       IDE_EXIT;
449     }
450 
451   if (ide_task_return_error_if_cancelled (task))
452     IDE_EXIT;
453 
454   context = ide_task_get_task_data (task);
455   g_assert (IDE_IS_CONTEXT (context));
456 
457   gbp_code_index_plan_cull_indexed_async (plan,
458                                           context,
459                                           ide_task_get_cancellable (task),
460                                           gbp_code_index_service_cull_index_cb,
461                                           g_object_ref (task));
462 
463   IDE_EXIT;
464 }
465 
466 static void
gbp_code_index_service_index_async(GbpCodeIndexService * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)467 gbp_code_index_service_index_async (GbpCodeIndexService *self,
468                                     GCancellable        *cancellable,
469                                     GAsyncReadyCallback  callback,
470                                     gpointer             user_data)
471 {
472   g_autoptr(GbpCodeIndexPlan) plan = NULL;
473   g_autoptr(IdeContext) context = NULL;
474   g_autoptr(IdeTask) task = NULL;
475 
476   IDE_ENTRY;
477 
478   g_assert (IDE_IS_MAIN_THREAD ());
479   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
480   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
481 
482   if (cancellable == NULL)
483     g_warning ("Attempt to index without a valid cancellable. This will affect pausibility.");
484 
485   self->indexing = TRUE;
486   self->needs_indexing = FALSE;
487 
488   task = ide_task_new (self, cancellable, callback, user_data);
489   ide_task_set_source_tag (task, gbp_code_index_service_index_async);
490 
491   if (ide_task_return_error_if_cancelled (task))
492     IDE_EXIT;
493 
494   context = ide_object_ref_context (IDE_OBJECT (self));
495   g_assert (IDE_IS_CONTEXT (context));
496 
497   ide_task_set_task_data (task, g_object_ref (context), g_object_unref);
498 
499   plan = gbp_code_index_plan_new ();
500 
501   gbp_code_index_plan_populate_async (plan,
502                                       context,
503                                       cancellable,
504                                       gbp_code_index_service_populate_cb,
505                                       g_steal_pointer (&task));
506 
507   update_notification (self);
508 
509   IDE_EXIT;
510 }
511 
512 static gboolean
gbp_code_index_service_index_finish(GbpCodeIndexService * self,GAsyncResult * result,GError ** error)513 gbp_code_index_service_index_finish (GbpCodeIndexService  *self,
514                                      GAsyncResult         *result,
515                                      GError              **error)
516 {
517   g_assert (IDE_IS_MAIN_THREAD ());
518   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
519   g_assert (IDE_IS_TASK (result));
520 
521   self->indexing = FALSE;
522 
523   if (!ide_object_in_destruction (IDE_OBJECT (self)))
524     {
525       update_notification (self);
526       gbp_code_index_service_reload_indexes (self);
527     }
528 
529   return ide_task_propagate_boolean (IDE_TASK (result), error);
530 }
531 
532 static void
gbp_code_index_service_buffer_saved_cb(GbpCodeIndexService * self,IdeBuffer * buffer,IdeBufferManager * buffer_manager)533 gbp_code_index_service_buffer_saved_cb (GbpCodeIndexService *self,
534                                         IdeBuffer           *buffer,
535                                         IdeBufferManager    *buffer_manager)
536 {
537   GtkSourceLanguage *lang;
538 
539   g_assert (IDE_IS_MAIN_THREAD ());
540   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
541   g_assert (IDE_IS_BUFFER (buffer));
542   g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
543 
544   /*
545    * Only update the index if the file save will result in a change to the
546    * directory's index. We determine that by if an indexer is available.
547    */
548 
549   if ((lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer))))
550     {
551       const gchar *lang_id = gtk_source_language_get_id (lang);
552       const GList *list = peas_engine_get_plugin_list (peas_engine_get_default ());
553 
554       for (const GList *iter = list; iter; iter = iter->next)
555         {
556           PeasPluginInfo *plugin_info = iter->data;
557           const gchar *languages = peas_plugin_info_get_external_data (plugin_info,
558                                                                        "Code-Indexer-Languages");
559 
560           /* Not exact check, but good enough for now */
561           if (languages != NULL && strstr (languages, lang_id) != NULL)
562             {
563               gbp_code_index_service_queue_index (self);
564               break;
565             }
566         }
567     }
568 }
569 
570 static void
gbp_code_index_service_build_started_cb(GbpCodeIndexService * self,IdePipeline * pipeline,IdeBuildManager * build_manager)571 gbp_code_index_service_build_started_cb (GbpCodeIndexService *self,
572                                          IdePipeline         *pipeline,
573                                          IdeBuildManager     *build_manager)
574 {
575   g_assert (IDE_IS_MAIN_THREAD ());
576   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
577   g_assert (IDE_IS_PIPELINE (pipeline));
578   g_assert (IDE_IS_BUILD_MANAGER (build_manager));
579 
580   /* If we are starting a new build that is going to ensure that we reach to
581    * the configure phase (or further), then delay any index building until
582    * after that operation completes. There is no need to compete for resources
583    * while building (especially if indexing might fail anyway).
584    */
585   if (ide_pipeline_get_requested_phase (pipeline) >= IDE_PIPELINE_PHASE_CONFIGURE)
586     {
587       self->build_inhibit = TRUE;
588       g_cancellable_cancel (self->cancellable);
589       g_clear_object (&self->cancellable);
590     }
591 }
592 
593 static void
gbp_code_index_service_build_failed_cb(GbpCodeIndexService * self,IdePipeline * pipeline,IdeBuildManager * build_manager)594 gbp_code_index_service_build_failed_cb (GbpCodeIndexService *self,
595                                         IdePipeline         *pipeline,
596                                         IdeBuildManager     *build_manager)
597 {
598   g_assert (IDE_IS_MAIN_THREAD ());
599   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
600   g_assert (IDE_IS_PIPELINE (pipeline));
601   g_assert (IDE_IS_BUILD_MANAGER (build_manager));
602 
603   self->build_inhibit = FALSE;
604 }
605 
606 static void
gbp_code_index_service_build_finished_cb(GbpCodeIndexService * self,IdePipeline * pipeline,IdeBuildManager * build_manager)607 gbp_code_index_service_build_finished_cb (GbpCodeIndexService *self,
608                                           IdePipeline         *pipeline,
609                                           IdeBuildManager     *build_manager)
610 {
611   g_assert (IDE_IS_MAIN_THREAD ());
612   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
613   g_assert (IDE_IS_PIPELINE (pipeline));
614   g_assert (IDE_IS_BUILD_MANAGER (build_manager));
615 
616   /*
617    * If we paused building due to inhibition while building, then we need to
618    * possibly restore the build process and queue a new indexing.
619    */
620 
621   if (self->build_inhibit)
622     {
623       self->build_inhibit = FALSE;
624 
625       if (ide_pipeline_has_configured (pipeline))
626         gbp_code_index_service_queue_index (self);
627     }
628 }
629 
630 static void
gbp_code_index_service_vcs_changed_cb(GbpCodeIndexService * self,IdeVcs * vcs)631 gbp_code_index_service_vcs_changed_cb (GbpCodeIndexService *self,
632                                        IdeVcs              *vcs)
633 {
634   IDE_ENTRY;
635 
636   g_assert (IDE_IS_MAIN_THREAD ());
637   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
638   g_assert (IDE_IS_VCS (vcs));
639 
640   /* Possibly switched branches, queue re-indexing */
641   gbp_code_index_service_queue_index (self);
642 
643   IDE_EXIT;
644 }
645 
646 static void
gbp_code_index_service_file_trashed_cb(GbpCodeIndexService * self,GFile * file,IdeProject * project)647 gbp_code_index_service_file_trashed_cb (GbpCodeIndexService *self,
648                                         GFile               *file,
649                                         IdeProject          *project)
650 {
651   g_assert (IDE_IS_MAIN_THREAD ());
652   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
653   g_assert (G_IS_FILE (file));
654   g_assert (IDE_IS_PROJECT (project));
655 
656   gbp_code_index_service_queue_index (self);
657 }
658 
659 static void
gbp_code_index_service_file_renamed_cb(GbpCodeIndexService * self,GFile * src_file,GFile * dst_file,IdeProject * project)660 gbp_code_index_service_file_renamed_cb (GbpCodeIndexService *self,
661                                         GFile               *src_file,
662                                         GFile               *dst_file,
663                                         IdeProject          *project)
664 {
665   g_assert (IDE_IS_MAIN_THREAD ());
666   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
667   g_assert (G_IS_FILE (src_file));
668   g_assert (G_IS_FILE (dst_file));
669   g_assert (IDE_IS_PROJECT (project));
670 
671   gbp_code_index_service_queue_index (self);
672 }
673 
674 static void
gbp_code_index_service_load_indexes_cb(GFile * directory,GPtrArray * file_infos,gpointer user_data)675 gbp_code_index_service_load_indexes_cb (GFile     *directory,
676                                         GPtrArray *file_infos,
677                                         gpointer   user_data)
678 {
679   LoadIndexes *state = user_data;
680   g_autoptr(GFile) source_directory = NULL;
681 
682   g_assert (G_IS_FILE (directory));
683   g_assert (state != NULL);
684 
685   if (!g_file_equal (directory, state->indexdir))
686     {
687       g_autofree gchar *relative = NULL;
688 
689       relative = g_file_get_relative_path (state->indexdir, directory);
690       source_directory = g_file_get_child (state->workdir, relative);
691     }
692   else
693     {
694       source_directory = g_object_ref (state->workdir);
695     }
696 
697   ide_code_index_index_load (state->index,
698                              directory,
699                              source_directory,
700                              NULL,
701                              NULL);
702 }
703 
704 static void
gbp_code_index_service_load_indexes(IdeTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)705 gbp_code_index_service_load_indexes (IdeTask      *task,
706                                      gpointer      source_object,
707                                      gpointer      task_data,
708                                      GCancellable *cancellable)
709 {
710   LoadIndexes *state = task_data;
711 
712   g_assert (IDE_IS_TASK (task));
713   g_assert (GBP_IS_CODE_INDEX_SERVICE (source_object));
714   g_assert (state != NULL);
715   g_assert (IDE_IS_CODE_INDEX_INDEX (state->index));
716   g_assert (G_IS_FILE (state->workdir));
717   g_assert (G_IS_FILE (state->indexdir));
718 
719   ide_g_file_walk (state->indexdir,
720                    NULL,
721                    cancellable,
722                    gbp_code_index_service_load_indexes_cb,
723                    state);
724 
725   ide_task_return_boolean (task, TRUE);
726 }
727 
728 static void
gbp_code_index_service_reload_indexes(GbpCodeIndexService * self)729 gbp_code_index_service_reload_indexes (GbpCodeIndexService *self)
730 {
731   g_autoptr(IdeContext) context = NULL;
732   g_autoptr(IdeTask) task = NULL;
733   LoadIndexes *state;
734 
735   g_assert (IDE_IS_MAIN_THREAD ());
736   g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
737 
738   if (!(context = ide_object_ref_context (IDE_OBJECT (self))))
739     return;
740 
741   state = g_slice_new0 (LoadIndexes);
742   state->index = g_object_ref (self->index);
743   state->workdir = ide_context_ref_workdir (context);
744   state->indexdir = ide_context_cache_file (context, "code-index", NULL);
745 
746   task = ide_task_new (self, NULL, NULL, NULL);
747   ide_task_set_source_tag (task, gbp_code_index_service_reload_indexes);
748   ide_task_set_task_data (task, state, load_indexes_free);
749   ide_task_run_in_thread (task, gbp_code_index_service_load_indexes);
750 }
751 
752 void
gbp_code_index_service_start(GbpCodeIndexService * self)753 gbp_code_index_service_start (GbpCodeIndexService *self)
754 {
755   g_autoptr(IdeContext) context = NULL;
756   g_autoptr(GFile) index_dir = NULL;
757   IdeBufferManager *buffer_manager;
758   IdeBuildManager *build_manager;
759   IdeProject *project;
760   IdeVcs *vcs;
761   gboolean has_index;
762 
763   IDE_ENTRY;
764 
765   g_return_if_fail (IDE_IS_MAIN_THREAD ());
766   g_return_if_fail (GBP_IS_CODE_INDEX_SERVICE (self));
767   g_return_if_fail (self->started == FALSE);
768   g_return_if_fail (!ide_object_in_destruction (IDE_OBJECT (self)));
769 
770   self->started = TRUE;
771 
772   if (!(context = ide_object_ref_context (IDE_OBJECT (self))))
773     {
774       g_warning ("Attempt to start code-index service without access to context");
775       IDE_EXIT;
776     }
777 
778   buffer_manager = ide_buffer_manager_from_context (context);
779 
780   g_signal_connect_object (buffer_manager,
781                            "buffer-saved",
782                            G_CALLBACK (gbp_code_index_service_buffer_saved_cb),
783                            self,
784                            G_CONNECT_SWAPPED);
785 
786   build_manager = ide_build_manager_from_context (context);
787 
788   g_signal_connect_object (build_manager,
789                            "build-failed",
790                            G_CALLBACK (gbp_code_index_service_build_failed_cb),
791                            self,
792                            G_CONNECT_SWAPPED);
793 
794   g_signal_connect_object (build_manager,
795                            "build-finished",
796                            G_CALLBACK (gbp_code_index_service_build_finished_cb),
797                            self,
798                            G_CONNECT_SWAPPED);
799 
800   g_signal_connect_object (build_manager,
801                            "build-started",
802                            G_CALLBACK (gbp_code_index_service_build_started_cb),
803                            self,
804                            G_CONNECT_SWAPPED);
805 
806   vcs = ide_vcs_from_context (context);
807 
808   g_signal_connect_object (vcs,
809                            "changed",
810                            G_CALLBACK (gbp_code_index_service_vcs_changed_cb),
811                            self,
812                            G_CONNECT_SWAPPED);
813 
814   project = ide_project_from_context (context);
815 
816   g_signal_connect_object (project,
817                            "file-trashed",
818                            G_CALLBACK (gbp_code_index_service_file_trashed_cb),
819                            self,
820                            G_CONNECT_SWAPPED);
821 
822   g_signal_connect_object (project,
823                            "file-renamed",
824                            G_CALLBACK (gbp_code_index_service_file_renamed_cb),
825                            self,
826                            G_CONNECT_SWAPPED);
827 
828   index_dir = ide_context_cache_file (context, "code-index", NULL);
829   has_index = g_file_query_exists (index_dir, NULL);
830 
831   if (!self->paused)
832     {
833       /*
834        * We only want to immediately start indexing at startup if the project
835        * does not yet have an index. Otherwise, we want to wait for a user
836        * action to cause the indexes to be rebuilt so that we don't risk
837        * annoying the user with build actions.
838        */
839       if (!has_index && !ide_build_manager_get_busy (build_manager))
840         gbp_code_index_service_queue_index (self);
841     }
842 
843   gbp_code_index_service_reload_indexes (self);
844 
845   IDE_EXIT;
846 }
847 
848 void
gbp_code_index_service_stop(GbpCodeIndexService * self)849 gbp_code_index_service_stop (GbpCodeIndexService *self)
850 {
851   g_return_if_fail (IDE_IS_MAIN_THREAD ());
852   g_return_if_fail (GBP_IS_CODE_INDEX_SERVICE (self));
853 
854   if (!self->started)
855     return;
856 
857   self->started = FALSE;
858 
859   g_cancellable_cancel (self->cancellable);
860   g_clear_object (&self->cancellable);
861   g_clear_handle_id (&self->queued_source, g_source_remove);
862 
863   if (self->notif)
864     {
865       ide_notification_withdraw (self->notif);
866       g_clear_object (&self->notif);
867     }
868 }
869 
870 gboolean
gbp_code_index_service_get_paused(GbpCodeIndexService * self)871 gbp_code_index_service_get_paused (GbpCodeIndexService *self)
872 {
873   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
874   g_return_val_if_fail (GBP_IS_CODE_INDEX_SERVICE (self), FALSE);
875 
876   return self->paused;
877 }
878 
879 void
gbp_code_index_service_set_paused(GbpCodeIndexService * self,gboolean paused)880 gbp_code_index_service_set_paused (GbpCodeIndexService *self,
881                                    gboolean             paused)
882 {
883   g_return_if_fail (IDE_IS_MAIN_THREAD ());
884   g_return_if_fail (GBP_IS_CODE_INDEX_SERVICE (self));
885 
886   paused = !!paused;
887 
888   if (paused != self->paused)
889     {
890       if (paused)
891         gbp_code_index_service_pause (self);
892       else
893         gbp_code_index_service_unpause (self);
894     }
895 }
896 
897 static IdeCodeIndexer *
create_indexer(GbpCodeIndexService * self,const gchar * module_name)898 create_indexer (GbpCodeIndexService *self,
899                 const gchar         *module_name)
900 {
901   PeasEngine *engine = peas_engine_get_default ();
902   PeasPluginInfo *plugin_info;
903 
904   g_return_val_if_fail (module_name != NULL, NULL);
905 
906   if ((plugin_info = peas_engine_get_plugin_info (engine, module_name)) &&
907       peas_plugin_info_is_loaded (plugin_info))
908     {
909       PeasExtension *exten;
910 
911       exten = peas_engine_create_extension (peas_engine_get_default (),
912                                             plugin_info,
913                                             IDE_TYPE_CODE_INDEXER,
914                                             NULL);
915 
916       if (IDE_IS_OBJECT (exten))
917         ide_object_append (IDE_OBJECT (self), IDE_OBJECT (exten));
918 
919       return IDE_CODE_INDEXER (exten);
920     }
921 
922   return NULL;
923 }
924 
925 IdeCodeIndexer *
gbp_code_index_service_get_indexer(GbpCodeIndexService * self,const gchar * lang_id,const gchar * path)926 gbp_code_index_service_get_indexer (GbpCodeIndexService *self,
927                                     const gchar         *lang_id,
928                                     const gchar         *path)
929 {
930   g_autoptr(GPtrArray) indexers = NULL;
931 
932   g_return_val_if_fail (GBP_IS_CODE_INDEX_SERVICE (self), NULL);
933 
934   indexers = collect_indexer_info ();
935 
936   if (lang_id != NULL)
937     {
938       for (guint i = 0; i < indexers->len; i++)
939         {
940           const IndexerInfo *info = g_ptr_array_index (indexers, i);
941 
942           if (info->lang_ids == NULL)
943             continue;
944 
945           for (guint j = 0; info->lang_ids[j]; j++)
946             {
947               if (g_str_equal (lang_id, info->lang_ids[j]))
948                 return create_indexer (self, info->module_name);
949             }
950         }
951     }
952 
953   if (path != NULL)
954     {
955       g_autofree gchar *name = g_path_get_basename (path);
956       g_autofree gchar *reversed = g_utf8_strreverse (name, -1);
957 
958       for (guint i = 0; i < indexers->len; i++)
959         {
960           const IndexerInfo *info = g_ptr_array_index (indexers, i);
961 
962           if (indexer_info_matches (info, name, reversed, NULL))
963             return create_indexer (self, info->module_name);
964         }
965     }
966 
967   return NULL;
968 }
969 
970 GbpCodeIndexService *
gbp_code_index_service_from_context(IdeContext * context)971 gbp_code_index_service_from_context (IdeContext *context)
972 {
973   GbpCodeIndexService *ret;
974 
975   g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
976 
977   if (!(ret = ide_context_peek_child_typed (context, GBP_TYPE_CODE_INDEX_SERVICE)))
978     {
979       g_autoptr(GbpCodeIndexService) self = NULL;
980 
981       self = g_object_new (GBP_TYPE_CODE_INDEX_SERVICE,
982                            "parent", context,
983                            NULL);
984       gbp_code_index_service_start (self);
985       ret = ide_context_peek_child_typed (context, GBP_TYPE_CODE_INDEX_SERVICE);
986     }
987 
988   return ret;
989 }
990 
991 IdeCodeIndexIndex *
gbp_code_index_service_get_index(GbpCodeIndexService * self)992 gbp_code_index_service_get_index (GbpCodeIndexService *self)
993 {
994   g_return_val_if_fail (GBP_IS_CODE_INDEX_SERVICE (self), NULL);
995 
996   return self->index;
997 }
998