Line | Branch | Exec | Source |
---|---|---|---|
1 | /* cc-object-storage.h | ||
2 | * | ||
3 | * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.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 2 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 | |||
19 | #define G_LOG_DOMAIN "cc-object-storage" | ||
20 | |||
21 | #include "cc-object-storage.h" | ||
22 | |||
23 | struct _CcObjectStorage | ||
24 | { | ||
25 | GObject parent_instance; | ||
26 | |||
27 | GHashTable *id_to_object; | ||
28 | }; | ||
29 | |||
30 |
6/7✓ Branch 0 taken 1 times.
✓ Branch 1 taken 43 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 43 times.
|
92 | G_DEFINE_TYPE (CcObjectStorage, cc_object_storage, G_TYPE_OBJECT) |
31 | |||
32 | /* Singleton instance */ | ||
33 | static CcObjectStorage *_instance = NULL; | ||
34 | |||
35 | /* GTask API to create a new D-Bus proxy */ | ||
36 | typedef struct | ||
37 | { | ||
38 | GBusType bus_type; | ||
39 | GDBusProxyFlags flags; | ||
40 | gchar *name; | ||
41 | gchar *path; | ||
42 | gchar *interface; | ||
43 | gboolean cached; | ||
44 | } TaskData; | ||
45 | |||
46 | static TaskData* | ||
47 | ✗ | task_data_new (GBusType bus_type, | |
48 | GDBusProxyFlags flags, | ||
49 | const gchar *name, | ||
50 | const gchar *path, | ||
51 | const gchar *interface) | ||
52 | { | ||
53 | ✗ | TaskData *data = g_slice_new (TaskData); | |
54 | ✗ | data->bus_type = bus_type; | |
55 | ✗ | data->flags =flags; | |
56 | ✗ | data->name = g_strdup (name); | |
57 | ✗ | data->path = g_strdup (path); | |
58 | ✗ | data->interface = g_strdup (interface); | |
59 | ✗ | data->cached = FALSE; | |
60 | |||
61 | ✗ | return data; | |
62 | } | ||
63 | |||
64 | static void | ||
65 | ✗ | task_data_free (TaskData *data) | |
66 | { | ||
67 | ✗ | g_free (data->name); | |
68 | ✗ | g_free (data->path); | |
69 | ✗ | g_free (data->interface); | |
70 | ✗ | g_slice_free (TaskData, data); | |
71 | ✗ | } | |
72 | |||
73 | static void | ||
74 | ✗ | create_dbus_proxy_in_thread_cb (GTask *task, | |
75 | gpointer source_object, | ||
76 | gpointer task_data, | ||
77 | GCancellable *cancellable) | ||
78 | { | ||
79 | ✗ | g_autoptr(GDBusProxy) proxy = NULL; | |
80 | ✗ | g_autoptr(GError) local_error = NULL; | |
81 | ✗ | TaskData *data = task_data; | |
82 | |||
83 | ✗ | proxy = g_dbus_proxy_new_for_bus_sync (data->bus_type, | |
84 | data->flags, | ||
85 | NULL, | ||
86 | ✗ | data->name, | |
87 | ✗ | data->path, | |
88 | ✗ | data->interface, | |
89 | cancellable, | ||
90 | &local_error); | ||
91 | |||
92 | ✗ | if (local_error) | |
93 | { | ||
94 | ✗ | g_task_return_error (task, g_steal_pointer (&local_error)); | |
95 | ✗ | return; | |
96 | } | ||
97 | |||
98 | ✗ | g_task_return_pointer (task, g_object_ref (g_steal_pointer (&proxy)), g_object_unref); | |
99 | } | ||
100 | |||
101 | static void | ||
102 | 11 | cc_object_storage_finalize (GObject *object) | |
103 | { | ||
104 | 11 | CcObjectStorage *self = (CcObjectStorage *)object; | |
105 | |||
106 | 11 | g_debug ("Destroying cached objects"); | |
107 | |||
108 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | g_clear_pointer (&self->id_to_object, g_hash_table_destroy); |
109 | |||
110 | 11 | G_OBJECT_CLASS (cc_object_storage_parent_class)->finalize (object); | |
111 | 11 | } | |
112 | |||
113 | static void | ||
114 | 1 | cc_object_storage_class_init (CcObjectStorageClass *klass) | |
115 | { | ||
116 | 1 | GObjectClass *object_class = G_OBJECT_CLASS (klass); | |
117 | |||
118 | 1 | object_class->finalize = cc_object_storage_finalize; | |
119 | 1 | } | |
120 | |||
121 | static void | ||
122 | 11 | cc_object_storage_init (CcObjectStorage *self) | |
123 | { | ||
124 | 11 | self->id_to_object = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); | |
125 | 11 | } | |
126 | |||
127 | /** | ||
128 | * cc_object_storage_has_object: | ||
129 | * @key: the unique string identifier of the object | ||
130 | * | ||
131 | * Checks whether there is an object associated with @key. | ||
132 | * | ||
133 | * Returns: %TRUE if the object is stored, %FALSE otherwise. | ||
134 | */ | ||
135 | gboolean | ||
136 | 11 | cc_object_storage_has_object (const gchar *key) | |
137 | { | ||
138 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
11 | g_assert (CC_IS_OBJECT_STORAGE (_instance)); |
139 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | g_assert (key != NULL); |
140 | |||
141 | 11 | return g_hash_table_contains (_instance->id_to_object, key); | |
142 | } | ||
143 | |||
144 | /** | ||
145 | * cc_object_storage_add_object: | ||
146 | * @key: the unique string identifier of the object | ||
147 | * @object: (type GObject): the object to be stored | ||
148 | * | ||
149 | * Adds @object to the object storage. It is a programming error to try to | ||
150 | * add an object that was already added. | ||
151 | * | ||
152 | * @object must be a GObject. | ||
153 | * | ||
154 | * Always check if the object is stored with cc_object_storage_has_object() | ||
155 | * before calling this function. | ||
156 | */ | ||
157 | void | ||
158 | 11 | cc_object_storage_add_object (const gchar *key, | |
159 | gpointer object) | ||
160 | { | ||
161 | /* Trying to add an object that was already added is a hard error. Each | ||
162 | * object must be added once, and only once, over the entire lifetime | ||
163 | * of the application. | ||
164 | */ | ||
165 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
11 | g_assert (CC_IS_OBJECT_STORAGE (_instance)); |
166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | g_assert (key != NULL); |
167 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | g_assert (G_IS_OBJECT (object)); |
168 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
11 | g_assert (!g_hash_table_contains (_instance->id_to_object, key)); |
169 | |||
170 | 11 | g_debug ("Adding object %s (%s → %p) to the storage", | |
171 | g_type_name (G_OBJECT_TYPE (object)), | ||
172 | key, | ||
173 | object); | ||
174 | |||
175 | 22 | g_hash_table_insert (_instance->id_to_object, g_strdup (key), g_object_ref (object)); | |
176 | 11 | } | |
177 | |||
178 | /** | ||
179 | * cc_object_storage_get_object: | ||
180 | * @key: the unique string identifier of the object | ||
181 | * | ||
182 | * Retrieves the object associated with @key. It is a programming error to | ||
183 | * try to retrieve an object before adding it. | ||
184 | * | ||
185 | * Always check if the object is stored with cc_object_storage_has_object() | ||
186 | * before calling this function. | ||
187 | * | ||
188 | * Returns: (transfer full): the GObject associated with @key. | ||
189 | */ | ||
190 | gpointer | ||
191 | 11 | cc_object_storage_get_object (const gchar *key) | |
192 | { | ||
193 | /* Trying to peek an object that was not yet added is a hard error. Users | ||
194 | * of this API need to first check if the object is available with | ||
195 | * cc_object_storage_has_object(). | ||
196 | */ | ||
197 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
11 | g_assert (CC_IS_OBJECT_STORAGE (_instance)); |
198 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | g_assert (key != NULL); |
199 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
|
11 | g_assert (g_hash_table_contains (_instance->id_to_object, key)); |
200 | |||
201 | 11 | return g_object_ref (g_hash_table_lookup (_instance->id_to_object, key)); | |
202 | } | ||
203 | |||
204 | /** | ||
205 | * cc_object_storage_create_dbus_proxy_sync: | ||
206 | * @name: the D-Bus name | ||
207 | * @flags: the D-Bus proxy flags | ||
208 | * @path: the D-Bus object path | ||
209 | * @interface: the D-Bus interface name | ||
210 | * @cancellable: (nullable): #GCancellable to cancel the operation | ||
211 | * @error: (nullable): return location for a #GError | ||
212 | * | ||
213 | * Synchronously create a #GDBusProxy with @name, @path and @interface, | ||
214 | * stores it in the cache, and returns the newly created proxy. | ||
215 | * | ||
216 | * If a proxy with that signature is already created, it will be used | ||
217 | * instead of creating a new one. | ||
218 | * | ||
219 | * Returns: (transfer full)(nullable): the new #GDBusProxy. | ||
220 | */ | ||
221 | gpointer | ||
222 | ✗ | cc_object_storage_create_dbus_proxy_sync (GBusType bus_type, | |
223 | GDBusProxyFlags flags, | ||
224 | const gchar *name, | ||
225 | const gchar *path, | ||
226 | const gchar *interface, | ||
227 | GCancellable *cancellable, | ||
228 | GError **error) | ||
229 | { | ||
230 | ✗ | g_autoptr(GDBusProxy) proxy = NULL; | |
231 | ✗ | g_autoptr(GError) local_error = NULL; | |
232 | ✗ | g_autofree gchar *key = NULL; | |
233 | |||
234 | ✗ | g_assert (CC_IS_OBJECT_STORAGE (_instance)); | |
235 | ✗ | g_assert (name && *name); | |
236 | ✗ | g_assert (path && *path); | |
237 | ✗ | g_assert (interface && *interface); | |
238 | ✗ | g_assert (!error || !*error); | |
239 | |||
240 | ✗ | key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", name, path, interface); | |
241 | |||
242 | ✗ | g_debug ("Creating D-Bus proxy for %s", key); | |
243 | |||
244 | /* Check if a DBus proxy with that signature is already available; if it is, | ||
245 | * return that instead of a new one. | ||
246 | */ | ||
247 | ✗ | if (g_hash_table_contains (_instance->id_to_object, key)) | |
248 | ✗ | return cc_object_storage_get_object (key); | |
249 | |||
250 | ✗ | proxy = g_dbus_proxy_new_for_bus_sync (bus_type, | |
251 | flags, | ||
252 | NULL, | ||
253 | name, | ||
254 | path, | ||
255 | interface, | ||
256 | cancellable, | ||
257 | &local_error); | ||
258 | |||
259 | ✗ | if (local_error) | |
260 | { | ||
261 | ✗ | g_propagate_error (error, g_steal_pointer (&local_error)); | |
262 | ✗ | return NULL; | |
263 | } | ||
264 | |||
265 | /* Store the newly created D-Bus proxy */ | ||
266 | ✗ | cc_object_storage_add_object (key, proxy); | |
267 | |||
268 | ✗ | return g_steal_pointer (&proxy); | |
269 | } | ||
270 | |||
271 | |||
272 | /** | ||
273 | * cc_object_storage_create_dbus_proxy: | ||
274 | * @name: the D-Bus name | ||
275 | * @flags: the D-Bus proxy flags | ||
276 | * @path: the D-Bus object path | ||
277 | * @interface: the D-Bus interface name | ||
278 | * @cancellable: (nullable): #GCancellable to cancel the operation | ||
279 | * @callback: callback for when the async operation is finished | ||
280 | * @user_data: user data for @callback | ||
281 | * | ||
282 | * Asynchronously create a #GDBusProxy with @name, @path and @interface. | ||
283 | * | ||
284 | * If a proxy with that signature is already created, it will be used instead of | ||
285 | * creating a new one. | ||
286 | * | ||
287 | * It is a programming error to create the an identical proxy while asynchronously | ||
288 | * creating one. Not cancelling this operation will result in an assertion failure | ||
289 | * when calling cc_object_storage_create_dbus_proxy_finish(). | ||
290 | */ | ||
291 | void | ||
292 | ✗ | cc_object_storage_create_dbus_proxy (GBusType bus_type, | |
293 | GDBusProxyFlags flags, | ||
294 | const gchar *name, | ||
295 | const gchar *path, | ||
296 | const gchar *interface, | ||
297 | GCancellable *cancellable, | ||
298 | GAsyncReadyCallback callback, | ||
299 | gpointer user_data) | ||
300 | { | ||
301 | ✗ | g_autoptr(GTask) task = NULL; | |
302 | ✗ | g_autofree gchar *key = NULL; | |
303 | ✗ | TaskData *data = NULL; | |
304 | |||
305 | ✗ | g_assert (CC_IS_OBJECT_STORAGE (_instance)); | |
306 | ✗ | g_assert (name && *name); | |
307 | ✗ | g_assert (path && *path); | |
308 | ✗ | g_assert (interface && *interface); | |
309 | ✗ | g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); | |
310 | |||
311 | ✗ | data = task_data_new (bus_type, flags, name, path, interface); | |
312 | |||
313 | ✗ | task = g_task_new (_instance, cancellable, callback, user_data); | |
314 | ✗ | g_task_set_source_tag (task, cc_object_storage_create_dbus_proxy); | |
315 | ✗ | g_task_set_task_data (task, data, (GDestroyNotify) task_data_free); | |
316 | |||
317 | /* Check if the D-Bus proxy is already created */ | ||
318 | ✗ | key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", name, path, interface); | |
319 | |||
320 | ✗ | g_debug ("Asynchronously creating D-Bus proxy for %s", key); | |
321 | |||
322 | ✗ | if (g_hash_table_contains (_instance->id_to_object, key)) | |
323 | { | ||
324 | /* Mark this GTask as already cached, so we can call the right assertions | ||
325 | * on the callback | ||
326 | * */ | ||
327 | ✗ | data->cached = TRUE; | |
328 | |||
329 | ✗ | g_debug ("Found in cache the D-Bus proxy %s", key); | |
330 | |||
331 | ✗ | g_task_return_pointer (task, cc_object_storage_get_object (key), g_object_unref); | |
332 | ✗ | return; | |
333 | } | ||
334 | |||
335 | ✗ | g_task_run_in_thread (task, create_dbus_proxy_in_thread_cb); | |
336 | } | ||
337 | |||
338 | /** | ||
339 | * cc_object_storage_create_dbus_proxy_finish: | ||
340 | * @result: | ||
341 | * @error: (nullable): return location for a #GError | ||
342 | * | ||
343 | * Finishes a D-Bus proxy creation started by cc_object_storage_create_dbus_proxy(). | ||
344 | * | ||
345 | * Synchronously create a #GDBusProxy with @name, @path and @interface, | ||
346 | * stores it in the cache, and returns the newly created proxy. | ||
347 | * | ||
348 | * If a proxy with that signature is already created, it will be used | ||
349 | * instead of creating a new one. | ||
350 | * | ||
351 | * Returns: (transfer full)(nullable): the new #GDBusProxy. | ||
352 | */ | ||
353 | gpointer | ||
354 | ✗ | cc_object_storage_create_dbus_proxy_finish (GAsyncResult *result, | |
355 | GError **error) | ||
356 | { | ||
357 | ✗ | g_autoptr(GDBusProxy) proxy = NULL; | |
358 | ✗ | g_autoptr(GError) local_error = NULL; | |
359 | ✗ | g_autofree gchar *key = NULL; | |
360 | TaskData *task_data; | ||
361 | GTask *task; | ||
362 | |||
363 | ✗ | task = G_TASK (result); | |
364 | |||
365 | ✗ | g_assert (task && G_TASK (result)); | |
366 | ✗ | g_assert (!error || !*error); | |
367 | |||
368 | ✗ | task_data = g_task_get_task_data (task); | |
369 | ✗ | g_assert (task_data != NULL); | |
370 | |||
371 | ✗ | key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", | |
372 | task_data->name, | ||
373 | task_data->path, | ||
374 | task_data->interface); | ||
375 | |||
376 | ✗ | g_debug ("Finished creating D-Bus proxy for %s", key); | |
377 | |||
378 | /* Retrieve the newly created proxy */ | ||
379 | ✗ | proxy = g_task_propagate_pointer (task, &local_error); | |
380 | |||
381 | /* If the proxy is not cached, do the normal caching routine */ | ||
382 | ✗ | if (local_error) | |
383 | { | ||
384 | ✗ | g_propagate_error (error, g_steal_pointer (&local_error)); | |
385 | ✗ | return NULL; | |
386 | } | ||
387 | |||
388 | /* Either we have the object stored right when trying to create it - in which case, | ||
389 | * task_data->cached == TRUE and cc_object_storage_has_object (key) == TRUE - or we | ||
390 | * didn't have a cached proxy before, and we shouldn't have it now. | ||
391 | * | ||
392 | * This is to force consumers of this code to *never* try to create the same D-Bus | ||
393 | * proxy asynchronously multiple times. Trying to do so is considered a programming | ||
394 | * error. | ||
395 | */ | ||
396 | ✗ | g_assert (task_data->cached == cc_object_storage_has_object (key)); | |
397 | |||
398 | /* If the proxy is already cached, destroy the newly created and used the cached proxy | ||
399 | * instead. | ||
400 | */ | ||
401 | ✗ | if (cc_object_storage_has_object (key)) | |
402 | ✗ | return cc_object_storage_get_object (key); | |
403 | |||
404 | /* Store the newly created D-Bus proxy */ | ||
405 | ✗ | cc_object_storage_add_object (key, proxy); | |
406 | |||
407 | ✗ | return g_steal_pointer (&proxy); | |
408 | } | ||
409 | |||
410 | /** | ||
411 | * cc_object_storage_initialize: | ||
412 | * | ||
413 | * Initializes the single CcObjectStorage. This must be called only once, | ||
414 | * and before every other method of this object. | ||
415 | */ | ||
416 | void | ||
417 | 11 | cc_object_storage_initialize (void) | |
418 | { | ||
419 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | g_assert (_instance == NULL); |
420 | |||
421 |
3/6✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 11 times.
✗ Branch 6 not taken.
|
11 | if (g_once_init_enter (&_instance)) |
422 | { | ||
423 | 11 | CcObjectStorage *instance = g_object_new (CC_TYPE_OBJECT_STORAGE, NULL); | |
424 | |||
425 | 11 | g_debug ("Initializing object storage"); | |
426 | |||
427 | 11 | g_once_init_leave (&_instance, instance); | |
428 | } | ||
429 | 11 | } | |
430 | |||
431 | /** | ||
432 | * cc_object_storage_destroy: | ||
433 | * | ||
434 | * Destroys the instance of #CcObjectStorage. This must be called only | ||
435 | * once during the application lifetime. It is a programming error to | ||
436 | * call this function multiple times | ||
437 | */ | ||
438 | void | ||
439 | 11 | cc_object_storage_destroy (void) | |
440 | { | ||
441 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
11 | g_assert (_instance != NULL); |
442 | |||
443 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | g_clear_object (&_instance); |
444 | 11 | } | |
445 |