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