Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2017, 2018 Endless Mobile, Inc.
4 : : *
5 : : * This library is free software; you can redistribute it and/or
6 : : * modify it under the terms of the GNU Lesser General Public
7 : : * License as published by the Free Software Foundation; either
8 : : * version 2.1 of the License, or (at your option) any later version.
9 : : *
10 : : * This library 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 GNU
13 : : * Lesser General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU Lesser General Public
16 : : * License along with this library; if not, write to the Free Software
17 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 : : *
19 : : * Authors:
20 : : * - Philip Withnall <withnall@endlessm.com>
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include <errno.h>
26 : : #include <glib.h>
27 : : #include <glib-unix.h>
28 : : #include <glib/gi18n-lib.h>
29 : : #include <gio/gio.h>
30 : : #include <libmogwai-schedule/schedule-entry.h>
31 : : #include <libmogwai-schedule/schedule-entry-interface.h>
32 : : #include <libmogwai-schedule/schedule-service.h>
33 : : #include <libmogwai-schedule/scheduler.h>
34 : : #include <libmogwai-schedule/scheduler-interface.h>
35 : : #include <string.h>
36 : :
37 : :
38 : : static void mws_schedule_service_constructed (GObject *object);
39 : : static void mws_schedule_service_dispose (GObject *object);
40 : : static void mws_schedule_service_get_property (GObject *object,
41 : : guint property_id,
42 : : GValue *value,
43 : : GParamSpec *pspec);
44 : : static void mws_schedule_service_set_property (GObject *object,
45 : : guint property_id,
46 : : const GValue *value,
47 : : GParamSpec *pspec);
48 : :
49 : : static gchar **mws_schedule_service_entry_enumerate (GDBusConnection *connection,
50 : : const gchar *sender,
51 : : const gchar *object_path,
52 : : gpointer user_data);
53 : : static GDBusInterfaceInfo **mws_schedule_service_entry_introspect (GDBusConnection *connection,
54 : : const gchar *sender,
55 : : const gchar *object_path,
56 : : const gchar *node,
57 : : gpointer user_data);
58 : : static const GDBusInterfaceVTable *mws_schedule_service_entry_dispatch (GDBusConnection *connection,
59 : : const gchar *sender,
60 : : const gchar *object_path,
61 : : const gchar *interface_name,
62 : : const gchar *node,
63 : : gpointer *out_user_data,
64 : : gpointer user_data);
65 : : static void mws_schedule_service_entry_method_call (GDBusConnection *connection,
66 : : const gchar *sender,
67 : : const gchar *object_path,
68 : : const gchar *interface_name,
69 : : const gchar *method_name,
70 : : GVariant *parameters,
71 : : GDBusMethodInvocation *invocation,
72 : : gpointer user_data);
73 : :
74 : : static void mws_schedule_service_entry_properties_get (MwsScheduleService *self,
75 : : MwsScheduleEntry *entry,
76 : : GDBusConnection *connection,
77 : : const gchar *sender,
78 : : GVariant *parameters,
79 : : GDBusMethodInvocation *invocation);
80 : : static void mws_schedule_service_entry_properties_set (MwsScheduleService *self,
81 : : MwsScheduleEntry *entry,
82 : : GDBusConnection *connection,
83 : : const gchar *sender,
84 : : GVariant *parameters,
85 : : GDBusMethodInvocation *invocation);
86 : : static void mws_schedule_service_entry_properties_get_all (MwsScheduleService *self,
87 : : MwsScheduleEntry *entry,
88 : : GDBusConnection *connection,
89 : : const gchar *sender,
90 : : GVariant *parameters,
91 : : GDBusMethodInvocation *invocation);
92 : :
93 : : static void mws_schedule_service_entry_remove (MwsScheduleService *self,
94 : : MwsScheduleEntry *entry,
95 : : GDBusConnection *connection,
96 : : const gchar *sender,
97 : : GVariant *parameters,
98 : : GDBusMethodInvocation *invocation);
99 : :
100 : : static void mws_schedule_service_scheduler_method_call (GDBusConnection *connection,
101 : : const gchar *sender,
102 : : const gchar *object_path,
103 : : const gchar *interface_name,
104 : : const gchar *method_name,
105 : : GVariant *parameters,
106 : : GDBusMethodInvocation *invocation,
107 : : gpointer user_data);
108 : :
109 : : static void mws_schedule_service_scheduler_properties_get (MwsScheduleService *self,
110 : : GDBusConnection *connection,
111 : : const gchar *sender,
112 : : GVariant *parameters,
113 : : GDBusMethodInvocation *invocation);
114 : : static void mws_schedule_service_scheduler_properties_set (MwsScheduleService *self,
115 : : GDBusConnection *connection,
116 : : const gchar *sender,
117 : : GVariant *parameters,
118 : : GDBusMethodInvocation *invocation);
119 : : static void mws_schedule_service_scheduler_properties_get_all (MwsScheduleService *self,
120 : : GDBusConnection *connection,
121 : : const gchar *sender,
122 : : GVariant *parameters,
123 : : GDBusMethodInvocation *invocation);
124 : : static void mws_schedule_service_scheduler_schedule_entries (MwsScheduleService *self,
125 : : GDBusConnection *connection,
126 : : const gchar *sender,
127 : : GVariant *parameters,
128 : : GDBusMethodInvocation *invocation);
129 : : static void mws_schedule_service_scheduler_hold (MwsScheduleService *self,
130 : : GDBusConnection *connection,
131 : : const gchar *sender,
132 : : GVariant *parameters,
133 : : GDBusMethodInvocation *invocation);
134 : : static void mws_schedule_service_scheduler_release (MwsScheduleService *self,
135 : : GDBusConnection *connection,
136 : : const gchar *sender,
137 : : GVariant *parameters,
138 : : GDBusMethodInvocation *invocation);
139 : :
140 : : static gboolean mws_schedule_service_hold (MwsScheduleService *self,
141 : : const gchar *sender,
142 : : const gchar *reason,
143 : : GError **error);
144 : : static gboolean mws_schedule_service_release (MwsScheduleService *self,
145 : : const gchar *sender,
146 : : GError **error);
147 : :
148 : : static void entries_changed_cb (MwsScheduler *scheduler,
149 : : GPtrArray *added,
150 : : GPtrArray *removed,
151 : : gpointer user_data);
152 : : static void active_entries_changed_cb (MwsScheduler *scheduler,
153 : : GPtrArray *added,
154 : : GPtrArray *removed,
155 : : gpointer user_data);
156 : : static void allow_downloads_changed_cb (GObject *obj,
157 : : GParamSpec *pspec,
158 : : gpointer user_data);
159 : : static void entry_notify_cb (GObject *obj,
160 : : GParamSpec *pspec,
161 : : gpointer user_data);
162 : : static void peer_vanished_cb (MwsPeerManager *manager,
163 : : const gchar *name,
164 : : gpointer user_data);
165 : :
166 : : static const GDBusErrorEntry scheduler_error_map[] =
167 : : {
168 : : { MWS_SCHEDULER_ERROR_FULL, "com.endlessm.DownloadManager1.Scheduler.Error.Full" },
169 : : { MWS_SCHEDULER_ERROR_IDENTIFYING_PEER,
170 : : "com.endlessm.DownloadManager1.Scheduler.Error.IdentifyingPeer" },
171 : : { MWS_SCHEDULER_ERROR_INVALID_PARAMETERS,
172 : : "org.freedesktop.DBus.Error.InvalidArgs" },
173 : : };
174 : : G_STATIC_ASSERT (G_N_ELEMENTS (scheduler_error_map) == MWS_SCHEDULER_N_ERRORS);
175 : : G_STATIC_ASSERT (G_N_ELEMENTS (scheduler_error_map) == G_N_ELEMENTS (scheduler_errors) + 1 /* o.fd.D.E.InvalidArgs */);
176 : :
177 : : /**
178 : : * MwsScheduleService:
179 : : *
180 : : * An implementation of a D-Bus interface to expose the download scheduler and
181 : : * all its schedule entries on the bus. This will expose all the necessary
182 : : * objects on the bus for peers to interact with them, and hooks them up to
183 : : * internal state management using #MwsScheduleService:scheduler.
184 : : *
185 : : * Since: 0.1.0
186 : : */
187 : : struct _MwsScheduleService
188 : : {
189 : : GObject parent;
190 : :
191 : : GDBusConnection *connection; /* (owned) */
192 : : gchar *object_path; /* (owned) */
193 : : guint entry_subtree_id;
194 : :
195 : : /* Used to cancel any pending operations when the object is unregistered. */
196 : : GCancellable *cancellable; /* (owned) */
197 : :
198 : : MwsScheduler *scheduler; /* (owned) */
199 : :
200 : : /* Maps D-Bus unique names to their reasons for holding the service open,
201 : : * provided in calls to Hold(). */
202 : : GHashTable *hold_reasons; /* (owned) (element-type utf8 utf8) */
203 : : };
204 : :
205 : : typedef enum
206 : : {
207 : : PROP_CONNECTION = 1,
208 : : PROP_OBJECT_PATH,
209 : : PROP_SCHEDULER,
210 : : PROP_BUSY,
211 : : } MwsScheduleServiceProperty;
212 : :
213 [ + + + - : 163 : G_DEFINE_TYPE (MwsScheduleService, mws_schedule_service, G_TYPE_OBJECT)
+ + ]
214 : :
215 : : static void
216 : 1 : mws_schedule_service_class_init (MwsScheduleServiceClass *klass)
217 : : {
218 : 1 : GObjectClass *object_class = (GObjectClass *) klass;
219 : 1 : GParamSpec *props[PROP_BUSY + 1] = { NULL, };
220 : :
221 : 1 : object_class->constructed = mws_schedule_service_constructed;
222 : 1 : object_class->dispose = mws_schedule_service_dispose;
223 : 1 : object_class->get_property = mws_schedule_service_get_property;
224 : 1 : object_class->set_property = mws_schedule_service_set_property;
225 : :
226 : : /**
227 : : * MwsScheduleService:connection:
228 : : *
229 : : * D-Bus connection to export objects on.
230 : : *
231 : : * Since: 0.1.0
232 : : */
233 : 1 : props[PROP_CONNECTION] =
234 : 1 : g_param_spec_object ("connection", "Connection",
235 : : "D-Bus connection to export objects on.",
236 : : G_TYPE_DBUS_CONNECTION,
237 : : G_PARAM_READWRITE |
238 : : G_PARAM_CONSTRUCT_ONLY |
239 : : G_PARAM_STATIC_STRINGS);
240 : :
241 : : /**
242 : : * MwsScheduleService:object-path:
243 : : *
244 : : * Object path to root all exported objects at. If this does not end in a
245 : : * slash, one will be added.
246 : : *
247 : : * Since: 0.1.0
248 : : */
249 : 1 : props[PROP_OBJECT_PATH] =
250 : 1 : g_param_spec_string ("object-path", "Object Path",
251 : : "Object path to root all exported objects at.",
252 : : "/",
253 : : G_PARAM_READWRITE |
254 : : G_PARAM_CONSTRUCT_ONLY |
255 : : G_PARAM_STATIC_STRINGS);
256 : :
257 : : /**
258 : : * MwsScheduleService:scheduler:
259 : : *
260 : : * Scheduler to contain and order all schedule entries.
261 : : *
262 : : * Since: 0.1.0
263 : : */
264 : 1 : props[PROP_SCHEDULER] =
265 : 1 : g_param_spec_object ("scheduler", "Scheduler",
266 : : "Scheduler to contain and order all schedule entries.",
267 : : MWS_TYPE_SCHEDULER,
268 : : G_PARAM_READWRITE |
269 : : G_PARAM_CONSTRUCT_ONLY |
270 : : G_PARAM_STATIC_STRINGS);
271 : :
272 : : /**
273 : : * MwsScheduleService:busy:
274 : : *
275 : : * %TRUE if the D-Bus API is busy; for example if there are currently any
276 : : * schedule entries exposed on the bus, or if a client has called Hold()
277 : : * without yet calling Release().
278 : : *
279 : : * Since: 0.1.0
280 : : */
281 : 1 : props[PROP_BUSY] =
282 : 1 : g_param_spec_boolean ("busy", "Busy",
283 : : "%TRUE if the D-Bus API is busy.",
284 : : FALSE,
285 : : G_PARAM_READABLE |
286 : : G_PARAM_STATIC_STRINGS);
287 : :
288 : 1 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
289 : :
290 : : /* Error domain registration for D-Bus. We do this here, rather than in a
291 : : * #GOnce section in mws_scheduler_error_quark(), to avoid spreading the D-Bus
292 : : * code outside this file. */
293 [ + + ]: 4 : for (gsize i = 0; i < G_N_ELEMENTS (scheduler_error_map); i++)
294 : 3 : g_dbus_error_register_error (MWS_SCHEDULER_ERROR,
295 : 3 : scheduler_error_map[i].error_code,
296 : 3 : scheduler_error_map[i].dbus_error_name);
297 : 1 : }
298 : :
299 : : static void
300 : 11 : mws_schedule_service_init (MwsScheduleService *self)
301 : : {
302 : 11 : self->cancellable = g_cancellable_new ();
303 : 11 : self->hold_reasons = g_hash_table_new_full (g_str_hash, g_str_equal,
304 : : g_free, g_free);
305 : 11 : }
306 : :
307 : : static GPtrArray *
308 : 21 : hash_table_get_values_as_ptr_array (GHashTable *table)
309 : : {
310 : : GHashTableIter iter;
311 : : gpointer value;
312 : :
313 : 42 : g_autoptr(GPtrArray) values = g_ptr_array_new_with_free_func (NULL);
314 : 21 : g_hash_table_iter_init (&iter, table);
315 [ + + ]: 26 : while (g_hash_table_iter_next (&iter, NULL, &value))
316 : 5 : g_ptr_array_add (values, value);
317 : :
318 : 21 : return g_steal_pointer (&values);
319 : : }
320 : :
321 : : static void
322 : 11 : mws_schedule_service_constructed (GObject *object)
323 : : {
324 : 11 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (object);
325 : :
326 : : /* Chain up. */
327 : 11 : G_OBJECT_CLASS (mws_schedule_service_parent_class)->constructed (object);
328 : :
329 : : /* Expose the initial set of schedule entries. */
330 : : GHashTable *entries; /* (element-type utf8 MwsScheduleEntry) */
331 : 11 : entries = mws_scheduler_get_entries (self->scheduler);
332 : :
333 : 22 : g_autoptr(GPtrArray) entries_array = hash_table_get_values_as_ptr_array (entries);
334 : 11 : entries_changed_cb (self->scheduler, entries_array, NULL, self);
335 : 11 : }
336 : :
337 : : static void
338 : 10 : mws_schedule_service_dispose (GObject *object)
339 : : {
340 : 10 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (object);
341 : :
342 [ - + ]: 10 : g_assert (self->entry_subtree_id == 0);
343 : :
344 : : /* Disconnect from signals from the scheduler, and from any remaining
345 : : * schedule entries. */
346 [ + - ]: 10 : if (self->scheduler != NULL)
347 : : {
348 : 10 : g_signal_handlers_disconnect_by_data (mws_scheduler_get_peer_manager (self->scheduler), self);
349 : 10 : g_signal_handlers_disconnect_by_data (self->scheduler, self);
350 : :
351 : : GHashTable *entries; /* (element-type utf8 MwsScheduleEntry) */
352 : 10 : entries = mws_scheduler_get_entries (self->scheduler);
353 : :
354 : 20 : g_autoptr(GPtrArray) entries_array = hash_table_get_values_as_ptr_array (entries);
355 : 10 : entries_changed_cb (self->scheduler, NULL, entries_array, self);
356 : : }
357 : :
358 [ + - ]: 10 : g_clear_object (&self->scheduler);
359 : :
360 [ + - ]: 10 : g_clear_object (&self->connection);
361 [ + - ]: 10 : g_clear_pointer (&self->object_path, g_free);
362 [ + - ]: 10 : g_clear_object (&self->cancellable);
363 : :
364 [ + - + + ]: 20 : if (self->hold_reasons != NULL &&
365 : 10 : g_hash_table_size (self->hold_reasons) > 0)
366 : : {
367 : 1 : g_debug ("Disposing of MwsScheduleService while still held by %u peers",
368 : : g_hash_table_size (self->hold_reasons));
369 : :
370 : : /* Release all the services. We have to get all the keys first, since
371 : : * mws_schedule_service_release() modifies the hash table. */
372 : 1 : g_autofree const gchar **names =
373 : 1 : (const gchar **) g_hash_table_get_keys_as_array (self->hold_reasons, NULL);
374 : :
375 [ + + ]: 2 : for (gsize i = 0; names[i] != NULL; i++)
376 : : {
377 : 1 : g_autoptr(GError) local_error = NULL;
378 : :
379 [ - + ]: 1 : if (!mws_schedule_service_release (self, names[i], &local_error))
380 : 0 : g_debug ("Error releasing service for peer ‘%s’: %s",
381 : : names[i], local_error->message);
382 : : }
383 : : }
384 [ + - ]: 10 : g_clear_pointer (&self->hold_reasons, g_hash_table_unref);
385 : :
386 : : /* Chain up to the parent class */
387 : 10 : G_OBJECT_CLASS (mws_schedule_service_parent_class)->dispose (object);
388 : 10 : }
389 : :
390 : : static void
391 : 0 : mws_schedule_service_get_property (GObject *object,
392 : : guint property_id,
393 : : GValue *value,
394 : : GParamSpec *pspec)
395 : : {
396 : 0 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (object);
397 : :
398 [ # # # # : 0 : switch ((MwsScheduleServiceProperty) property_id)
# ]
399 : : {
400 : 0 : case PROP_CONNECTION:
401 : 0 : g_value_set_object (value, self->connection);
402 : 0 : break;
403 : 0 : case PROP_OBJECT_PATH:
404 : 0 : g_value_set_string (value, self->object_path);
405 : 0 : break;
406 : 0 : case PROP_SCHEDULER:
407 : 0 : g_value_set_object (value, self->scheduler);
408 : 0 : break;
409 : 0 : case PROP_BUSY:
410 : 0 : g_value_set_boolean (value, mws_schedule_service_get_busy (self));
411 : 0 : break;
412 : 0 : default:
413 : 0 : g_assert_not_reached ();
414 : : }
415 : 0 : }
416 : :
417 : : static void
418 : 33 : mws_schedule_service_set_property (GObject *object,
419 : : guint property_id,
420 : : const GValue *value,
421 : : GParamSpec *pspec)
422 : : {
423 : 33 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (object);
424 : :
425 [ + + + - ]: 33 : switch ((MwsScheduleServiceProperty) property_id)
426 : : {
427 : 11 : case PROP_CONNECTION:
428 : : /* Construct only. */
429 [ - + ]: 11 : g_assert (self->connection == NULL);
430 : 11 : self->connection = g_value_dup_object (value);
431 : 11 : break;
432 : 11 : case PROP_OBJECT_PATH:
433 : : /* Construct only. */
434 [ - + ]: 11 : g_assert (self->object_path == NULL);
435 [ - + ]: 11 : g_assert (g_variant_is_object_path (g_value_get_string (value)));
436 : 11 : self->object_path = g_value_dup_string (value);
437 : 11 : break;
438 : 11 : case PROP_SCHEDULER: {
439 : : /* Construct only. */
440 [ - + ]: 11 : g_assert (self->scheduler == NULL);
441 : 11 : self->scheduler = g_value_dup_object (value);
442 : :
443 : : /* Connect to signals from the scheduler. Connect to the initial
444 : : * set of schedule entries in constructed(). */
445 : 11 : g_signal_connect (self->scheduler, "entries-changed",
446 : : (GCallback) entries_changed_cb, self);
447 : 11 : g_signal_connect (self->scheduler, "active-entries-changed",
448 : : (GCallback) active_entries_changed_cb, self);
449 : 11 : g_signal_connect (self->scheduler, "notify::allow-downloads",
450 : : (GCallback) allow_downloads_changed_cb, self);
451 : 11 : g_signal_connect (mws_scheduler_get_peer_manager (self->scheduler),
452 : : "peer-vanished", (GCallback) peer_vanished_cb, self);
453 : :
454 : 11 : break;
455 : : }
456 : 0 : case PROP_BUSY:
457 : : /* Read only. Fall through. */
458 : : default:
459 : 0 : g_assert_not_reached ();
460 : : }
461 : 33 : }
462 : :
463 : : static MwsScheduleEntry *
464 : 0 : object_path_to_schedule_entry (MwsScheduleService *self,
465 : : const gchar *object_path)
466 : : {
467 : : /* Convert the object path into a schedule entry ID and check it’s known to
468 : : * the scheduler. */
469 [ # # ]: 0 : if (!mws_schedule_entry_id_is_valid (object_path))
470 : 0 : return NULL;
471 : :
472 : 0 : return mws_scheduler_get_entry (self->scheduler, object_path);
473 : : }
474 : :
475 : : static gchar *
476 : 13 : schedule_entry_to_object_path (MwsScheduleService *self,
477 : : MwsScheduleEntry *entry)
478 : : {
479 : 13 : return g_strconcat (self->object_path, "/", mws_schedule_entry_get_id (entry), NULL);
480 : : }
481 : :
482 : : static void
483 : 27 : count_entries (MwsScheduleService *self,
484 : : guint32 *out_entry_count,
485 : : guint32 *out_active_entry_count)
486 : : {
487 : : GHashTable *entries; /* (element-type utf8 MwsScheduleEntry) */
488 : 27 : entries = mws_scheduler_get_entries (self->scheduler);
489 : :
490 : : GHashTableIter iter;
491 : : gpointer value;
492 : 27 : guint32 active_entries = 0;
493 : :
494 [ - + ]: 27 : g_assert (out_entry_count != NULL);
495 [ - + ]: 27 : g_assert (out_active_entry_count != NULL);
496 : :
497 : 27 : g_hash_table_iter_init (&iter, entries);
498 [ + + ]: 42 : while (g_hash_table_iter_next (&iter, NULL, &value))
499 : : {
500 : 15 : MwsScheduleEntry *entry = MWS_SCHEDULE_ENTRY (value);
501 : :
502 [ + + ]: 15 : if (mws_scheduler_is_entry_active (self->scheduler, entry))
503 : 6 : active_entries++;
504 : : }
505 : :
506 : 27 : *out_entry_count = (guint32) g_hash_table_size (entries);
507 : 27 : *out_active_entry_count = active_entries;
508 : :
509 [ - + ]: 27 : g_assert (*out_active_entry_count <= *out_entry_count);
510 : 27 : }
511 : :
512 : : static void
513 : 27 : notify_scheduler_properties (MwsScheduleService *self,
514 : : gboolean notify_entries,
515 : : gboolean notify_allow_downloads)
516 : : {
517 : 27 : g_auto(GVariantDict) changed_properties_dict = G_VARIANT_DICT_INIT (NULL);
518 : : guint32 entries, active_entries;
519 : :
520 [ - + + - ]: 27 : g_debug ("%s: Notifying (notify_entries: %s, notify_allow_downloads: %s)",
521 : : G_STRFUNC, notify_entries ? "yes" : "no",
522 : : notify_allow_downloads ? "yes" : "no");
523 : :
524 [ + - ]: 27 : if (notify_entries)
525 : : {
526 : 27 : count_entries (self, &entries, &active_entries);
527 : :
528 : 27 : g_variant_dict_insert (&changed_properties_dict,
529 : : "ActiveEntryCount", "u", active_entries);
530 : 27 : g_variant_dict_insert (&changed_properties_dict,
531 : : "EntryCount", "u", entries);
532 : : }
533 : :
534 [ - + ]: 27 : if (notify_allow_downloads)
535 : : {
536 : 0 : g_variant_dict_insert (&changed_properties_dict,
537 : : "DownloadsAllowed", "b",
538 : : mws_scheduler_get_allow_downloads (self->scheduler));
539 : : }
540 : :
541 : 27 : g_autoptr(GVariant) parameters = NULL;
542 : 27 : parameters = g_variant_ref_sink (
543 : : g_variant_new ("(s@a{sv}as)",
544 : : "com.endlessm.DownloadManager1.Scheduler",
545 : : g_variant_dict_end (&changed_properties_dict),
546 : : NULL));
547 : :
548 : 27 : g_autoptr(GError) local_error = NULL;
549 : 27 : g_dbus_connection_emit_signal (self->connection,
550 : : NULL, /* broadcast */
551 : : "/com/endlessm/DownloadManager1",
552 : : "org.freedesktop.DBus.Properties",
553 : : "PropertiesChanged",
554 : : parameters,
555 : : &local_error);
556 [ + + ]: 27 : if (local_error != NULL)
557 : 10 : g_debug ("Error emitting PropertiesChanged signal: %s",
558 : : local_error->message);
559 : 27 : }
560 : :
561 : : static void
562 : 24 : entries_changed_cb (MwsScheduler *scheduler,
563 : : GPtrArray *added,
564 : : GPtrArray *removed,
565 : : gpointer user_data)
566 : : {
567 : 24 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
568 : :
569 : : /* Update signal subscriptions for the added and removed entries. */
570 [ + + + + ]: 29 : for (gsize i = 0; removed != NULL && i < removed->len; i++)
571 : : {
572 : 5 : MwsScheduleEntry *entry = MWS_SCHEDULE_ENTRY (removed->pdata[i]);
573 : :
574 : 5 : g_message ("Removing schedule entry ‘%s’.", mws_schedule_entry_get_id (entry));
575 : :
576 : 5 : g_signal_handlers_disconnect_by_data (entry, self);
577 : : }
578 : :
579 [ + + + + ]: 29 : for (gsize i = 0; added != NULL && i < added->len; i++)
580 : : {
581 : 5 : MwsScheduleEntry *entry = MWS_SCHEDULE_ENTRY (added->pdata[i]);
582 : :
583 : 5 : g_message ("Adding schedule entry ‘%s’.", mws_schedule_entry_get_id (entry));
584 : :
585 : 5 : g_signal_connect (entry, "notify",
586 : : (GCallback) entry_notify_cb, self);
587 : : }
588 : :
589 : : /* Emit Removed signals for entries. */
590 [ + + + + ]: 29 : for (gsize i = 0; removed != NULL && i < removed->len; i++)
591 : : {
592 : 5 : MwsScheduleEntry *entry = MWS_SCHEDULE_ENTRY (removed->pdata[i]);
593 : 5 : g_autoptr(GError) local_error = NULL;
594 : :
595 : 10 : g_autofree gchar *entry_path = schedule_entry_to_object_path (self, entry);
596 : :
597 : 5 : g_dbus_connection_emit_signal (self->connection,
598 : : mws_schedule_entry_get_owner (entry),
599 : : entry_path,
600 : : "com.endlessm.DownloadManager1.ScheduleEntry",
601 : : "Removed",
602 : : NULL, /* no arguments */
603 : : &local_error);
604 [ + - ]: 5 : if (local_error != NULL)
605 : 5 : g_debug ("Error emitting Removed signal for ‘%s’: %s",
606 : : entry_path, local_error->message);
607 : : }
608 : :
609 : : /* The com.endlessm.DownloadManager1.Scheduler properties potentially changed */
610 [ + + + - ]: 24 : if (((added == NULL) != (removed == NULL)) ||
611 [ + - + - ]: 3 : (added != NULL && removed != NULL && added->len != removed->len))
612 : : {
613 : 24 : notify_scheduler_properties (self, TRUE, FALSE);
614 : : }
615 : :
616 : : /* This will potentially have changed. */
617 : 24 : g_object_notify (G_OBJECT (self), "busy");
618 : 24 : }
619 : :
620 : : static void
621 : 6 : emit_download_now_changed (MwsScheduleService *self,
622 : : GPtrArray *entries,
623 : : gboolean download_now)
624 : : {
625 : 6 : g_auto(GVariantDict) changed_properties_dict = G_VARIANT_DICT_INIT (NULL);
626 : 6 : g_variant_dict_insert (&changed_properties_dict,
627 : : "DownloadNow", "b", download_now);
628 : :
629 : 6 : g_autoptr(GVariant) parameters = NULL;
630 : 6 : parameters = g_variant_ref_sink (
631 : : g_variant_new ("(s@a{sv}as)",
632 : : "com.endlessm.DownloadManager1.ScheduleEntry",
633 : : g_variant_dict_end (&changed_properties_dict),
634 : : NULL));
635 : :
636 [ + - + + ]: 9 : for (gsize i = 0; entries != NULL && i < entries->len; i++)
637 : : {
638 : 3 : MwsScheduleEntry *entry = MWS_SCHEDULE_ENTRY (entries->pdata[i]);
639 : 3 : g_autoptr(GError) local_error = NULL;
640 : :
641 [ + - ]: 3 : g_message ("Notifying entry ‘%s’ as %s.",
642 : : mws_schedule_entry_get_id (entry),
643 : : download_now ? "active" : "inactive");
644 : :
645 : 6 : g_autofree gchar *entry_path = schedule_entry_to_object_path (self, entry);
646 : :
647 : 3 : g_dbus_connection_emit_signal (self->connection,
648 : : mws_schedule_entry_get_owner (entry),
649 : : entry_path,
650 : : "org.freedesktop.DBus.Properties",
651 : : "PropertiesChanged",
652 : : parameters,
653 : : &local_error);
654 [ - + ]: 3 : if (local_error != NULL)
655 : 0 : g_debug ("Error emitting PropertiesChanged signal: %s",
656 : : local_error->message);
657 : : }
658 : 6 : }
659 : :
660 : : static void
661 : 3 : active_entries_changed_cb (MwsScheduler *scheduler,
662 : : GPtrArray *added,
663 : : GPtrArray *removed,
664 : : gpointer user_data)
665 : : {
666 : 3 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
667 : :
668 : : /* These entries have become inactive (told to stop downloading).
669 : : * Signal that on the bus. */
670 : 3 : emit_download_now_changed (self, removed, FALSE);
671 : :
672 : : /* These entries have become active (told they can start downloading).
673 : : * Signal that on the bus. */
674 : 3 : emit_download_now_changed (self, added, TRUE);
675 : :
676 : : /* The com.endlessm.DownloadManager1.Scheduler properties potentially changed */
677 [ + - + - ]: 3 : if (((added == NULL) != (removed == NULL)) ||
678 [ + - + - ]: 3 : (added != NULL && removed != NULL && added->len != removed->len))
679 : : {
680 : 3 : notify_scheduler_properties (self, TRUE, FALSE);
681 : : }
682 : 3 : }
683 : :
684 : : static void
685 : 0 : allow_downloads_changed_cb (GObject *obj,
686 : : GParamSpec *pspec,
687 : : gpointer user_data)
688 : : {
689 : 0 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
690 : :
691 : : /* The com.endlessm.DownloadManager1.Scheduler.DownloadsAllowed property
692 : : * potentially changed */
693 : 0 : notify_scheduler_properties (self, FALSE, TRUE);
694 : 0 : }
695 : :
696 : : static void
697 : 0 : entry_notify_cb (GObject *obj,
698 : : GParamSpec *pspec,
699 : : gpointer user_data)
700 : : {
701 : 0 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
702 : 0 : MwsScheduleEntry *entry = MWS_SCHEDULE_ENTRY (obj);
703 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
704 : :
705 : : /* Propagate the signal as a D-Bus signal. */
706 : 0 : const gchar *property_name = g_param_spec_get_name (pspec);
707 [ # # ]: 0 : g_auto(GVariantDict) changed_properties_dict = G_VARIANT_DICT_INIT (NULL);
708 : :
709 [ # # ]: 0 : if (g_str_equal (property_name, "priority"))
710 : 0 : g_variant_dict_insert (&changed_properties_dict,
711 : : "Priority", "u", mws_schedule_entry_get_priority (entry));
712 [ # # ]: 0 : else if (g_str_equal (property_name, "resumable"))
713 : 0 : g_variant_dict_insert (&changed_properties_dict,
714 : : "Resumable", "b", mws_schedule_entry_get_resumable (entry));
715 : : else
716 : : /* Unrecognised property. */
717 : 0 : return;
718 : :
719 : 0 : g_autofree gchar *entry_path = schedule_entry_to_object_path (self, entry);
720 : :
721 : 0 : g_autoptr(GVariant) parameters = NULL;
722 : 0 : parameters = g_variant_ref_sink (
723 : : g_variant_new ("(s@a{sv}as)",
724 : : "com.endlessm.DownloadManager1.ScheduleEntry",
725 : : g_variant_dict_end (&changed_properties_dict),
726 : : NULL));
727 : 0 : g_dbus_connection_emit_signal (self->connection,
728 : : NULL, /* broadcast */
729 : : entry_path,
730 : : "org.freedesktop.DBus.Properties",
731 : : "PropertiesChanged",
732 : : parameters,
733 : : &local_error);
734 [ # # ]: 0 : if (local_error != NULL)
735 : 0 : g_debug ("Error emitting PropertiesChanged signal: %s",
736 : : local_error->message);
737 : : }
738 : :
739 : : static void
740 : 0 : peer_vanished_cb (MwsPeerManager *manager,
741 : : const gchar *name,
742 : : gpointer user_data)
743 : : {
744 : 0 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
745 : 0 : g_autoptr(GError) local_error = NULL;
746 : :
747 : 0 : g_debug ("%s: Peer ‘%s’ vanished", G_STRFUNC, name);
748 : :
749 [ # # ]: 0 : if (!mws_schedule_service_release (self, name, &local_error))
750 : 0 : g_debug ("Error releasing service for peer ‘%s’: %s",
751 : : name, local_error->message);
752 : 0 : }
753 : :
754 : : /**
755 : : * mws_schedule_service_register:
756 : : * @self: a #MwsScheduleService
757 : : * @error: return location for a #GError
758 : : *
759 : : * Register the schedule service objects on D-Bus using the connection details
760 : : * given in #MwsScheduleService:connection and #MwsScheduleService:object-path.
761 : : *
762 : : * Use mws_schedule_service_unregister() to unregister them. Calls to these two
763 : : * functions must be well paired.
764 : : *
765 : : * Returns: %TRUE on success, %FALSE otherwise
766 : : * Since: 0.1.0
767 : : */
768 : : gboolean
769 : 11 : mws_schedule_service_register (MwsScheduleService *self,
770 : : GError **error)
771 : : {
772 [ - + ]: 11 : g_return_val_if_fail (MWS_IS_SCHEDULE_SERVICE (self), FALSE);
773 [ + - - + ]: 11 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
774 : :
775 : 11 : GDBusSubtreeVTable subtree_vtable =
776 : : {
777 : : mws_schedule_service_entry_enumerate,
778 : : mws_schedule_service_entry_introspect,
779 : : mws_schedule_service_entry_dispatch,
780 : : };
781 : :
782 : 11 : guint id = g_dbus_connection_register_subtree (self->connection,
783 : 11 : self->object_path,
784 : : &subtree_vtable,
785 : : G_DBUS_SUBTREE_FLAGS_NONE,
786 : : g_object_ref (self),
787 : : g_object_unref,
788 : : error);
789 : :
790 [ - + ]: 11 : if (id == 0)
791 : 0 : return FALSE;
792 : :
793 : 11 : self->entry_subtree_id = id;
794 : :
795 : : /* This has potentially changed. */
796 : 11 : g_object_notify (G_OBJECT (self), "busy");
797 : :
798 : 11 : return TRUE;
799 : : }
800 : :
801 : : /**
802 : : * mws_schedule_service_unregister:
803 : : * @self: a #MwsScheduleService
804 : : *
805 : : * Unregister objects from D-Bus which were previously registered using
806 : : * mws_schedule_service_register(). Calls to these two functions must be well
807 : : * paired.
808 : : *
809 : : * Since: 0.1.0
810 : : */
811 : : void
812 : 11 : mws_schedule_service_unregister (MwsScheduleService *self)
813 : : {
814 [ - + ]: 11 : g_return_if_fail (MWS_IS_SCHEDULE_SERVICE (self));
815 : :
816 : 11 : g_dbus_connection_unregister_subtree (self->connection,
817 : : self->entry_subtree_id);
818 : 11 : self->entry_subtree_id = 0;
819 : :
820 : : /* This has potentially changed. */
821 : 11 : g_object_notify (G_OBJECT (self), "busy");
822 : : }
823 : :
824 : : static gchar **
825 : 0 : mws_schedule_service_entry_enumerate (GDBusConnection *connection,
826 : : const gchar *sender,
827 : : const gchar *object_path,
828 : : gpointer user_data)
829 : : {
830 : 0 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
831 : :
832 : : /* Don’t implement any permissions checks here, as they should be specific to
833 : : * the APIs being called and objects being accessed. */
834 : : GHashTable *entries; /* (element-type utf8 MwsScheduleEntry) */
835 : 0 : entries = mws_scheduler_get_entries (self->scheduler);
836 : :
837 : : /* Output a list of paths to schedule entry objects. */
838 : 0 : g_autoptr(GPtrArray) paths = NULL; /* (element-type utf8) */
839 : 0 : paths = g_ptr_array_new_with_free_func (g_free);
840 : :
841 : : GHashTableIter iter;
842 : : gpointer key;
843 : :
844 : 0 : g_hash_table_iter_init (&iter, entries);
845 [ # # ]: 0 : while (g_hash_table_iter_next (&iter, &key, NULL))
846 : : {
847 : 0 : const gchar *entry_id = key;
848 : 0 : g_ptr_array_add (paths, g_strdup (entry_id));
849 : : }
850 : 0 : g_ptr_array_add (paths, NULL); /* terminator */
851 : :
852 : 0 : return (gchar **) g_ptr_array_free (g_steal_pointer (&paths), FALSE);
853 : : }
854 : :
855 : : static GDBusInterfaceInfo **
856 : 13 : mws_schedule_service_entry_introspect (GDBusConnection *connection,
857 : : const gchar *sender,
858 : : const gchar *object_path,
859 : : const gchar *node,
860 : : gpointer user_data)
861 : : {
862 : 13 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
863 : 13 : g_autofree GDBusInterfaceInfo **interfaces = NULL;
864 : :
865 : : /* Don’t implement any permissions checks here, as they should be specific to
866 : : * the APIs being called and objects being accessed. */
867 : :
868 [ + - ]: 13 : if (node == NULL)
869 : : {
870 : : /* The root node implements Scheduler only. */
871 : 13 : interfaces = g_new0 (GDBusInterfaceInfo *, 2);
872 : 13 : interfaces[0] = (GDBusInterfaceInfo *) &scheduler_interface;
873 : 13 : interfaces[1] = NULL;
874 : : }
875 [ # # ]: 0 : else if (object_path_to_schedule_entry (self, node) != NULL)
876 : : {
877 : : /* Build the array of interfaces which are implemented by the schedule
878 : : * entry object. */
879 : 0 : interfaces = g_new0 (GDBusInterfaceInfo *, 2);
880 : 0 : interfaces[0] = (GDBusInterfaceInfo *) &schedule_entry_interface;
881 : 0 : interfaces[1] = NULL;
882 : : }
883 : :
884 : 13 : return g_steal_pointer (&interfaces);
885 : : }
886 : :
887 : : static const GDBusInterfaceVTable *
888 : 13 : mws_schedule_service_entry_dispatch (GDBusConnection *connection,
889 : : const gchar *sender,
890 : : const gchar *object_path,
891 : : const gchar *interface_name,
892 : : const gchar *node,
893 : : gpointer *out_user_data,
894 : : gpointer user_data)
895 : : {
896 : 13 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
897 : : static const GDBusInterfaceVTable schedule_entry_interface_vtable =
898 : : {
899 : : mws_schedule_service_entry_method_call,
900 : : NULL, /* handled in mws_schedule_service_entry_method_call() */
901 : : NULL, /* handled in mws_schedule_service_entry_method_call() */
902 : : };
903 : : static const GDBusInterfaceVTable scheduler_interface_vtable =
904 : : {
905 : : mws_schedule_service_scheduler_method_call,
906 : : NULL, /* handled in mws_schedule_service_scheduler_method_call() */
907 : : NULL, /* handled in mws_schedule_service_scheduler_method_call() */
908 : : };
909 : :
910 : : /* Don’t implement any permissions checks here, as they should be specific to
911 : : * the APIs being called and objects being accessed. */
912 : :
913 : : /* Scheduler is implemented on the root of the tree. */
914 [ + - + - ]: 26 : if (node == NULL &&
915 : 13 : g_str_equal (interface_name, "com.endlessm.DownloadManager1.Scheduler"))
916 : : {
917 : 13 : *out_user_data = user_data;
918 : 13 : return &scheduler_interface_vtable;
919 : : }
920 [ # # ]: 0 : else if (node == NULL)
921 : : {
922 : 0 : return NULL;
923 : : }
924 : :
925 : : /* We only handle the ScheduleEntry interface on other objects. */
926 [ # # ]: 0 : if (!g_str_equal (interface_name, "com.endlessm.DownloadManager1.ScheduleEntry"))
927 : 0 : return NULL;
928 : :
929 : : /* Find the schedule entry. */
930 : 0 : MwsScheduleEntry *entry = object_path_to_schedule_entry (self, node);
931 : :
932 [ # # ]: 0 : if (entry == NULL)
933 : 0 : return NULL;
934 : :
935 : 0 : *out_user_data = user_data;
936 : 0 : return &schedule_entry_interface_vtable;
937 : : }
938 : :
939 : : typedef void (*ScheduleEntryMethodCallFunc) (MwsScheduleService *self,
940 : : MwsScheduleEntry *entry,
941 : : GDBusConnection *connection,
942 : : const gchar *sender,
943 : : GVariant *parameters,
944 : : GDBusMethodInvocation *invocation);
945 : :
946 : : static const struct
947 : : {
948 : : const gchar *interface_name;
949 : : const gchar *method_name;
950 : : ScheduleEntryMethodCallFunc func;
951 : : }
952 : : schedule_entry_methods[] =
953 : : {
954 : : /* Handle properties. We have to do this here so we can handle them
955 : : * asynchronously for authorisation checks. */
956 : : { "org.freedesktop.DBus.Properties", "Get",
957 : : mws_schedule_service_entry_properties_get },
958 : : { "org.freedesktop.DBus.Properties", "Set",
959 : : mws_schedule_service_entry_properties_set },
960 : : { "org.freedesktop.DBus.Properties", "GetAll",
961 : : mws_schedule_service_entry_properties_get_all },
962 : :
963 : : /* Schedule entry methods. */
964 : : { "com.endlessm.DownloadManager1.ScheduleEntry", "Remove",
965 : : mws_schedule_service_entry_remove },
966 : : };
967 : :
968 : : G_STATIC_ASSERT (G_N_ELEMENTS (schedule_entry_methods) ==
969 : : G_N_ELEMENTS (schedule_entry_interface_methods) +
970 : : -1 /* NULL terminator */ +
971 : : 3 /* o.fdo.DBus.Properties */);
972 : :
973 : : /* Main handler for incoming D-Bus method calls. */
974 : : static void
975 : 0 : mws_schedule_service_entry_method_call (GDBusConnection *connection,
976 : : const gchar *sender,
977 : : const gchar *object_path,
978 : : const gchar *interface_name,
979 : : const gchar *method_name,
980 : : GVariant *parameters,
981 : : GDBusMethodInvocation *invocation,
982 : : gpointer user_data)
983 : : {
984 : 0 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
985 : :
986 : : /* Remove the service prefix from the path. */
987 [ # # ]: 0 : g_assert (g_str_has_prefix (object_path, self->object_path));
988 [ # # ]: 0 : g_assert (object_path[strlen (self->object_path)] == '/');
989 : :
990 : : MwsScheduleEntry *entry;
991 : 0 : entry = object_path_to_schedule_entry (self,
992 : : object_path +
993 : 0 : strlen (self->object_path) + 1);
994 [ # # ]: 0 : g_assert (entry != NULL);
995 : :
996 : : /* Check the @sender is the owner of @entry. */
997 [ # # ]: 0 : if (!g_str_equal (mws_schedule_entry_get_owner (entry), sender))
998 : : {
999 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1000 : : G_DBUS_ERROR_UNKNOWN_OBJECT,
1001 : : _("Unknown object ‘%s’."), object_path);
1002 : 0 : return;
1003 : : }
1004 : :
1005 : : /* Work out which method to call. */
1006 [ # # ]: 0 : for (gsize i = 0; i < G_N_ELEMENTS (schedule_entry_methods); i++)
1007 : : {
1008 [ # # # # ]: 0 : if (g_str_equal (schedule_entry_methods[i].interface_name, interface_name) &&
1009 : 0 : g_str_equal (schedule_entry_methods[i].method_name, method_name))
1010 : : {
1011 : 0 : schedule_entry_methods[i].func (self, entry, connection, sender,
1012 : : parameters, invocation);
1013 : 0 : return;
1014 : : }
1015 : : }
1016 : :
1017 : : /* Make sure we actually called a method implementation. GIO guarantees that
1018 : : * this function is only called with methods we’ve declared in the interface
1019 : : * info, so this should never fail. */
1020 : 0 : g_assert_not_reached ();
1021 : : }
1022 : :
1023 : : static gboolean
1024 : 0 : validate_dbus_interface_name (GDBusMethodInvocation *invocation,
1025 : : const gchar *interface_name)
1026 : : {
1027 [ # # ]: 0 : if (!g_dbus_is_interface_name (interface_name))
1028 : : {
1029 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1030 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
1031 : : _("Invalid interface name ‘%s’."),
1032 : : interface_name);
1033 : 0 : return FALSE;
1034 : : }
1035 : :
1036 : 0 : return TRUE;
1037 : : }
1038 : :
1039 : : static void
1040 : 0 : mws_schedule_service_entry_properties_get (MwsScheduleService *self,
1041 : : MwsScheduleEntry *entry,
1042 : : GDBusConnection *connection,
1043 : : const gchar *sender,
1044 : : GVariant *parameters,
1045 : : GDBusMethodInvocation *invocation)
1046 : : {
1047 : : const gchar *interface_name, *property_name;
1048 : 0 : g_variant_get (parameters, "(&s&s)", &interface_name, &property_name);
1049 : :
1050 : : /* D-Bus property names can be anything. */
1051 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
1052 : 0 : return;
1053 : :
1054 : : /* Try the property. */
1055 : 0 : g_autoptr(GVariant) value = NULL;
1056 : :
1057 [ # # ]: 0 : if (g_str_equal (interface_name, "com.endlessm.DownloadManager1.ScheduleEntry"))
1058 : : {
1059 [ # # ]: 0 : if (g_str_equal (property_name, "Resumable"))
1060 : 0 : value = g_variant_new_boolean (mws_schedule_entry_get_resumable (entry));
1061 [ # # ]: 0 : else if (g_str_equal (property_name, "Priority"))
1062 : 0 : value = g_variant_new_uint32 (mws_schedule_entry_get_priority (entry));
1063 [ # # ]: 0 : else if (g_str_equal (property_name, "DownloadNow"))
1064 : 0 : value = g_variant_new_boolean (mws_scheduler_is_entry_active (self->scheduler, entry));
1065 : : }
1066 : :
1067 [ # # ]: 0 : if (value != NULL)
1068 : 0 : g_dbus_method_invocation_return_value (invocation,
1069 : 0 : g_variant_new ("(v)", g_steal_pointer (&value)));
1070 : : else
1071 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1072 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
1073 : : _("Unknown property ‘%s.%s’."),
1074 : : interface_name, property_name);
1075 : : }
1076 : :
1077 : : static void
1078 : 0 : mws_schedule_service_entry_properties_set (MwsScheduleService *self,
1079 : : MwsScheduleEntry *entry,
1080 : : GDBusConnection *connection,
1081 : : const gchar *sender,
1082 : : GVariant *parameters,
1083 : : GDBusMethodInvocation *invocation)
1084 : : {
1085 : : const gchar *interface_name, *property_name;
1086 [ # # ]: 0 : g_autoptr(GVariant) value = NULL;
1087 : 0 : g_variant_get (parameters, "(&s&sv)", &interface_name, &property_name, &value);
1088 : :
1089 : : /* D-Bus property names can be anything. */
1090 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
1091 : 0 : return;
1092 : :
1093 : : /* Try the property. */
1094 : 0 : gboolean read_only = FALSE, type_error = FALSE, handled = FALSE;
1095 : :
1096 [ # # ]: 0 : if (g_str_equal (interface_name, "com.endlessm.DownloadManager1.ScheduleEntry"))
1097 : : {
1098 [ # # ]: 0 : if (g_str_equal (property_name, "Resumable"))
1099 : : {
1100 [ # # ]: 0 : if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
1101 : : {
1102 : 0 : mws_schedule_entry_set_resumable (entry, g_variant_get_boolean (value));
1103 : 0 : handled = TRUE;
1104 : : }
1105 : : else
1106 : 0 : type_error = TRUE;
1107 : : }
1108 [ # # ]: 0 : else if (g_str_equal (property_name, "Priority"))
1109 : : {
1110 [ # # ]: 0 : if (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
1111 : : {
1112 : 0 : mws_schedule_entry_set_priority (entry, g_variant_get_uint32 (value));
1113 : 0 : handled = TRUE;
1114 : : }
1115 : : else
1116 : 0 : type_error = TRUE;
1117 : : }
1118 [ # # ]: 0 : else if (g_str_equal (property_name, "DownloadNow"))
1119 : 0 : read_only = TRUE;
1120 : : }
1121 : :
1122 [ # # ]: 0 : if (read_only)
1123 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1124 : : G_DBUS_ERROR_PROPERTY_READ_ONLY,
1125 : : _("Attribute ‘%s.%s’ is read-only."),
1126 : : interface_name, property_name);
1127 [ # # ]: 0 : else if (type_error)
1128 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1129 : : G_DBUS_ERROR_INVALID_SIGNATURE,
1130 : : _("Invalid type value for property ‘%s.%s’."),
1131 : : interface_name, property_name);
1132 [ # # ]: 0 : else if (handled)
1133 : 0 : g_dbus_method_invocation_return_value (invocation, NULL);
1134 : : else
1135 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1136 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
1137 : : _("Unknown property ‘%s.%s’."),
1138 : : interface_name, property_name);
1139 : : }
1140 : :
1141 : : static void
1142 : 0 : mws_schedule_service_entry_properties_get_all (MwsScheduleService *self,
1143 : : MwsScheduleEntry *entry,
1144 : : GDBusConnection *connection,
1145 : : const gchar *sender,
1146 : : GVariant *parameters,
1147 : : GDBusMethodInvocation *invocation)
1148 : : {
1149 : : const gchar *interface_name;
1150 : 0 : g_variant_get (parameters, "(&s)", &interface_name);
1151 : :
1152 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
1153 : 0 : return;
1154 : :
1155 : : /* Try the interface. */
1156 : 0 : g_autoptr(GVariantDict) dict = NULL;
1157 : :
1158 [ # # ]: 0 : if (g_str_equal (interface_name, "com.endlessm.DownloadManager1.ScheduleEntry"))
1159 : : {
1160 : 0 : dict = g_variant_dict_new (NULL);
1161 : :
1162 : 0 : g_variant_dict_insert (dict, "Resumable",
1163 : : "b", mws_schedule_entry_get_resumable (entry));
1164 : 0 : g_variant_dict_insert (dict, "Priority",
1165 : : "u", mws_schedule_entry_get_priority (entry));
1166 : 0 : g_variant_dict_insert (dict, "DownloadNow",
1167 : : "b", mws_scheduler_is_entry_active (self->scheduler, entry));
1168 : : }
1169 : :
1170 [ # # ]: 0 : if (dict != NULL)
1171 : 0 : g_dbus_method_invocation_return_value (invocation,
1172 : : g_variant_new ("(@a{sv})",
1173 : : g_variant_dict_end (dict)));
1174 : : else
1175 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1176 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
1177 : : _("Unknown interface ‘%s’."),
1178 : : interface_name);
1179 : : }
1180 : :
1181 : : static void
1182 : 0 : mws_schedule_service_entry_remove (MwsScheduleService *self,
1183 : : MwsScheduleEntry *entry,
1184 : : GDBusConnection *connection,
1185 : : const gchar *sender,
1186 : : GVariant *parameters,
1187 : : GDBusMethodInvocation *invocation)
1188 : : {
1189 : 0 : g_autoptr(GError) local_error = NULL;
1190 : :
1191 : 0 : g_autoptr(GPtrArray) removed = g_ptr_array_new_with_free_func (NULL);
1192 : 0 : g_ptr_array_add (removed, GUINT_TO_POINTER (mws_schedule_entry_get_id (entry)));
1193 [ # # ]: 0 : if (mws_scheduler_update_entries (self->scheduler, NULL, removed, &local_error))
1194 : : {
1195 : 0 : g_dbus_method_invocation_return_value (invocation, NULL);
1196 : : }
1197 : : else
1198 : : {
1199 : : /* We know this error domain is registered with #GDBusError. */
1200 [ # # ]: 0 : g_warn_if_fail (local_error->domain == MWS_SCHEDULER_ERROR);
1201 : 0 : g_prefix_error (&local_error, _("Error removing entry from scheduler: "));
1202 : 0 : g_dbus_method_invocation_return_gerror (invocation, local_error);
1203 : : }
1204 : 0 : }
1205 : :
1206 : : typedef void (*SchedulerMethodCallFunc) (MwsScheduleService *self,
1207 : : GDBusConnection *connection,
1208 : : const gchar *sender,
1209 : : GVariant *parameters,
1210 : : GDBusMethodInvocation *invocation);
1211 : :
1212 : : static const struct
1213 : : {
1214 : : const gchar *interface_name;
1215 : : const gchar *method_name;
1216 : : SchedulerMethodCallFunc func;
1217 : : }
1218 : : scheduler_methods[] =
1219 : : {
1220 : : /* Handle properties. */
1221 : : { "org.freedesktop.DBus.Properties", "Get",
1222 : : mws_schedule_service_scheduler_properties_get },
1223 : : { "org.freedesktop.DBus.Properties", "Set",
1224 : : mws_schedule_service_scheduler_properties_set },
1225 : : { "org.freedesktop.DBus.Properties", "GetAll",
1226 : : mws_schedule_service_scheduler_properties_get_all },
1227 : :
1228 : : /* Scheduler methods. */
1229 : : { "com.endlessm.DownloadManager1.Scheduler", "Schedule",
1230 : : mws_schedule_service_scheduler_schedule_entries },
1231 : : { "com.endlessm.DownloadManager1.Scheduler", "ScheduleEntries",
1232 : : mws_schedule_service_scheduler_schedule_entries },
1233 : : { "com.endlessm.DownloadManager1.Scheduler", "Hold",
1234 : : mws_schedule_service_scheduler_hold },
1235 : : { "com.endlessm.DownloadManager1.Scheduler", "Release",
1236 : : mws_schedule_service_scheduler_release },
1237 : : };
1238 : :
1239 : : G_STATIC_ASSERT (G_N_ELEMENTS (scheduler_methods) ==
1240 : : G_N_ELEMENTS (scheduler_interface_methods) +
1241 : : -1 /* NULL terminator */ +
1242 : : 3 /* o.fdo.DBus.Properties */);
1243 : :
1244 : : static void
1245 : 13 : mws_schedule_service_scheduler_method_call (GDBusConnection *connection,
1246 : : const gchar *sender,
1247 : : const gchar *object_path,
1248 : : const gchar *interface_name,
1249 : : const gchar *method_name,
1250 : : GVariant *parameters,
1251 : : GDBusMethodInvocation *invocation,
1252 : : gpointer user_data)
1253 : : {
1254 : 13 : MwsScheduleService *self = MWS_SCHEDULE_SERVICE (user_data);
1255 : :
1256 : : /* FIXME: Add permissions checks? This is the right place to add them.
1257 : : * Currently, we rely on D-Bus policy allowing/preventing access from
1258 : : * appropriate peers. */
1259 : :
1260 : : /* Remove the service prefix from the path. */
1261 [ - + ]: 13 : g_assert (g_str_equal (object_path, self->object_path));
1262 : :
1263 : : /* Work out which method to call. */
1264 [ + - ]: 69 : for (gsize i = 0; i < G_N_ELEMENTS (scheduler_methods); i++)
1265 : : {
1266 [ + + + + ]: 99 : if (g_str_equal (scheduler_methods[i].interface_name, interface_name) &&
1267 : 30 : g_str_equal (scheduler_methods[i].method_name, method_name))
1268 : : {
1269 : 13 : scheduler_methods[i].func (self, connection, sender,
1270 : : parameters, invocation);
1271 : 13 : return;
1272 : : }
1273 : : }
1274 : :
1275 : : /* Make sure we actually called a method implementation. GIO guarantees that
1276 : : * this function is only called with methods we’ve declared in the interface
1277 : : * info, so this should never fail. */
1278 : 0 : g_assert_not_reached ();
1279 : : }
1280 : :
1281 : : static void
1282 : 0 : mws_schedule_service_scheduler_properties_get (MwsScheduleService *self,
1283 : : GDBusConnection *connection,
1284 : : const gchar *sender,
1285 : : GVariant *parameters,
1286 : : GDBusMethodInvocation *invocation)
1287 : : {
1288 : : const gchar *interface_name, *property_name;
1289 : 0 : g_variant_get (parameters, "(&s&s)", &interface_name, &property_name);
1290 : :
1291 : : /* D-Bus property names can be anything. */
1292 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
1293 : 0 : return;
1294 : :
1295 : 0 : g_autoptr(GVariant) value = NULL;
1296 : :
1297 [ # # ]: 0 : if (g_str_equal (interface_name, "com.endlessm.DownloadManager1.Scheduler"))
1298 : : {
1299 : : guint32 entries, active_entries;
1300 : :
1301 : 0 : count_entries (self, &entries, &active_entries);
1302 : :
1303 [ # # ]: 0 : if (g_str_equal (property_name, "ActiveEntryCount"))
1304 : 0 : value = g_variant_new_uint32 (active_entries);
1305 [ # # ]: 0 : else if (g_str_equal (property_name, "EntryCount"))
1306 : 0 : value = g_variant_new_uint32 (entries);
1307 [ # # ]: 0 : else if (g_str_equal (property_name, "DownloadsAllowed"))
1308 : 0 : value = g_variant_new_boolean (mws_scheduler_get_allow_downloads (self->scheduler));
1309 : : }
1310 : :
1311 [ # # ]: 0 : if (value != NULL)
1312 : 0 : g_dbus_method_invocation_return_value (invocation,
1313 : 0 : g_variant_new ("(v)", g_steal_pointer (&value)));
1314 : : else
1315 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1316 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
1317 : : _("Unknown property ‘%s.%s’."),
1318 : : interface_name, property_name);
1319 : : }
1320 : :
1321 : : static void
1322 : 0 : mws_schedule_service_scheduler_properties_set (MwsScheduleService *self,
1323 : : GDBusConnection *connection,
1324 : : const gchar *sender,
1325 : : GVariant *parameters,
1326 : : GDBusMethodInvocation *invocation)
1327 : : {
1328 : : const gchar *interface_name, *property_name;
1329 : 0 : g_variant_get (parameters, "(&s&sv)", &interface_name, &property_name, NULL);
1330 : :
1331 : : /* D-Bus property names can be anything. */
1332 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
1333 : 0 : return;
1334 : :
1335 : : /* No properties exposed. */
1336 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1337 : : G_DBUS_ERROR_UNKNOWN_PROPERTY,
1338 : : _("Unknown property ‘%s.%s’."),
1339 : : interface_name, property_name);
1340 : : }
1341 : :
1342 : : static void
1343 : 0 : mws_schedule_service_scheduler_properties_get_all (MwsScheduleService *self,
1344 : : GDBusConnection *connection,
1345 : : const gchar *sender,
1346 : : GVariant *parameters,
1347 : : GDBusMethodInvocation *invocation)
1348 : : {
1349 : : const gchar *interface_name;
1350 : 0 : g_variant_get (parameters, "(&s)", &interface_name);
1351 : :
1352 [ # # ]: 0 : if (!validate_dbus_interface_name (invocation, interface_name))
1353 : 0 : return;
1354 : :
1355 : : /* Try the interface. */
1356 [ # # ]: 0 : if (g_str_equal (interface_name, "com.endlessm.DownloadManager1.Scheduler"))
1357 : : {
1358 : 0 : g_auto(GVariantDict) changed_properties_dict = G_VARIANT_DICT_INIT (NULL);
1359 : : guint32 entries, active_entries;
1360 : :
1361 : 0 : count_entries (self, &entries, &active_entries);
1362 : :
1363 : 0 : g_variant_dict_insert (&changed_properties_dict,
1364 : : "ActiveEntryCount", "u", active_entries);
1365 : 0 : g_variant_dict_insert (&changed_properties_dict,
1366 : : "EntryCount", "u", entries);
1367 : 0 : g_variant_dict_insert (&changed_properties_dict,
1368 : : "DownloadsAllowed", "b",
1369 : : mws_scheduler_get_allow_downloads (self->scheduler));
1370 : :
1371 : 0 : g_dbus_method_invocation_return_value (invocation,
1372 : : g_variant_new ("(@a{sv})",
1373 : : g_variant_dict_end (&changed_properties_dict)));
1374 : : }
1375 : : else
1376 : : {
1377 : 0 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1378 : : G_DBUS_ERROR_UNKNOWN_INTERFACE,
1379 : : _("Unknown interface ‘%s’."),
1380 : : interface_name);
1381 : : }
1382 : : }
1383 : :
1384 : : typedef struct
1385 : : {
1386 : : MwsScheduleService *schedule_service; /* (unowned) */
1387 : : GDBusMethodInvocation *invocation; /* (owned) */
1388 : : GPtrArray *entries; /* (element-type MwsScheduleEntry) (owned) */
1389 : : } ScheduleData;
1390 : :
1391 : : static ScheduleData *
1392 : 6 : schedule_data_new (MwsScheduleService *schedule_service,
1393 : : GDBusMethodInvocation *invocation,
1394 : : GPtrArray *entries)
1395 : : {
1396 : 6 : ScheduleData *data = g_new0 (ScheduleData, 1);
1397 : 6 : data->schedule_service = schedule_service;
1398 : 6 : data->invocation = g_object_ref (invocation);
1399 : 6 : data->entries = g_ptr_array_ref (entries);
1400 : 6 : return data;
1401 : : }
1402 : :
1403 : : static void
1404 : 6 : schedule_data_free (ScheduleData *data)
1405 : : {
1406 [ + - ]: 6 : g_clear_object (&data->invocation);
1407 [ + - ]: 6 : g_clear_pointer (&data->entries, g_ptr_array_unref);
1408 : 6 : g_free (data);
1409 : 6 : }
1410 : :
1411 [ + - ]: 12 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (ScheduleData, schedule_data_free)
1412 : :
1413 : : static void schedule_cb (GObject *obj,
1414 : : GAsyncResult *result,
1415 : : gpointer user_data);
1416 : :
1417 : : static void
1418 : 8 : mws_schedule_service_scheduler_schedule_entries (MwsScheduleService *self,
1419 : : GDBusConnection *connection,
1420 : : const gchar *sender,
1421 : : GVariant *parameters,
1422 : : GDBusMethodInvocation *invocation)
1423 : : {
1424 [ + + ]: 8 : g_autoptr(GError) local_error = NULL;
1425 : :
1426 : : /* This method implements both .Schedule and .ScheduleEntries, switching on
1427 : : * the invoked method name to work out whether to handle one or several
1428 : : * entries. */
1429 [ + + ]: 16 : g_autoptr(GPtrArray) entries = g_ptr_array_new_with_free_func (g_object_unref);
1430 : :
1431 : : /* Create one or more schedule entries, validating the parameters at the time. */
1432 [ + + ]: 8 : if (g_str_equal (g_dbus_method_invocation_get_method_name (invocation), "Schedule"))
1433 : : {
1434 [ + + ]: 3 : g_autoptr(MwsScheduleEntry) entry = NULL;
1435 [ + + ]: 3 : g_autoptr(GVariant) properties_variant = NULL;
1436 : 3 : g_variant_get (parameters, "(@a{sv})", &properties_variant);
1437 : :
1438 : 3 : entry = mws_schedule_entry_new_from_variant (sender, properties_variant, &local_error);
1439 : :
1440 [ + + ]: 3 : if (local_error != NULL)
1441 : : {
1442 : 1 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1443 : : G_DBUS_ERROR_INVALID_ARGS,
1444 : : _("Invalid schedule entry parameters: %s"),
1445 : 1 : local_error->message);
1446 : 1 : return;
1447 : : }
1448 : :
1449 : 2 : g_ptr_array_add (entries, g_steal_pointer (&entry));
1450 : : }
1451 [ + - ]: 5 : else if (g_str_equal (g_dbus_method_invocation_get_method_name (invocation), "ScheduleEntries"))
1452 : : {
1453 [ + + ]: 5 : g_autoptr(GVariantIter) properties_array_iter = NULL;
1454 : 5 : g_variant_get (parameters, "(aa{sv})", &properties_array_iter);
1455 : :
1456 [ + + ]: 5 : g_autoptr(GVariant) properties_variant = NULL;
1457 [ + + ]: 23 : while (g_variant_iter_loop (properties_array_iter, "@a{sv}", &properties_variant))
1458 : : {
1459 [ + + ]: 19 : g_autoptr(MwsScheduleEntry) entry = NULL;
1460 : 19 : entry = mws_schedule_entry_new_from_variant (sender, properties_variant, &local_error);
1461 : :
1462 [ + + ]: 19 : if (local_error != NULL)
1463 : : {
1464 : 1 : g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
1465 : : G_DBUS_ERROR_INVALID_ARGS,
1466 : : _("Invalid schedule entry parameters: %s"),
1467 : 1 : local_error->message);
1468 : 1 : return;
1469 : : }
1470 : :
1471 : 18 : g_ptr_array_add (entries, g_steal_pointer (&entry));
1472 : : }
1473 : : }
1474 : : else
1475 : : {
1476 : 0 : g_assert_not_reached ();
1477 : : }
1478 : :
1479 : : /* Load the peer’s credentials and watch to see if it disappears in future (to
1480 : : * allow removing all its schedule entries). The credentials will allow the
1481 : : * scheduler to prioritise entries by sender. */
1482 : 6 : mws_peer_manager_ensure_peer_credentials_async (mws_scheduler_get_peer_manager (self->scheduler),
1483 : : sender, self->cancellable,
1484 : : schedule_cb,
1485 : 6 : schedule_data_new (self, invocation, entries));
1486 : : }
1487 : :
1488 : : static void
1489 : 6 : schedule_cb (GObject *obj,
1490 : : GAsyncResult *result,
1491 : : gpointer user_data)
1492 : : {
1493 : 6 : MwsPeerManager *peer_manager = MWS_PEER_MANAGER (obj);
1494 [ + + ]: 6 : g_autoptr(ScheduleData) data = user_data;
1495 : 6 : MwsScheduleService *self = data->schedule_service;
1496 : 6 : GDBusMethodInvocation *invocation = data->invocation;
1497 : 6 : GPtrArray *entries = data->entries; /* (element-type MwsScheduleEntry) */
1498 [ + + ]: 6 : g_autoptr(GError) local_error = NULL;
1499 : :
1500 : : /* Finish looking up the sender. */
1501 [ + + ]: 6 : g_autofree gchar *sender_path = NULL;
1502 : 6 : sender_path = mws_peer_manager_ensure_peer_credentials_finish (peer_manager,
1503 : : result, &local_error);
1504 : :
1505 [ + + ]: 6 : if (sender_path == NULL)
1506 : : {
1507 : 2 : g_prefix_error (&local_error, _("Error adding entry to scheduler: "));
1508 : 2 : g_dbus_method_invocation_return_gerror (invocation, local_error);
1509 : 2 : return;
1510 : : }
1511 : :
1512 : : /* Add the entries to the scheduler. */
1513 [ + + ]: 4 : if (!mws_scheduler_update_entries (self->scheduler, entries, NULL, &local_error))
1514 : : {
1515 : : /* We know this error domain is registered with #GDBusError. */
1516 [ - + ]: 1 : g_warn_if_fail (local_error->domain == MWS_SCHEDULER_ERROR);
1517 : 1 : g_prefix_error (&local_error, _("Error adding entry to scheduler: "));
1518 : 1 : g_dbus_method_invocation_return_gerror (invocation, local_error);
1519 : 1 : return;
1520 : : }
1521 : :
1522 : : /* Build paths for the entries and return them. */
1523 [ + + ]: 3 : if (g_str_equal (g_dbus_method_invocation_get_method_name (invocation), "Schedule"))
1524 : : {
1525 [ - + ]: 1 : g_assert (entries->len == 1);
1526 : 1 : MwsScheduleEntry *entry = g_ptr_array_index (entries, 0);
1527 : 2 : g_autofree gchar *entry_path = schedule_entry_to_object_path (self, entry);
1528 : 1 : g_dbus_method_invocation_return_value (invocation,
1529 : : g_variant_new ("(o)", entry_path));
1530 : : }
1531 [ + - ]: 2 : else if (g_str_equal (g_dbus_method_invocation_get_method_name (invocation), "ScheduleEntries"))
1532 : : {
1533 : 4 : g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(ao)"));
1534 : 2 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("ao"));
1535 [ + + ]: 6 : for (gsize i = 0; i < entries->len; i++)
1536 : : {
1537 : 4 : MwsScheduleEntry *entry = g_ptr_array_index (entries, i);
1538 : 8 : g_autofree gchar *entry_path = schedule_entry_to_object_path (self, entry);
1539 : 4 : g_variant_builder_add (&builder, "o", entry_path);
1540 : : }
1541 : 2 : g_variant_builder_close (&builder);
1542 : :
1543 : 2 : g_dbus_method_invocation_return_value (invocation,
1544 : : g_variant_builder_end (&builder));
1545 : : }
1546 : : else
1547 : : {
1548 : 0 : g_assert_not_reached ();
1549 : : }
1550 : : }
1551 : :
1552 : : typedef struct
1553 : : {
1554 : : MwsScheduleService *schedule_service; /* (owned) */
1555 : : GDBusMethodInvocation *invocation; /* (owned) */
1556 : : } HoldData;
1557 : :
1558 : : static void
1559 : 3 : hold_data_free (HoldData *data)
1560 : : {
1561 [ + - ]: 3 : g_clear_object (&data->invocation);
1562 [ + - ]: 3 : g_clear_object (&data->schedule_service);
1563 : 3 : g_free (data);
1564 : 3 : }
1565 : :
1566 [ + + ]: 12 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (HoldData, hold_data_free)
1567 : :
1568 : : static HoldData *
1569 : 3 : hold_data_new (MwsScheduleService *schedule_service,
1570 : : GDBusMethodInvocation *invocation)
1571 : : {
1572 : 6 : g_autoptr(HoldData) data = g_new0 (HoldData, 1);
1573 : 3 : data->schedule_service = g_object_ref (schedule_service);
1574 : 3 : data->invocation = g_object_ref (invocation);
1575 : 3 : return g_steal_pointer (&data);
1576 : : }
1577 : :
1578 : : static void hold_cb (GObject *obj,
1579 : : GAsyncResult *result,
1580 : : gpointer user_data);
1581 : :
1582 : : static void
1583 : 3 : mws_schedule_service_scheduler_hold (MwsScheduleService *self,
1584 : : GDBusConnection *connection,
1585 : : const gchar *sender,
1586 : : GVariant *parameters,
1587 : : GDBusMethodInvocation *invocation)
1588 : : {
1589 : : /* Load the peer’s credentials so we can watch to see if it disappears in future. */
1590 : 3 : mws_peer_manager_ensure_peer_credentials_async (mws_scheduler_get_peer_manager (self->scheduler),
1591 : : sender, self->cancellable,
1592 : 3 : hold_cb, hold_data_new (self, invocation));
1593 : 3 : }
1594 : :
1595 : : static void
1596 : 3 : hold_cb (GObject *obj,
1597 : : GAsyncResult *result,
1598 : : gpointer user_data)
1599 : : {
1600 : 3 : MwsPeerManager *peer_manager = MWS_PEER_MANAGER (obj);
1601 [ + - ]: 3 : g_autoptr(HoldData) data = user_data;
1602 : 3 : MwsScheduleService *self = data->schedule_service;
1603 : 3 : GDBusMethodInvocation *invocation = data->invocation;
1604 [ + - ]: 3 : g_autoptr(GError) local_error = NULL;
1605 : :
1606 : : /* Finish looking up the sender. */
1607 [ + - ]: 3 : g_autofree gchar *sender_path = NULL;
1608 : 3 : sender_path = mws_peer_manager_ensure_peer_credentials_finish (peer_manager,
1609 : : result, &local_error);
1610 : :
1611 [ - + ]: 3 : if (sender_path == NULL)
1612 : : {
1613 : 0 : g_prefix_error (&local_error, _("Error looking up peer credentials: "));
1614 : 0 : g_dbus_method_invocation_return_gerror (invocation, local_error);
1615 : 0 : return;
1616 : : }
1617 : :
1618 : : /* Actually hold the service. */
1619 : 3 : const gchar *sender = g_dbus_method_invocation_get_sender (invocation);
1620 : : const gchar *reason;
1621 : 3 : g_variant_get (g_dbus_method_invocation_get_parameters (invocation), "(&s)", &reason);
1622 : :
1623 [ + + ]: 3 : if (!mws_schedule_service_hold (self, sender, reason, &local_error))
1624 : 1 : g_dbus_method_invocation_return_gerror (invocation, local_error);
1625 : : else
1626 : 2 : g_dbus_method_invocation_return_value (invocation, NULL);
1627 : : }
1628 : :
1629 : : static void
1630 : 2 : mws_schedule_service_scheduler_release (MwsScheduleService *self,
1631 : : GDBusConnection *connection,
1632 : : const gchar *sender,
1633 : : GVariant *parameters,
1634 : : GDBusMethodInvocation *invocation)
1635 : : {
1636 : 2 : g_autoptr(GError) local_error = NULL;
1637 [ + + ]: 2 : if (!mws_schedule_service_release (self, sender, &local_error))
1638 : 1 : g_dbus_method_invocation_return_gerror (invocation, local_error);
1639 : : else
1640 : 1 : g_dbus_method_invocation_return_value (invocation, NULL);
1641 : 2 : }
1642 : :
1643 : : static gboolean
1644 : 3 : mws_schedule_service_hold (MwsScheduleService *self,
1645 : : const gchar *sender,
1646 : : const gchar *reason,
1647 : : GError **error)
1648 : : {
1649 : : /* Has this client already got a hold on the service? */
1650 : 3 : const gchar *old_reason = g_hash_table_lookup (self->hold_reasons, sender);
1651 : :
1652 [ + + ]: 3 : if (old_reason != NULL)
1653 : : {
1654 : 1 : g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
1655 : : _("D-Bus peer ‘%s’ already has a hold on the scheduler"),
1656 : : sender);
1657 : 1 : return FALSE;
1658 : : }
1659 : :
1660 : : /* Add the hold. */
1661 : 2 : g_message ("Holding service for D-Bus peer ‘%s’ because: %s", sender, reason);
1662 : 2 : g_hash_table_insert (self->hold_reasons, g_strdup (sender), g_strdup (reason));
1663 : :
1664 [ + - ]: 2 : if (g_hash_table_size (self->hold_reasons) == 1)
1665 : : {
1666 : : /* This has potentially changed. */
1667 : 2 : g_object_notify (G_OBJECT (self), "busy");
1668 : : }
1669 : :
1670 : 2 : return TRUE;
1671 : : }
1672 : :
1673 : : static gboolean
1674 : 3 : mws_schedule_service_release (MwsScheduleService *self,
1675 : : const gchar *sender,
1676 : : GError **error)
1677 : : {
1678 : : /* Has this client actually got a hold on the service? */
1679 : 3 : const gchar *reason = g_hash_table_lookup (self->hold_reasons, sender);
1680 : :
1681 [ + + ]: 3 : if (reason == NULL)
1682 : : {
1683 : 1 : g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
1684 : : _("D-Bus peer ‘%s’ does not have a hold on the scheduler"),
1685 : : sender);
1686 : 1 : return FALSE;
1687 : : }
1688 : :
1689 : : /* Drop the hold. */
1690 : 2 : g_message ("Releasing hold on service for D-Bus peer ‘%s’", sender);
1691 : 2 : g_hash_table_remove (self->hold_reasons, sender);
1692 : :
1693 [ + - ]: 2 : if (g_hash_table_size (self->hold_reasons) == 0)
1694 : : {
1695 : : /* This has potentially changed. */
1696 : 2 : g_object_notify (G_OBJECT (self), "busy");
1697 : : }
1698 : :
1699 : 2 : return TRUE;
1700 : : }
1701 : :
1702 : : /**
1703 : : * mws_schedule_service_new:
1704 : : * @connection: (transfer none): D-Bus connection to export objects on
1705 : : * @object_path: root path to export objects below; must be a valid D-Bus object
1706 : : * path
1707 : : * @scheduler: (transfer none): scheduler to expose
1708 : : *
1709 : : * Create a new #MwsScheduleService instance which is set up to run as a
1710 : : * service.
1711 : : *
1712 : : * Returns: (transfer full): a new #MwsScheduleService
1713 : : * Since: 0.1.0
1714 : : */
1715 : : MwsScheduleService *
1716 : 11 : mws_schedule_service_new (GDBusConnection *connection,
1717 : : const gchar *object_path,
1718 : : MwsScheduler *scheduler)
1719 : : {
1720 [ - + + - : 11 : g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+ - - + ]
1721 [ - + ]: 11 : g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
1722 [ - + ]: 11 : g_return_val_if_fail (MWS_IS_SCHEDULER (scheduler), NULL);
1723 : :
1724 : 11 : return g_object_new (MWS_TYPE_SCHEDULE_SERVICE,
1725 : : "connection", connection,
1726 : : "object-path", object_path,
1727 : : "scheduler", scheduler,
1728 : : NULL);
1729 : : }
1730 : :
1731 : : /**
1732 : : * mws_schedule_service_get_busy:
1733 : : * @self: a #MwsScheduleService
1734 : : *
1735 : : * Get the value of #MwsScheduleService:busy.
1736 : : *
1737 : : * Returns: %TRUE if the service is busy, %FALSE otherwise
1738 : : * Since: 0.1.0
1739 : : */
1740 : : gboolean
1741 : 8 : mws_schedule_service_get_busy (MwsScheduleService *self)
1742 : : {
1743 [ - + ]: 8 : g_return_val_if_fail (MWS_IS_SCHEDULE_SERVICE (self), FALSE);
1744 : :
1745 : 8 : GHashTable *entries = mws_scheduler_get_entries (self->scheduler);
1746 [ + - + - : 16 : return ((self->entry_subtree_id != 0 && g_hash_table_size (entries) > 0) ||
+ + ]
1747 : 8 : g_hash_table_size (self->hold_reasons) > 0);
1748 : : }
|