1 /* ide-gca-service.c
2  *
3  * Copyright 2015-2019 Christian Hergert <christian@hergert.me>
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 "ide-gca-service"
22 
23 #include <dazzle.h>
24 #include <glib/gi18n.h>
25 #include <libide-threading.h>
26 
27 #include "ide-gca-service.h"
28 
29 struct _IdeGcaService
30 {
31   IdeObject        parent_instance;
32 
33   GDBusConnection *bus;
34   GHashTable      *proxy_cache;
35 
36   gulong           bus_closed_handler;
37 };
38 
G_DEFINE_FINAL_TYPE(IdeGcaService,ide_gca_service,IDE_TYPE_OBJECT)39 G_DEFINE_FINAL_TYPE (IdeGcaService, ide_gca_service, IDE_TYPE_OBJECT)
40 
41 static void
42 on_bus_closed (GDBusConnection *bus,
43                gboolean         remote_peer_vanished,
44                GError          *error,
45                gpointer         user_data)
46 {
47   IdeGcaService *self = user_data;
48 
49   g_assert (G_IS_DBUS_CONNECTION (bus));
50   g_assert (IDE_IS_GCA_SERVICE (self));
51 
52   if (self->bus_closed_handler != 0)
53     dzl_clear_signal_handler (bus, &self->bus_closed_handler);
54 
55   g_clear_object (&self->bus);
56   g_hash_table_remove_all (self->proxy_cache);
57 }
58 
59 static GDBusConnection *
ide_gca_service_get_bus(IdeGcaService * self,GCancellable * cancellable,GError ** error)60 ide_gca_service_get_bus (IdeGcaService  *self,
61                          GCancellable   *cancellable,
62                          GError        **error)
63 {
64   g_assert (IDE_IS_GCA_SERVICE (self));
65   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
66 
67   if (self->bus == NULL)
68     {
69       const GDBusConnectionFlags flags = (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
70                                           G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION);
71       g_autofree gchar *address = NULL;
72       g_autoptr(GDBusConnection) bus = NULL;
73 
74       address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, cancellable, error);
75       if (address == NULL)
76         return NULL;
77 
78       bus = g_dbus_connection_new_for_address_sync (address, flags, NULL, cancellable, error);
79       if (bus == NULL)
80         return NULL;
81 
82       self->bus_closed_handler = g_signal_connect (bus,
83                                                    "closed",
84                                                    G_CALLBACK (on_bus_closed),
85                                                    self);
86 
87       g_dbus_connection_set_exit_on_close (bus, FALSE);
88 
89       self->bus = g_object_ref (bus);
90     }
91 
92   return self->bus;
93 }
94 
95 static const gchar *
remap_language(const gchar * lang_id)96 remap_language (const gchar *lang_id)
97 {
98   static GHashTable *remap;
99   gchar *remapped_lang_id;
100 
101   if (lang_id == NULL)
102     return NULL;
103 
104   if (remap == NULL)
105     {
106       remap = g_hash_table_new (g_str_hash, g_str_equal);
107 #define ADD_REMAP(key,val) g_hash_table_insert (remap, (gchar *)key, (gchar *)val)
108       ADD_REMAP ("chdr", "c");
109       ADD_REMAP ("cpp", "c");
110       ADD_REMAP ("objc", "c");
111       ADD_REMAP ("scss", "css");
112 #undef ADD_REMAP
113     }
114 
115   remapped_lang_id = g_hash_table_lookup (remap, lang_id);
116 
117   if (remapped_lang_id == NULL)
118     return lang_id;
119   else
120     return remapped_lang_id;
121 }
122 
123 static void
proxy_new_cb(GObject * object,GAsyncResult * result,gpointer user_data)124 proxy_new_cb (GObject      *object,
125               GAsyncResult *result,
126               gpointer      user_data)
127 {
128   IdeGcaService *self;
129   g_autoptr(IdeTask) task = user_data;
130   g_autoptr(GError) error = NULL;
131   const gchar *language_id;
132   GcaService *proxy;
133 
134   g_assert (IDE_IS_TASK (task));
135   g_assert (G_IS_ASYNC_RESULT (result));
136 
137   self = ide_task_get_source_object (task);
138 
139   proxy = gca_service_proxy_new_finish (result, &error);
140 
141   if (!proxy)
142     {
143       ide_task_return_error (task, g_steal_pointer (&error));
144       return;
145     }
146 
147   language_id = ide_task_get_task_data (task);
148   g_hash_table_replace (self->proxy_cache, g_strdup (language_id),
149                         g_object_ref (proxy));
150 
151   ide_task_return_pointer (task, g_object_ref (proxy), g_object_unref);
152 
153   g_clear_object (&proxy);
154 }
155 
156 void
ide_gca_service_get_proxy_async(IdeGcaService * self,const gchar * language_id,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)157 ide_gca_service_get_proxy_async (IdeGcaService       *self,
158                                  const gchar         *language_id,
159                                  GCancellable        *cancellable,
160                                  GAsyncReadyCallback  callback,
161                                  gpointer             user_data)
162 {
163   g_autoptr(IdeTask) task = NULL;
164   g_autoptr(GError) error = NULL;
165   g_autofree gchar *name = NULL;
166   g_autofree gchar *object_path = NULL;
167   GcaService *proxy;
168   GDBusConnection *bus;
169 
170   g_return_if_fail (IDE_IS_GCA_SERVICE (self));
171   g_return_if_fail (language_id);
172   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
173 
174   task = ide_task_new (self, cancellable, callback, user_data);
175 
176   language_id = remap_language (language_id);
177 
178   if (!language_id)
179     {
180       ide_task_return_new_error (task,
181                                  G_IO_ERROR,
182                                  G_IO_ERROR_FAILED,
183                                  _("No language specified"));
184       return;
185     }
186 
187   bus = ide_gca_service_get_bus (self, cancellable, &error);
188 
189   if (bus == NULL)
190     {
191       ide_task_return_error (task, g_steal_pointer (&error));
192       return;
193     }
194 
195   if ((proxy = g_hash_table_lookup (self->proxy_cache, language_id)))
196     {
197       ide_task_return_pointer (task, g_object_ref (proxy), g_object_unref);
198       return;
199     }
200 
201   ide_task_set_task_data (task, g_strdup (language_id), g_free);
202 
203   name = g_strdup_printf ("org.gnome.CodeAssist.v1.%s", language_id);
204   object_path = g_strdup_printf ("/org/gnome/CodeAssist/v1/%s", language_id);
205 
206   gca_service_proxy_new (bus,
207                          G_DBUS_PROXY_FLAGS_NONE,
208                          name,
209                          object_path,
210                          cancellable,
211                          proxy_new_cb,
212                          g_object_ref (task));
213 }
214 
215 /**
216  * ide_gca_service_get_proxy_finish:
217  *
218  * Completes an asynchronous request to load a Gca proxy.
219  *
220  * Returns: (transfer full): a #GcaService or %NULL upon failure.
221  */
222 GcaService *
ide_gca_service_get_proxy_finish(IdeGcaService * self,GAsyncResult * result,GError ** error)223 ide_gca_service_get_proxy_finish (IdeGcaService  *self,
224                                   GAsyncResult   *result,
225                                   GError        **error)
226 {
227   IdeTask *task = (IdeTask *)result;
228 
229   g_return_val_if_fail (IDE_IS_GCA_SERVICE (self), NULL);
230   g_return_val_if_fail (IDE_IS_TASK (task), NULL);
231 
232   return ide_task_propagate_pointer (task, error);
233 }
234 
235 static void
ide_gca_service_finalize(GObject * object)236 ide_gca_service_finalize (GObject *object)
237 {
238   IdeGcaService *self = (IdeGcaService *)object;
239 
240   if (self->bus != NULL)
241     {
242       dzl_clear_signal_handler (self->bus, &self->bus_closed_handler);
243       g_clear_object (&self->bus);
244     }
245 
246   g_clear_pointer (&self->proxy_cache, g_hash_table_unref);
247 
248   G_OBJECT_CLASS (ide_gca_service_parent_class)->finalize (object);
249 }
250 
251 static void
ide_gca_service_class_init(IdeGcaServiceClass * klass)252 ide_gca_service_class_init (IdeGcaServiceClass *klass)
253 {
254   GObjectClass *object_class = G_OBJECT_CLASS (klass);
255 
256   object_class->finalize = ide_gca_service_finalize;
257 }
258 
259 static void
ide_gca_service_init(IdeGcaService * self)260 ide_gca_service_init (IdeGcaService *self)
261 {
262   self->proxy_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
263                                              g_free, g_object_unref);
264 }
265 
266 IdeGcaService *
ide_gca_service_from_context(IdeContext * context)267 ide_gca_service_from_context (IdeContext *context)
268 {
269   g_autoptr(IdeGcaService) self = NULL;
270 
271   g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
272 
273   self = ide_object_ensure_child_typed (IDE_OBJECT (context), IDE_TYPE_GCA_SERVICE);
274   return ide_context_peek_child_typed (context, IDE_TYPE_GCA_SERVICE);
275 }
276