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