1 /* gbp-git-client.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-git-client"
22
23 #include "config.h"
24
25 #include <gio/gunixinputstream.h>
26 #include <gio/gunixoutputstream.h>
27 #include <glib/gi18n.h>
28 #include <glib-unix.h>
29 #include <libide-threading.h>
30
31 #include "gbp-git-client.h"
32
33 struct _GbpGitClient
34 {
35 IdeObject parent;
36 IdeSubprocessSupervisor *supervisor;
37 GDBusConnection *connection;
38 IpcGitService *service;
39 GQueue get_service;
40 gint state;
41 };
42
43 enum {
44 STATE_INITIAL,
45 STATE_SPAWNING,
46 STATE_RUNNING,
47 STATE_SHUTDOWN,
48 };
49
G_DEFINE_FINAL_TYPE(GbpGitClient,gbp_git_client,IDE_TYPE_OBJECT)50 G_DEFINE_FINAL_TYPE (GbpGitClient, gbp_git_client, IDE_TYPE_OBJECT)
51
52 static void
53 gbp_git_client_subprocess_spawned (GbpGitClient *self,
54 IdeSubprocess *subprocess,
55 IdeSubprocessSupervisor *supervisor)
56 {
57 g_autoptr(GIOStream) stream = NULL;
58 GOutputStream *output;
59 GInputStream *input;
60 GList *queued;
61 gint fd;
62
63 IDE_ENTRY;
64
65 g_assert (GBP_IS_GIT_CLIENT (self));
66 g_assert (IDE_IS_SUBPROCESS (subprocess));
67 g_assert (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
68
69 ide_object_lock (IDE_OBJECT (self));
70
71 g_assert (self->service == NULL);
72
73 if (self->state == STATE_SPAWNING)
74 self->state = STATE_RUNNING;
75
76 input = ide_subprocess_get_stdout_pipe (subprocess);
77 output = ide_subprocess_get_stdin_pipe (subprocess);
78 stream = g_simple_io_stream_new (input, output);
79
80 g_assert (G_IS_UNIX_INPUT_STREAM (input));
81 g_assert (G_IS_UNIX_OUTPUT_STREAM (output));
82
83 fd = g_unix_input_stream_get_fd (G_UNIX_INPUT_STREAM (input));
84 g_unix_set_fd_nonblocking (fd, TRUE, NULL);
85
86 fd = g_unix_output_stream_get_fd (G_UNIX_OUTPUT_STREAM (output));
87 g_unix_set_fd_nonblocking (fd, TRUE, NULL);
88
89 self->connection = g_dbus_connection_new_sync (stream, NULL,
90 G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
91 NULL, NULL, NULL);
92 g_dbus_connection_set_exit_on_close (self->connection, FALSE);
93 g_dbus_connection_start_message_processing (self->connection);
94
95 self->service = ipc_git_service_proxy_new_sync (self->connection,
96 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
97 NULL,
98 "/org/gnome/Builder/Git",
99 NULL,
100 NULL);
101
102 /* We can have long running operations, so set no timeout */
103 g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (self->service), G_MAXINT);
104
105 queued = g_steal_pointer (&self->get_service.head);
106
107 self->get_service.head = NULL;
108 self->get_service.tail = NULL;
109 self->get_service.length = 0;
110
111 for (const GList *iter = queued; iter != NULL; iter = iter->next)
112 {
113 IdeTask *task = iter->data;
114
115 ide_task_return_object (task, g_object_ref (self->service));
116 }
117
118 g_list_free_full (queued, g_object_unref);
119
120 ide_object_unlock (IDE_OBJECT (self));
121
122 IDE_EXIT;
123 }
124
125 static void
gbp_git_client_subprocess_exited(GbpGitClient * self,IdeSubprocess * subprocess,IdeSubprocessSupervisor * supervisor)126 gbp_git_client_subprocess_exited (GbpGitClient *self,
127 IdeSubprocess *subprocess,
128 IdeSubprocessSupervisor *supervisor)
129 {
130 IDE_ENTRY;
131
132 g_assert (GBP_IS_GIT_CLIENT (self));
133 g_assert (IDE_IS_SUBPROCESS (subprocess));
134 g_assert (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
135
136 ide_object_lock (IDE_OBJECT (self));
137
138 if (self->state == STATE_RUNNING)
139 self->state = STATE_SPAWNING;
140
141 g_clear_object (&self->service);
142
143 ide_object_unlock (IDE_OBJECT (self));
144
145 IDE_EXIT;
146 }
147
148 static void
gbp_git_client_destroy(IdeObject * object)149 gbp_git_client_destroy (IdeObject *object)
150 {
151 GbpGitClient *self = (GbpGitClient *)object;
152 g_autoptr(IdeSubprocessSupervisor) supervisor = g_steal_pointer (&self->supervisor);
153
154 if (supervisor != NULL)
155 ide_subprocess_supervisor_stop (supervisor);
156
157 IDE_OBJECT_CLASS (gbp_git_client_parent_class)->destroy (object);
158 }
159
160 static void
gbp_git_client_parent_set(IdeObject * object,IdeObject * parent)161 gbp_git_client_parent_set (IdeObject *object,
162 IdeObject *parent)
163 {
164 GbpGitClient *self= (GbpGitClient *)object;
165 g_autoptr(IdeSubprocessLauncher) launcher = NULL;
166
167 g_assert (GBP_IS_GIT_CLIENT (self));
168 g_assert (!parent || IDE_IS_OBJECT (parent));
169
170 if (parent == NULL)
171 return;
172
173 ide_object_lock (IDE_OBJECT (self));
174
175 launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE |
176 G_SUBPROCESS_FLAGS_STDIN_PIPE);
177 ide_subprocess_launcher_set_cwd (launcher, g_get_home_dir ());
178 ide_subprocess_launcher_set_clear_env (launcher, FALSE);
179 #if 0
180 ide_subprocess_launcher_push_argv (launcher, "gdbserver");
181 ide_subprocess_launcher_push_argv (launcher, "localhost:8888");
182 #endif
183 ide_subprocess_launcher_push_argv (launcher, PACKAGE_LIBEXECDIR"/gnome-builder-git");
184
185 self->supervisor = ide_subprocess_supervisor_new ();
186 ide_subprocess_supervisor_set_launcher (self->supervisor, launcher);
187
188 g_signal_connect_object (self->supervisor,
189 "spawned",
190 G_CALLBACK (gbp_git_client_subprocess_spawned),
191 self,
192 G_CONNECT_SWAPPED);
193
194 g_signal_connect_object (self->supervisor,
195 "exited",
196 G_CALLBACK (gbp_git_client_subprocess_exited),
197 self,
198 G_CONNECT_SWAPPED);
199
200 ide_object_unlock (IDE_OBJECT (self));
201 }
202
203 static void
gbp_git_client_class_init(GbpGitClientClass * klass)204 gbp_git_client_class_init (GbpGitClientClass *klass)
205 {
206 IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
207
208 i_object_class->destroy = gbp_git_client_destroy;
209 i_object_class->parent_set = gbp_git_client_parent_set;
210 }
211
212 static void
gbp_git_client_init(GbpGitClient * self)213 gbp_git_client_init (GbpGitClient *self)
214 {
215 }
216
217 GbpGitClient *
gbp_git_client_from_context(IdeContext * context)218 gbp_git_client_from_context (IdeContext *context)
219 {
220 GbpGitClient *ret;
221
222 g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
223 g_return_val_if_fail (!ide_object_in_destruction (IDE_OBJECT (context)), NULL);
224
225 if (!(ret = ide_context_peek_child_typed (context, GBP_TYPE_GIT_CLIENT)))
226 {
227 g_autoptr(GbpGitClient) client = NULL;
228
229 client = ide_object_ensure_child_typed (IDE_OBJECT (context), GBP_TYPE_GIT_CLIENT);
230 ret = ide_context_peek_child_typed (context, GBP_TYPE_GIT_CLIENT);
231 }
232
233 return ret;
234 }
235
236 static void
gbp_git_client_get_service_cb(GObject * object,GAsyncResult * result,gpointer user_data)237 gbp_git_client_get_service_cb (GObject *object,
238 GAsyncResult *result,
239 gpointer user_data)
240 {
241 GbpGitClient *self = (GbpGitClient *)object;
242 g_autoptr(IpcGitService) service = NULL;
243 g_autoptr(IdeTask) task = user_data;
244 g_autoptr(GError) error = NULL;
245
246 g_assert (GBP_IS_GIT_CLIENT (self));
247 g_assert (G_IS_ASYNC_RESULT (result));
248 g_assert (IDE_IS_TASK (task));
249
250 if (!(service = gbp_git_client_get_service_finish (self, result, &error)))
251 ide_task_return_error (task, g_steal_pointer (&error));
252 else
253 ide_task_return_object (task, g_steal_pointer (&service));
254 }
255
256 IpcGitService *
gbp_git_client_get_service(GbpGitClient * self,GCancellable * cancellable,GError ** error)257 gbp_git_client_get_service (GbpGitClient *self,
258 GCancellable *cancellable,
259 GError **error)
260 {
261 g_autoptr(IdeTask) task = NULL;
262 g_autoptr(GMainContext) gcontext = NULL;
263 IpcGitService *ret = NULL;
264
265 g_assert (GBP_IS_GIT_CLIENT (self));
266 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
267
268 ide_object_lock (IDE_OBJECT (self));
269 if (self->service != NULL)
270 ret = g_object_ref (self->service);
271 ide_object_unlock (IDE_OBJECT (self));
272
273 if (ret != NULL)
274 return g_steal_pointer (&ret);
275
276 task = ide_task_new (self, cancellable, NULL, NULL);
277 ide_task_set_source_tag (task, gbp_git_client_get_service);
278
279 gcontext = g_main_context_ref_thread_default ();
280
281 gbp_git_client_get_service_async (self,
282 cancellable,
283 gbp_git_client_get_service_cb,
284 g_object_ref (task));
285
286 while (!ide_task_get_completed (task))
287 g_main_context_iteration (gcontext, TRUE);
288
289 return ide_task_propagate_object (task, error);
290 }
291
292 void
gbp_git_client_get_service_async(GbpGitClient * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)293 gbp_git_client_get_service_async (GbpGitClient *self,
294 GCancellable *cancellable,
295 GAsyncReadyCallback callback,
296 gpointer user_data)
297 {
298 g_autoptr(IdeTask) task = NULL;
299
300 g_assert (GBP_IS_GIT_CLIENT (self));
301 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
302
303 ide_object_lock (IDE_OBJECT (self));
304
305 task = ide_task_new (self, cancellable, callback, user_data);
306 ide_task_set_source_tag (task, gbp_git_client_get_service_async);
307
308 switch (self->state)
309 {
310 case STATE_INITIAL:
311 self->state = STATE_SPAWNING;
312 g_queue_push_tail (&self->get_service, g_steal_pointer (&task));
313 ide_subprocess_supervisor_start (self->supervisor);
314 break;
315
316 case STATE_SPAWNING:
317 g_queue_push_tail (&self->get_service, g_steal_pointer (&task));
318 break;
319
320 case STATE_RUNNING:
321 ide_task_return_object (task, g_object_ref (self->service));
322 break;
323
324 case STATE_SHUTDOWN:
325 ide_task_return_new_error (task,
326 G_IO_ERROR,
327 G_IO_ERROR_CLOSED,
328 _("The client has been closed"));
329 break;
330
331 default:
332 g_assert_not_reached ();
333 break;
334 }
335
336 ide_object_unlock (IDE_OBJECT (self));
337 }
338
339 IpcGitService *
gbp_git_client_get_service_finish(GbpGitClient * self,GAsyncResult * result,GError ** error)340 gbp_git_client_get_service_finish (GbpGitClient *self,
341 GAsyncResult *result,
342 GError **error)
343 {
344 g_assert (GBP_IS_GIT_CLIENT (self));
345 g_assert (IDE_IS_TASK (result));
346
347 return ide_task_propagate_object (IDE_TASK (result), error);
348 }
349