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 <glib.h>
26 : : #include <glib-object.h>
27 : : #include <glib/gi18n-lib.h>
28 : : #include <gio/gio.h>
29 : : #include <libmogwai-schedule/schedule-entry.h>
30 : : #include <libmogwai-schedule/scheduler.h>
31 : :
32 : :
33 : : static void mws_schedule_entry_constructed (GObject *object);
34 : : static void mws_schedule_entry_dispose (GObject *object);
35 : :
36 : : static void mws_schedule_entry_get_property (GObject *object,
37 : : guint property_id,
38 : : GValue *value,
39 : : GParamSpec *pspec);
40 : : static void mws_schedule_entry_set_property (GObject *object,
41 : : guint property_id,
42 : : const GValue *value,
43 : : GParamSpec *pspec);
44 : :
45 : : /**
46 : : * MwsScheduleEntry:
47 : : *
48 : : * An entry in the scheduler representing a single download (either active or
49 : : * inactive). This stores the scheduling parameters for the download as provided
50 : : * by the app which is downloading it, but it does not store any of the
51 : : * scheduler’s state.
52 : : *
53 : : * The ID for a #MwsSchedulerEntry is globally unique and never re-used. It’s
54 : : * generated when the #MwsScheduleEntry is created.
55 : : *
56 : : * Since: 0.1.0
57 : : */
58 : : struct _MwsScheduleEntry
59 : : {
60 : : GObject parent;
61 : :
62 : : gchar *id; /* (owned) (not nullable) */
63 : : gchar *owner; /* (owned) (not nullable) */
64 : :
65 : : gboolean resumable;
66 : : guint32 priority;
67 : : };
68 : :
69 : : typedef enum
70 : : {
71 : : PROP_ID = 1,
72 : : PROP_OWNER,
73 : : PROP_RESUMABLE,
74 : : PROP_PRIORITY,
75 : : } MwsScheduleEntryProperty;
76 : :
77 [ + + + - : 1618 : G_DEFINE_TYPE (MwsScheduleEntry, mws_schedule_entry, G_TYPE_OBJECT)
+ + ]
78 : :
79 : : static void
80 : 3 : mws_schedule_entry_class_init (MwsScheduleEntryClass *klass)
81 : : {
82 : 3 : GObjectClass *object_class = (GObjectClass *) klass;
83 : 3 : GParamSpec *props[PROP_PRIORITY + 1] = { NULL, };
84 : :
85 : 3 : object_class->constructed = mws_schedule_entry_constructed;
86 : 3 : object_class->dispose = mws_schedule_entry_dispose;
87 : 3 : object_class->get_property = mws_schedule_entry_get_property;
88 : 3 : object_class->set_property = mws_schedule_entry_set_property;
89 : :
90 : : /**
91 : : * MwsScheduleEntry:id:
92 : : *
93 : : * The unique, persistent ID for this schedule entry. It’s generated at
94 : : * construction time, and never changes. It is suitable for use in a D-Bus
95 : : * object path.
96 : : *
97 : : * Since: 0.1.0
98 : : */
99 : 3 : props[PROP_ID] =
100 : 3 : g_param_spec_string ("id", "ID",
101 : : "Unique, persistent ID for this schedule entry.",
102 : : NULL,
103 : : G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
104 : :
105 : : /**
106 : : * MwsScheduleEntry:owner:
107 : : *
108 : : * The D-Bus unique name of the peer which created this schedule entry. This
109 : : * must be set at construction time.
110 : : *
111 : : * Since: 0.1.0
112 : : */
113 : 3 : props[PROP_OWNER] =
114 : 3 : g_param_spec_string ("owner", "Owner",
115 : : "D-Bus unique name of the peer which created this "
116 : : "schedule entry.",
117 : : NULL,
118 : : G_PARAM_CONSTRUCT_ONLY |
119 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
120 : :
121 : : /**
122 : : * MwsScheduleEntry:resumable:
123 : : *
124 : : * Whether pausing and resuming this download is supported by the owner after
125 : : * it’s started. Some applications and servers can only restart downloads from
126 : : * the beginning after pausing them.
127 : : *
128 : : * Since: 0.1.0
129 : : */
130 : 3 : props[PROP_RESUMABLE] =
131 : 3 : g_param_spec_boolean ("resumable", "Resumable",
132 : : "Whether pausing and resuming this download is supported.",
133 : : FALSE,
134 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
135 : :
136 : : /**
137 : : * MwsScheduleEntry:priority:
138 : : *
139 : : * The priority of this download relative to others belonging to the same
140 : : * owner. Higher numbers mean the download is more important.
141 : : *
142 : : * Since: 0.1.0
143 : : */
144 : 3 : props[PROP_PRIORITY] =
145 : 3 : g_param_spec_uint ("priority", "Priority",
146 : : "The priority of this download relative to others.",
147 : : 0, G_MAXUINT32, 0,
148 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
149 : :
150 : 3 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
151 : 3 : }
152 : :
153 : : static guint64 entry_id_counter = 0;
154 : : G_LOCK_DEFINE_STATIC (entry_id_counter);
155 : :
156 : : static void
157 : 83 : mws_schedule_entry_init (MwsScheduleEntry *self)
158 : : {
159 : : /* Generate a unique ID for the entry. With a 64-bit counter, we can generate
160 : : * a new #MwsScheduleEntry at 1GHz for over 500 years before we run out of
161 : : * numbers. Just in case, abort if we hit the limit.
162 : : * FIXME: Ideally we’d use 64-bit atomics here:
163 : : * https://bugzilla.gnome.org/show_bug.cgi?id=754182 */
164 : 83 : G_LOCK (entry_id_counter);
165 : 83 : guint64 our_id = entry_id_counter;
166 : 83 : entry_id_counter++;
167 : 83 : G_UNLOCK (entry_id_counter);
168 : :
169 [ - + ]: 83 : g_assert (our_id < G_MAXUINT64);
170 : 83 : self->id = g_strdup_printf ("%" G_GUINT64_FORMAT, our_id);
171 : 83 : }
172 : :
173 : : static void
174 : 83 : mws_schedule_entry_constructed (GObject *object)
175 : : {
176 : 83 : MwsScheduleEntry *self = MWS_SCHEDULE_ENTRY (object);
177 : :
178 : : /* Chain up to the parent class */
179 : 83 : G_OBJECT_CLASS (mws_schedule_entry_parent_class)->constructed (object);
180 : :
181 : : /* Check all our construct-only properties are set. */
182 [ + - + - ]: 83 : g_assert (self->id != NULL && *self->id != '\0');
183 [ + - + - ]: 83 : g_assert (self->owner != NULL && g_dbus_is_unique_name (self->owner));
184 : 83 : }
185 : :
186 : : static void
187 : 83 : mws_schedule_entry_dispose (GObject *object)
188 : : {
189 : 83 : MwsScheduleEntry *self = MWS_SCHEDULE_ENTRY (object);
190 : :
191 [ + - ]: 83 : g_clear_pointer (&self->owner, g_free);
192 [ + - ]: 83 : g_clear_pointer (&self->id, g_free);
193 : :
194 : : /* Chain up to the parent class */
195 : 83 : G_OBJECT_CLASS (mws_schedule_entry_parent_class)->dispose (object);
196 : 83 : }
197 : :
198 : : static void
199 : 4 : mws_schedule_entry_get_property (GObject *object,
200 : : guint property_id,
201 : : GValue *value,
202 : : GParamSpec *pspec)
203 : : {
204 : 4 : MwsScheduleEntry *self = MWS_SCHEDULE_ENTRY (object);
205 : :
206 [ + + + + : 4 : switch ((MwsScheduleEntryProperty) property_id)
- ]
207 : : {
208 : 1 : case PROP_ID:
209 : 1 : g_value_set_string (value, self->id);
210 : 1 : break;
211 : 1 : case PROP_OWNER:
212 : 1 : g_value_set_string (value, self->owner);
213 : 1 : break;
214 : 1 : case PROP_RESUMABLE:
215 : 1 : g_value_set_boolean (value, self->resumable);
216 : 1 : break;
217 : 1 : case PROP_PRIORITY:
218 : 1 : g_value_set_uint (value, self->priority);
219 : 1 : break;
220 : 0 : default:
221 : 0 : g_assert_not_reached ();
222 : : }
223 : 4 : }
224 : :
225 : : static void
226 : 89 : mws_schedule_entry_set_property (GObject *object,
227 : : guint property_id,
228 : : const GValue *value,
229 : : GParamSpec *pspec)
230 : : {
231 : 89 : MwsScheduleEntry *self = MWS_SCHEDULE_ENTRY (object);
232 : :
233 [ - + + + : 89 : switch ((MwsScheduleEntryProperty) property_id)
- ]
234 : : {
235 : 0 : case PROP_ID:
236 : : /* Read only. */
237 : 0 : g_assert_not_reached ();
238 : : break;
239 : 83 : case PROP_OWNER:
240 : : /* Construct only. */
241 [ - + ]: 83 : g_assert (self->owner == NULL);
242 [ - + ]: 83 : g_assert (g_dbus_is_unique_name (g_value_get_string (value)));
243 : 83 : self->owner = g_value_dup_string (value);
244 : 83 : break;
245 : 3 : case PROP_RESUMABLE:
246 : 3 : mws_schedule_entry_set_resumable (self, g_value_get_boolean (value));
247 : 3 : break;
248 : 3 : case PROP_PRIORITY:
249 : 3 : mws_schedule_entry_set_priority (self, g_value_get_uint (value));
250 : 3 : break;
251 : 0 : default:
252 : 0 : g_assert_not_reached ();
253 : : }
254 : 89 : }
255 : :
256 : : /**
257 : : * mws_schedule_entry_new:
258 : : * @owner: the D-Bus unique name of the peer creating this entry
259 : : *
260 : : * Create a new #MwsScheduleEntry belonging to the bus peer @owner.
261 : : *
262 : : * Returns: (transfer full): a new #MwsScheduleEntry
263 : : * Since: 0.1.0
264 : : */
265 : : MwsScheduleEntry *
266 : 60 : mws_schedule_entry_new (const gchar *owner)
267 : : {
268 [ - + ]: 60 : g_return_val_if_fail (g_dbus_is_unique_name (owner), NULL);
269 : :
270 : 60 : return mws_schedule_entry_new_from_variant (owner, NULL, NULL);
271 : : }
272 : :
273 : : /**
274 : : * mws_schedule_entry_new_from_variant:
275 : : * @owner: the D-Bus unique name of the peer creating this entry
276 : : * @parameters: (nullable): #GVariant dictionary mapping parameter names to
277 : : * values, or %NULL to ignore
278 : : * @error: return location for a #GError, or %NULL
279 : : *
280 : : * Create a new #MwsScheduleEntry belonging to the bus peer @owner, and with
281 : : * its properties initially set to the values from @parameters. If any of the
282 : : * @parameters are invalid (incorrect type or value), an error will be returned.
283 : : * Any @parameters which are not understood by this version of the server will
284 : : * be ignored without error.
285 : : *
286 : : * The following @parameters are currently supported:
287 : : *
288 : : * * `resumable` (`b`): sets #MwsScheduleEntry:resumable
289 : : * * `priority` (`u`): sets #MwsScheduleEntry:priority
290 : : *
291 : : * If @parameters is floating, it will be consumed.
292 : : *
293 : : * Returns: (transfer full): a new #MwsScheduleEntry
294 : : * Since: 0.1.0
295 : : */
296 : : MwsScheduleEntry *
297 : 86 : mws_schedule_entry_new_from_variant (const gchar *owner,
298 : : GVariant *parameters,
299 : : GError **error)
300 : : {
301 [ - + ]: 86 : g_return_val_if_fail (g_dbus_is_unique_name (owner), NULL);
302 [ + + - + ]: 86 : g_return_val_if_fail (parameters == NULL ||
303 : : g_variant_is_of_type (parameters, G_VARIANT_TYPE_VARDICT), NULL);
304 [ + + - + ]: 86 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
305 : :
306 [ + + ]: 172 : g_autoptr(GVariant) parameters_sunk = (parameters != NULL) ? g_variant_ref_sink (parameters) : NULL;
307 [ + + ]: 86 : gsize n_parameters = (parameters != NULL) ? g_variant_n_children (parameters) : 0;
308 : :
309 : 86 : g_autoptr(GPtrArray) names = NULL;
310 : 86 : names = g_ptr_array_new_full (n_parameters, NULL);
311 : 86 : g_autoptr(GArray) values = NULL;
312 : 86 : values = g_array_sized_new (FALSE, TRUE, sizeof (GValue), n_parameters);
313 : 86 : g_array_set_clear_func (values, (GDestroyNotify) g_value_unset);
314 : :
315 [ + + ]: 86 : if (parameters != NULL)
316 : : {
317 : : GVariantIter iter;
318 : : const gchar *key;
319 [ + + ]: 25 : g_autoptr(GVariant) variant = NULL;
320 : :
321 : 25 : g_variant_iter_init (&iter, parameters);
322 [ + + ]: 30 : while (g_variant_iter_loop (&iter, "{&sv}", &key, &variant))
323 : : {
324 : : const struct
325 : : {
326 : : const gchar *name;
327 : : const GVariantType *type;
328 : : }
329 : 8 : supported_properties[] =
330 : : {
331 : : { "resumable", G_VARIANT_TYPE_BOOLEAN },
332 : : { "priority", G_VARIANT_TYPE_UINT32 },
333 : : };
334 : :
335 [ + + ]: 13 : for (gsize i = 0; i < G_N_ELEMENTS (supported_properties); i++)
336 : : {
337 [ + + ]: 12 : if (g_str_equal (supported_properties[i].name, key))
338 : : {
339 [ + + ]: 7 : if (!g_variant_is_of_type (variant, supported_properties[i].type))
340 : : {
341 : 3 : g_set_error (error, MWS_SCHEDULER_ERROR,
342 : : MWS_SCHEDULER_ERROR_INVALID_PARAMETERS,
343 : : _("Invalid schedule entry parameters"));
344 : 3 : return NULL;
345 : : }
346 : :
347 : 4 : GValue value = G_VALUE_INIT;
348 : 4 : g_dbus_gvariant_to_gvalue (variant, &value);
349 : :
350 : 4 : g_ptr_array_add (names, (gpointer) key);
351 : 4 : g_array_append_val (values, value);
352 : :
353 : 4 : break;
354 : : }
355 : : }
356 : : }
357 : : }
358 : :
359 : : /* Always add the owner. */
360 : 83 : g_ptr_array_add (names, (gpointer) "owner");
361 : 83 : GValue value = G_VALUE_INIT;
362 : 83 : g_value_init (&value, G_TYPE_STRING);
363 : 83 : g_value_set_string (&value, owner);
364 : 83 : g_array_append_val (values, value);
365 : :
366 : : /* The cast of values->data to (const GValue *) triggers a -Wcast-align warning
367 : : * on ARM without the cast through (void *). The array body is guaranteed to
368 : : * be pointer aligned as the minimum array body size (from garray.c) is
369 : : * 16 bytes. */
370 [ - + ]: 83 : g_assert (names->len == values->len);
371 : 83 : return MWS_SCHEDULE_ENTRY (g_object_new_with_properties (MWS_TYPE_SCHEDULE_ENTRY, names->len,
372 : 83 : (const gchar **) names->pdata,
373 : 83 : (const GValue *) (void *) values->data));
374 : : }
375 : :
376 : : /**
377 : : * mws_schedule_entry_get_id:
378 : : * @self: a #MwsScheduleEntry
379 : : *
380 : : * Get the persistent identifier for this schedule entry. This is assigned when
381 : : * at construction time, uniquely and persistently, and is never %NULL or the
382 : : * empty string.
383 : : *
384 : : * Returns: identifier for the entry
385 : : * Since: 0.1.0
386 : : */
387 : : const gchar *
388 : 445 : mws_schedule_entry_get_id (MwsScheduleEntry *self)
389 : : {
390 [ - + ]: 445 : g_return_val_if_fail (MWS_IS_SCHEDULE_ENTRY (self), NULL);
391 : :
392 [ + - + - ]: 445 : g_assert (self->id != NULL && *self->id != '\0');
393 : 445 : return self->id;
394 : : }
395 : :
396 : : /**
397 : : * mws_schedule_entry_get_owner:
398 : : * @self: a #MwsScheduleEntry
399 : : *
400 : : * Get the value of #MwsScheduleEntry:owner.
401 : : *
402 : : * Returns: the entry’s owner
403 : : * Since: 0.1.0
404 : : */
405 : : const gchar *
406 : 168 : mws_schedule_entry_get_owner (MwsScheduleEntry *self)
407 : : {
408 [ - + ]: 168 : g_return_val_if_fail (MWS_IS_SCHEDULE_ENTRY (self), NULL);
409 : :
410 : 168 : return self->owner;
411 : : }
412 : :
413 : : /**
414 : : * mws_schedule_entry_get_priority:
415 : : * @self: a #MwsScheduleEntry
416 : : *
417 : : * Get the value of #MwsScheduleEntry:priority.
418 : : *
419 : : * Returns: the entry’s priority
420 : : * Since: 0.1.0
421 : : */
422 : : guint32
423 : 96 : mws_schedule_entry_get_priority (MwsScheduleEntry *self)
424 : : {
425 [ - + ]: 96 : g_return_val_if_fail (MWS_IS_SCHEDULE_ENTRY (self), 0);
426 : :
427 : 96 : return self->priority;
428 : : }
429 : :
430 : : /**
431 : : * mws_schedule_entry_set_priority:
432 : : * @self: a #MwsScheduleEntry
433 : : * @priority: the entry’s priority
434 : : *
435 : : * Set the value of #MwsScheduleEntry:priority.
436 : : *
437 : : * Since: 0.1.0
438 : : */
439 : : void
440 : 24 : mws_schedule_entry_set_priority (MwsScheduleEntry *self,
441 : : guint32 priority)
442 : : {
443 [ - + ]: 24 : g_return_if_fail (MWS_IS_SCHEDULE_ENTRY (self));
444 : :
445 [ + + ]: 24 : if (self->priority == priority)
446 : 1 : return;
447 : :
448 : 23 : self->priority = priority;
449 : 23 : g_object_notify (G_OBJECT (self), "priority");
450 : : }
451 : :
452 : : /**
453 : : * mws_schedule_entry_get_resumable:
454 : : * @self: a #MwsScheduleEntry
455 : : *
456 : : * Get the value of #MwsScheduleEntry:resumable.
457 : : *
458 : : * Returns: %TRUE if the download is resumable, %FALSE otherwise
459 : : * Since: 0.1.0
460 : : */
461 : : gboolean
462 : 8 : mws_schedule_entry_get_resumable (MwsScheduleEntry *self)
463 : : {
464 [ - + ]: 8 : g_return_val_if_fail (MWS_IS_SCHEDULE_ENTRY (self), FALSE);
465 : :
466 : 8 : return self->resumable;
467 : : }
468 : :
469 : : /**
470 : : * mws_schedule_entry_set_resumable:
471 : : * @self: a #MwsScheduleEntry
472 : : * @resumable: %TRUE if the download is resumable, %FALSE otherwise
473 : : *
474 : : * Set the value of #MwsScheduleEntry:resumable.
475 : : *
476 : : * Since: 0.1.0
477 : : */
478 : : void
479 : 5 : mws_schedule_entry_set_resumable (MwsScheduleEntry *self,
480 : : gboolean resumable)
481 : : {
482 [ - + ]: 5 : g_return_if_fail (MWS_IS_SCHEDULE_ENTRY (self));
483 : :
484 : 5 : resumable = !!resumable;
485 [ + + ]: 5 : if (self->resumable == resumable)
486 : 2 : return;
487 : :
488 : 3 : self->resumable = resumable;
489 : 3 : g_object_notify (G_OBJECT (self), "resumable");
490 : : }
|