GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 75.0% 102 / 0 / 136
Functions: 88.9% 16 / 0 / 18
Branches: 49.2% 31 / 0 / 63

shumate/shumate-data-source-request.c
Line Branch Exec Source
1 /*
2 * Copyright (C) 2023 James Westman <james@jwestman.net>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
16 */
17
18 #include "shumate-data-source-request.h"
19 #include "shumate-utils-private.h"
20 #include "shumate-profiling-private.h"
21
22
23 /**
24 * ShumateDataSourceRequest:
25 *
26 * Represents a request to a [class@DataSource] for a tile.
27 *
28 * Data sources can return a tile multiple times. For example, a
29 * [class@TileDownloader] may return cached data first, then later return data
30 * from a network service when it arrives. This allows the map to be rendered
31 * as quickly as possible without waiting for the network unnecessarily.
32 *
33 * Conventional async/finish method pairs don't support multiple returns.
34 * Instead, [method@DataSource.start_request] is available, which returns a
35 * [class@DataSourceRequest] whose properties, [property@DataSourceRequest:data]
36 * and [property@DataSourceRequest:error], update as data becomes available.
37 * The [signal@GObject.Object::notify] signal can be used to watch for these
38 * changes. When the request is done and no more data will be returned,
39 * [property@DataSourceRequest:completed] is set to %TRUE.
40 *
41 * [class@DataSource] implementations can use a subclass of
42 * [class@DataSourceRequest], but the base class should be sufficient in most
43 * cases.
44 *
45 * Since: 1.1
46 */
47
48
49 typedef struct {
50 GObject parent;
51
52 ShumateGridPosition pos;
53 GBytes *bytes;
54 GError *error;
55
56 gboolean completed : 1;
57 } ShumateDataSourceRequestPrivate;
58
59
4/6
shumate_data_source_request_class_intern_init:
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 5 not taken.
shumate_data_source_request_get_type:
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 7 taken 42 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 7 not taken.
107 G_DEFINE_TYPE_WITH_PRIVATE (ShumateDataSourceRequest, shumate_data_source_request, G_TYPE_OBJECT)
60
61 enum {
62 PROP_0,
63 PROP_X,
64 PROP_Y,
65 PROP_ZOOM_LEVEL,
66 PROP_DATA,
67 PROP_ERROR,
68 PROP_COMPLETED,
69 N_PROPS
70 };
71
72 static GParamSpec *properties [N_PROPS];
73
74 static void
75 4 shumate_data_source_request_finalize (GObject *object)
76 {
77 4 ShumateDataSourceRequest *self = (ShumateDataSourceRequest *)object;
78 4 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
79
80
2/2
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 2 times.
4 g_clear_pointer (&priv->bytes, g_bytes_unref);
81 4 g_clear_error (&priv->error);
82
83 4 G_OBJECT_CLASS (shumate_data_source_request_parent_class)->finalize (object);
84 4 }
85
86 static void
87 ✗ shumate_data_source_request_get_property (GObject *object,
88 guint prop_id,
89 GValue *value,
90 GParamSpec *pspec)
91 {
92 ✗ ShumateDataSourceRequest *self = SHUMATE_DATA_SOURCE_REQUEST (object);
93 ✗ ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
94
95 ✗ switch (prop_id)
96 {
97 ✗ case PROP_X:
98 ✗ g_value_set_int (value, priv->pos.x);
99 ✗ break;
100
101 ✗ case PROP_Y:
102 ✗ g_value_set_int (value, priv->pos.y);
103 ✗ break;
104
105 ✗ case PROP_ZOOM_LEVEL:
106 ✗ g_value_set_int (value, priv->pos.zoom);
107 ✗ break;
108
109 ✗ case PROP_DATA:
110 ✗ g_value_set_boxed (value, priv->bytes);
111 ✗ break;
112
113 ✗ case PROP_ERROR:
114 ✗ g_value_set_boxed (value, priv->error);
115 ✗ break;
116
117 ✗ case PROP_COMPLETED:
118 ✗ g_value_set_boolean (value, priv->completed);
119 ✗ break;
120
121 ✗ default:
122 ✗ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
123 }
124 ✗ }
125
126 static void
127 12 shumate_data_source_request_set_property (GObject *object,
128 guint prop_id,
129 const GValue *value,
130 GParamSpec *pspec)
131 {
132 12 ShumateDataSourceRequest *self = SHUMATE_DATA_SOURCE_REQUEST (object);
133 12 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
134
135
3/4
✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 5 taken 4 times.
✓ Branch 2 → 7 taken 4 times.
✗ Branch 2 → 9 not taken.
12 switch (prop_id)
136 {
137 4 case PROP_X:
138 4 priv->pos.x = g_value_get_int (value);
139 4 break;
140
141 4 case PROP_Y:
142 4 priv->pos.y = g_value_get_int (value);
143 4 break;
144
145 4 case PROP_ZOOM_LEVEL:
146 4 priv->pos.zoom = g_value_get_int (value);
147 4 break;
148
149 ✗ default:
150 ✗ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
151 }
152 12 }
153
154 static void
155 3 shumate_data_source_request_class_init (ShumateDataSourceRequestClass *klass)
156 {
157 3 GObjectClass *object_class = G_OBJECT_CLASS (klass);
158
159 3 object_class->finalize = shumate_data_source_request_finalize;
160 3 object_class->get_property = shumate_data_source_request_get_property;
161 3 object_class->set_property = shumate_data_source_request_set_property;
162
163 /**
164 * ShumateDataSourceRequest:x:
165 *
166 * The X coordinate of the requested tile.
167 *
168 * Since: 1.1
169 */
170 6 properties[PROP_X] =
171 3 g_param_spec_int ("x", NULL, NULL,
172 G_MININT, G_MAXINT, 0,
173 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
174
175 /**
176 * ShumateDataSourceRequest:y:
177 *
178 * The Y coordinate of the requested tile.
179 *
180 * Since: 1.1
181 */
182 6 properties[PROP_Y] =
183 3 g_param_spec_int ("y", NULL, NULL,
184 G_MININT, G_MAXINT, 0,
185 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
186
187 /**
188 * ShumateDataSourceRequest:zoom-level:
189 *
190 * The zoom level of the requested tile.
191 *
192 * Since: 1.1
193 */
194 6 properties[PROP_ZOOM_LEVEL] =
195 3 g_param_spec_int ("zoom-level", NULL, NULL,
196 G_MININT, G_MAXINT, 0,
197 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);
198
199 /**
200 * ShumateDataSourceRequest:data:
201 *
202 * The most recent data for the tile, if available. If an error is emitted,
203 * this will be set to %NULL.
204 *
205 * Since: 1.1
206 */
207 6 properties[PROP_DATA] =
208 3 g_param_spec_boxed ("data", NULL, NULL,
209 G_TYPE_BYTES,
210 G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
211
212 /**
213 * ShumateDataSourceRequest:error:
214 *
215 * The error that occurred during the request, if any.
216 *
217 * Since: 1.1
218 */
219 6 properties[PROP_ERROR] =
220 3 g_param_spec_boxed ("error", NULL, NULL,
221 G_TYPE_ERROR,
222 G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
223
224 /**
225 * ShumateDataSourceRequest:completed:
226 *
227 * %TRUE if the request has been completed, otherwise %FALSE. A completed
228 * request will not receive further updates to either
229 * [property@DataSourceRequest:data] or [property@DataSourceRequest:error].
230 *
231 * Since: 1.1
232 */
233 6 properties[PROP_COMPLETED] =
234 3 g_param_spec_boolean ("completed", NULL, NULL,
235 FALSE,
236 G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
237
238 3 g_object_class_install_properties (object_class, N_PROPS, properties);
239 3 }
240
241 static void
242 4 shumate_data_source_request_init (ShumateDataSourceRequest *self)
243 {
244 4 }
245
246 /**
247 * shumate_data_source_request_get_x:
248 * @self: a [class@DataSourceRequest]
249 *
250 * Gets the X coordinate of the requested tile.
251 *
252 * Returns: the X coordinate
253 *
254 * Since: 1.1
255 */
256 int
257 2 shumate_data_source_request_get_x (ShumateDataSourceRequest *self)
258 {
259 2 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
260
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), 0);
261 2 return priv->pos.x;
262 }
263
264 /**
265 * shumate_data_source_request_get_y:
266 * @self: a [class@DataSourceRequest]
267 *
268 * Gets the Y coordinate of the requested tile.
269 *
270 * Returns: the Y coordinate
271 *
272 * Since: 1.1
273 */
274 int
275 2 shumate_data_source_request_get_y (ShumateDataSourceRequest *self)
276 {
277 2 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
278
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), 0);
279 2 return priv->pos.y;
280 }
281
282 /**
283 * shumate_data_source_request_get_zoom_level:
284 * @self: a [class@DataSourceRequest]
285 *
286 * Gets the zoom level of the requested tile.
287 *
288 * Returns: the zoom level
289 *
290 * Since: 1.1
291 */
292 int
293 2 shumate_data_source_request_get_zoom_level (ShumateDataSourceRequest *self)
294 {
295 2 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
296
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), 0);
297 2 return priv->pos.zoom;
298 }
299
300 /**
301 * shumate_data_source_request_get_data:
302 * @self: a [class@DataSourceRequest]
303 *
304 * Gets the latest data from the request.
305 *
306 * Returns: (transfer none) (nullable): The latest data, if any.
307 *
308 * Since: 1.1
309 */
310 GBytes *
311 10 shumate_data_source_request_get_data (ShumateDataSourceRequest *self)
312 {
313 10 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
314
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 10 times.
10 g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), NULL);
315 10 return priv->bytes;
316 }
317
318 /**
319 * shumate_data_source_request_get_error:
320 * @self: a [class@DataSourceRequest]
321 *
322 * Gets the latest error from the request.
323 *
324 * Returns: (transfer none) (nullable): The latest error, if any.
325 *
326 * Since: 1.1
327 */
328 GError *
329 2 shumate_data_source_request_get_error (ShumateDataSourceRequest *self)
330 {
331 2 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
332
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), NULL);
333 2 return priv->error;
334 }
335
336 /**
337 * shumate_data_source_request_is_completed:
338 * @self: a [class@DataSourceRequest]
339 *
340 * Gets whether the request has been completed. Completed requests will not
341 * receive new data or errors.
342 *
343 * Returns: %TRUE if the request is completed, otherwise %FALSE
344 *
345 * Since: 1.1
346 */
347 gboolean
348 12 shumate_data_source_request_is_completed (ShumateDataSourceRequest *self)
349 {
350 12 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
351
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 12 times.
12 g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), FALSE);
352 12 return priv->completed;
353 }
354
355 /**
356 * shumate_data_source_request_new:
357 * @x: X coordinate of the requested tile
358 * @y: Y coordinate of the requested tile
359 * @zoom_level: Zoom level of the requested tile
360 *
361 * Creates a new [class@DataSourceRequest].
362 *
363 * Only implementations of [vfunc@DataSource.start_request] should need to
364 * construct a new request object.
365 *
366 * Returns: (transfer full): a new [class@DataSourceRequest]
367 *
368 * Since: 1.1
369 */
370 ShumateDataSourceRequest *
371 4 shumate_data_source_request_new (int x,
372 int y,
373 int zoom_level)
374 {
375 4 return g_object_new (SHUMATE_TYPE_DATA_SOURCE_REQUEST,
376 "x", x,
377 "y", y,
378 "zoom-level", zoom_level,
379 NULL);
380 }
381
382 /**
383 * shumate_data_source_request_emit_data:
384 * @self: a [class@DataSourceRequest]
385 * @data: the data to emit
386 * @complete: %TRUE to also complete the request, %FALSE otherwise
387 *
388 * Emits tile data as a response to the request. This sets the
389 * [property@DataSourceRequest:data] property.
390 *
391 * If @complete is %TRUE, then [property@DataSourceRequest:completed] is set to
392 * %TRUE as well.
393 *
394 * Since: 1.1
395 */
396 void
397 8 shumate_data_source_request_emit_data (ShumateDataSourceRequest *self,
398 GBytes *data,
399 gboolean complete)
400 {
401 8 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
402 8 g_autofree char *profiling_desc = NULL;
403
404
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 8 times.
8 g_return_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self));
405
1/2
✓ Branch 5 → 6 taken 8 times.
✗ Branch 5 → 7 not taken.
8 g_return_if_fail (data != NULL);
406
1/2
✓ Branch 6 → 8 taken 8 times.
✗ Branch 6 → 9 not taken.
8 g_return_if_fail (!priv->completed);
407
408
2/2
✓ Branch 8 → 10 taken 2 times.
✓ Branch 8 → 11 taken 6 times.
8 if (complete)
409 2 priv->completed = TRUE;
410
411
3/4
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 14 taken 4 times.
✓ Branch 13 → 14 taken 4 times.
✗ Branch 13 → 24 not taken.
8 if (priv->bytes == NULL || !g_bytes_equal (data, priv->bytes))
412 {
413
2/2
✓ Branch 14 → 15 taken 4 times.
✓ Branch 14 → 16 taken 4 times.
8 g_clear_pointer (&priv->bytes, g_bytes_unref);
414 8 priv->bytes = g_bytes_ref (data);
415
416 8 profiling_desc = g_strdup_printf ("(%d, %d) @ %d", priv->pos.x, priv->pos.y, priv->pos.zoom);
417 16 SHUMATE_PROFILE_START_NAMED (emit_data);
418 8 g_object_notify_by_pspec ((GObject *)self, properties[PROP_DATA]);
419 8 SHUMATE_PROFILE_END_NAMED (emit_data, profiling_desc);
420 }
421
422
2/2
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 26 taken 6 times.
8 if (complete)
423 2 g_object_notify_by_pspec ((GObject *)self, properties[PROP_COMPLETED]);
424 }
425
426 /**
427 * shumate_data_source_request_emit_error:
428 * @self: a [class@DataSourceRequest]
429 * @error: an error
430 *
431 * Emits a fatal error in response to the request. This completes the request,
432 * so no more data or errors can be emitted after this. Non-fatal errors should
433 * not be reported.
434 *
435 * If [property@DataSourceRequest:data] was previously set, it will be cleared.
436 *
437 * Since: 1.1
438 */
439 void
440 2 shumate_data_source_request_emit_error (ShumateDataSourceRequest *self,
441 const GError *error)
442 {
443 2 ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
444
445
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 g_return_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self));
446
1/2
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 7 not taken.
2 g_return_if_fail (error != NULL);
447
1/2
✓ Branch 6 → 8 taken 2 times.
✗ Branch 6 → 11 not taken.
2 g_return_if_fail (!priv->completed);
448
449 2 g_clear_error (&priv->error);
450 2 priv->error = g_error_copy (error);
451
452 2 priv->completed = TRUE;
453
454
1/2
✓ Branch 10 → 12 taken 2 times.
✗ Branch 10 → 14 not taken.
2 if (priv->bytes)
455 {
456 2 g_clear_pointer (&priv->bytes, g_bytes_unref);
457 2 g_object_notify_by_pspec ((GObject *)self, properties[PROP_DATA]);
458 }
459
460 2 g_object_notify_by_pspec ((GObject *)self, properties[PROP_ERROR]);
461 2 g_object_notify_by_pspec ((GObject *)self, properties[PROP_COMPLETED]);
462 }
463
464 /**
465 * shumate_data_source_request_complete:
466 * @self: a [class@DataSourceRequest]
467 *
468 * Marks the request as complete. No more data or errors may be emitted.
469 *
470 * This can only be called if data has been emitted. If there is no data,
471 * use [method@DataSourceRequest.emit_error] instead, which will automatically
472 * complete the request.
473 *
474 * Since: 1.1
475 */
476 void
477 ✗ shumate_data_source_request_complete (ShumateDataSourceRequest *self)
478 {
479 ✗ ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
480
481 ✗ g_return_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self));
482 ✗ g_return_if_fail (!priv->completed);
483 ✗ g_return_if_fail (priv->bytes != NULL || priv->error != NULL);
484
485 ✗ priv->completed = TRUE;
486 ✗ g_object_notify_by_pspec ((GObject *)self, properties[PROP_COMPLETED]);
487 }
488