Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2019 Canonical Limited
3 : : *
4 : : * SPDX-License-Identifier: LGPL-2.1-or-later
5 : : *
6 : : * This library is free software; you can redistribute it and/or
7 : : * modify it under the terms of the GNU Lesser General Public
8 : : * License as published by the Free Software Foundation; either
9 : : * version 2.1 of the License, or (at your option) any later version.
10 : : *
11 : : * This library is distributed in the hope that it will be useful,
12 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : : * Lesser General Public License for more details.
15 : : *
16 : : * You should have received a copy of the GNU Lesser General
17 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 : : *
19 : : * Authors: James Henstridge <james.henstridge@canonical.com>
20 : : */
21 : :
22 : : /* A stub implementation of xdg-document-portal covering enough to
23 : : * support g_document_portal_add_documents */
24 : :
25 : : #include <glib.h>
26 : : #include <gio/gio.h>
27 : : #include <gio/gunixfdlist.h>
28 : :
29 : : #include "fake-document-portal.h"
30 : : #include "fake-document-portal-generated.h"
31 : :
32 : : struct _GFakeDocumentPortalThread
33 : : {
34 : : GObject parent_instance;
35 : :
36 : : char *address; /* (not nullable) */
37 : : GCancellable *cancellable; /* (not nullable) (owned) */
38 : : GThread *thread; /* (not nullable) (owned) */
39 : : GCond cond; /* (mutex mutex) */
40 : : GMutex mutex;
41 : : gboolean ready; /* (mutex mutex) */
42 : : };
43 : :
44 : 14 : G_DEFINE_FINAL_TYPE (GFakeDocumentPortalThread, g_fake_document_portal_thread, G_TYPE_OBJECT)
45 : :
46 : : static void g_fake_document_portal_thread_finalize (GObject *object);
47 : :
48 : : static void
49 : 1 : g_fake_document_portal_thread_class_init (GFakeDocumentPortalThreadClass *klass)
50 : : {
51 : 1 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
52 : :
53 : 1 : gobject_class->finalize = g_fake_document_portal_thread_finalize;
54 : 1 : }
55 : :
56 : : static void
57 : 2 : g_fake_document_portal_thread_init (GFakeDocumentPortalThread *self)
58 : : {
59 : 2 : self->cancellable = g_cancellable_new ();
60 : 2 : g_cond_init (&self->cond);
61 : 2 : g_mutex_init (&self->mutex);
62 : 2 : }
63 : :
64 : : static void
65 : 2 : g_fake_document_portal_thread_finalize (GObject *object)
66 : : {
67 : 2 : GFakeDocumentPortalThread *self = G_FAKE_DOCUMENT_PORTAL_THREAD (object);
68 : :
69 : 2 : g_assert (self->thread == NULL); /* should already have been joined */
70 : :
71 : 2 : g_mutex_clear (&self->mutex);
72 : 2 : g_cond_clear (&self->cond);
73 : 2 : g_clear_object (&self->cancellable);
74 : 2 : g_clear_pointer (&self->address, g_free);
75 : :
76 : 2 : G_OBJECT_CLASS (g_fake_document_portal_thread_parent_class)->finalize (object);
77 : 2 : }
78 : :
79 : : static gboolean
80 : 2 : on_handle_get_mount_point (FakeDocuments *object,
81 : : GDBusMethodInvocation *invocation,
82 : : gpointer user_data)
83 : : {
84 : 2 : fake_documents_complete_get_mount_point (object,
85 : : invocation,
86 : : "/document-portal");
87 : 2 : return TRUE;
88 : : }
89 : :
90 : : static gboolean
91 : 1 : on_handle_add_full (FakeDocuments *object,
92 : : GDBusMethodInvocation *invocation,
93 : : GUnixFDList *o_path_fds,
94 : : guint flags,
95 : : const gchar *app_id,
96 : : const gchar * const *permissions,
97 : : gpointer user_data)
98 : : {
99 : 1 : const gchar **doc_ids = NULL;
100 : 1 : GVariant *extra_out = NULL;
101 : : gsize length, i;
102 : :
103 : 1 : if (o_path_fds != NULL)
104 : 1 : length = g_unix_fd_list_get_length (o_path_fds);
105 : : else
106 : 0 : length = 0;
107 : :
108 : 1 : doc_ids = g_new0 (const gchar *, length + 1 /* NULL terminator */);
109 : 2 : for (i = 0; i < length; i++)
110 : : {
111 : 1 : doc_ids[i] = "document-id";
112 : : }
113 : 1 : extra_out = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
114 : :
115 : 1 : fake_documents_complete_add_full (object,
116 : : invocation,
117 : : NULL,
118 : : doc_ids,
119 : : extra_out);
120 : :
121 : 1 : g_free (doc_ids);
122 : :
123 : 1 : return TRUE;
124 : : }
125 : :
126 : : static void
127 : 2 : on_name_acquired (GDBusConnection *connection,
128 : : const gchar *name,
129 : : gpointer user_data)
130 : : {
131 : 2 : GFakeDocumentPortalThread *self = G_FAKE_DOCUMENT_PORTAL_THREAD (user_data);
132 : :
133 : 2 : g_test_message ("Acquired the name %s", name);
134 : :
135 : 2 : g_mutex_lock (&self->mutex);
136 : 2 : self->ready = TRUE;
137 : 2 : g_cond_signal (&self->cond);
138 : 2 : g_mutex_unlock (&self->mutex);
139 : 2 : }
140 : :
141 : : static void
142 : 0 : on_name_lost (GDBusConnection *connection,
143 : : const gchar *name,
144 : : gpointer user_data)
145 : : {
146 : 0 : g_test_message ("Lost the name %s", name);
147 : 0 : }
148 : :
149 : : static gboolean
150 : 2 : cancelled_cb (GCancellable *cancellable,
151 : : gpointer user_data)
152 : : {
153 : 2 : g_test_message ("fake-document-portal cancelled");
154 : 2 : return G_SOURCE_CONTINUE;
155 : : }
156 : :
157 : : static gpointer
158 : 2 : fake_document_portal_thread_cb (gpointer user_data)
159 : : {
160 : 2 : GFakeDocumentPortalThread *self = G_FAKE_DOCUMENT_PORTAL_THREAD (user_data);
161 : 2 : GMainContext *context = NULL;
162 : 2 : GDBusConnection *connection = NULL;
163 : 2 : GSource *source = NULL;
164 : : guint id;
165 : 2 : FakeDocuments *interface = NULL;
166 : 2 : GError *local_error = NULL;
167 : :
168 : 2 : context = g_main_context_new ();
169 : 2 : g_main_context_push_thread_default (context);
170 : :
171 : 2 : connection = g_dbus_connection_new_for_address_sync (self->address,
172 : : G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
173 : : G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
174 : : NULL,
175 : : self->cancellable,
176 : : &local_error);
177 : 2 : g_assert_no_error (local_error);
178 : :
179 : : /* Listen for cancellation. The source will wake up the context iteration
180 : : * which can then re-check its exit condition below. */
181 : 2 : source = g_cancellable_source_new (self->cancellable);
182 : 2 : g_source_set_callback (source, G_SOURCE_FUNC (cancelled_cb), NULL, NULL);
183 : 2 : g_source_attach (source, context);
184 : 2 : g_source_unref (source);
185 : :
186 : : /* Set up the interface */
187 : 2 : g_test_message ("Acquired a message bus connection");
188 : :
189 : 2 : interface = fake_documents_skeleton_new ();
190 : 2 : g_signal_connect (interface,
191 : : "handle-get-mount-point",
192 : : G_CALLBACK (on_handle_get_mount_point),
193 : : NULL);
194 : 2 : g_signal_connect (interface,
195 : : "handle-add-full",
196 : : G_CALLBACK (on_handle_add_full),
197 : : NULL);
198 : :
199 : 2 : g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (interface),
200 : : connection,
201 : : "/org/freedesktop/portal/documents",
202 : : &local_error);
203 : 2 : g_assert_no_error (local_error);
204 : :
205 : : /* Own the portal name */
206 : 2 : id = g_bus_own_name_on_connection (connection,
207 : : "org.freedesktop.portal.Documents",
208 : : G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
209 : : G_BUS_NAME_OWNER_FLAGS_REPLACE,
210 : : on_name_acquired,
211 : : on_name_lost,
212 : : self,
213 : : NULL);
214 : :
215 : 11 : while (!g_cancellable_is_cancelled (self->cancellable))
216 : 9 : g_main_context_iteration (context, TRUE);
217 : :
218 : 2 : g_bus_unown_name (id);
219 : 2 : g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (interface));
220 : 2 : g_clear_object (&interface);
221 : 2 : g_clear_object (&connection);
222 : 2 : g_main_context_pop_thread_default (context);
223 : 2 : g_clear_pointer (&context, g_main_context_unref);
224 : :
225 : 2 : return NULL;
226 : : }
227 : :
228 : : /*
229 : : * Create a new #GFakeDocumentPortalThread. The thread isn’t started until
230 : : * g_fake_document_portal_thread_run() is called on it.
231 : : *
232 : : * Returns: (transfer full): the new fake document portal wrapper
233 : : */
234 : : GFakeDocumentPortalThread *
235 : 2 : g_fake_document_portal_thread_new (const char *address)
236 : : {
237 : 2 : GFakeDocumentPortalThread *self = g_object_new (G_TYPE_FAKE_DOCUMENT_PORTAL_THREAD, NULL);
238 : 2 : self->address = g_strdup (address);
239 : 2 : return g_steal_pointer (&self);
240 : : }
241 : :
242 : : /*
243 : : * Start a worker thread which will run a fake
244 : : * `org.freedesktop.portal.Documents` portal on the bus at @address. This is
245 : : * intended to be used with #GTestDBus to mock up a portal from within a unit
246 : : * test process, rather than relying on D-Bus activation of a mock portal
247 : : * subprocess.
248 : : *
249 : : * It blocks until the thread has owned its D-Bus name and is ready to handle
250 : : * requests.
251 : : */
252 : : void
253 : 2 : g_fake_document_portal_thread_run (GFakeDocumentPortalThread *self)
254 : : {
255 : 2 : g_return_if_fail (G_IS_FAKE_DOCUMENT_PORTAL_THREAD (self));
256 : 2 : g_return_if_fail (self->thread == NULL);
257 : :
258 : 2 : self->thread = g_thread_new ("fake-document-portal", fake_document_portal_thread_cb, self);
259 : :
260 : : /* Block until the thread is ready. */
261 : 2 : g_mutex_lock (&self->mutex);
262 : 4 : while (!self->ready)
263 : 2 : g_cond_wait (&self->cond, &self->mutex);
264 : 2 : g_mutex_unlock (&self->mutex);
265 : : }
266 : :
267 : : /* Stop and join a worker thread started with fake_document_portal_thread_run().
268 : : * Blocks until the thread has stopped and joined.
269 : : *
270 : : * Once this has been called, it’s safe to drop the final reference on @self. */
271 : : void
272 : 2 : g_fake_document_portal_thread_stop (GFakeDocumentPortalThread *self)
273 : : {
274 : 2 : g_return_if_fail (G_IS_FAKE_DOCUMENT_PORTAL_THREAD (self));
275 : 2 : g_return_if_fail (self->thread != NULL);
276 : :
277 : 2 : g_cancellable_cancel (self->cancellable);
278 : 2 : g_thread_join (g_steal_pointer (&self->thread));
279 : : }
|