Branch data Line data Source code
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 [ + + + - ]: 159 : 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 : 6 : shumate_data_source_request_finalize (GObject *object)
76 : : {
77 : 6 : ShumateDataSourceRequest *self = (ShumateDataSourceRequest *)object;
78 : 6 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
79 : :
80 [ + + ]: 6 : g_clear_pointer (&priv->bytes, g_bytes_unref);
81 : 6 : g_clear_error (&priv->error);
82 : :
83 : 6 : G_OBJECT_CLASS (shumate_data_source_request_parent_class)->finalize (object);
84 : 6 : }
85 : :
86 : : static void
87 : 0 : shumate_data_source_request_get_property (GObject *object,
88 : : guint prop_id,
89 : : GValue *value,
90 : : GParamSpec *pspec)
91 : : {
92 : 0 : ShumateDataSourceRequest *self = SHUMATE_DATA_SOURCE_REQUEST (object);
93 : 0 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
94 : :
95 [ # # # # : 0 : switch (prop_id)
# # # ]
96 : : {
97 : 0 : case PROP_X:
98 : 0 : g_value_set_int (value, priv->pos.x);
99 : 0 : break;
100 : :
101 : 0 : case PROP_Y:
102 : 0 : g_value_set_int (value, priv->pos.y);
103 : 0 : break;
104 : :
105 : 0 : case PROP_ZOOM_LEVEL:
106 : 0 : g_value_set_int (value, priv->pos.zoom);
107 : 0 : break;
108 : :
109 : 0 : case PROP_DATA:
110 : 0 : g_value_set_boxed (value, priv->bytes);
111 : 0 : break;
112 : :
113 : 0 : case PROP_ERROR:
114 : 0 : g_value_set_boxed (value, priv->error);
115 : 0 : break;
116 : :
117 : 0 : case PROP_COMPLETED:
118 : 0 : g_value_set_boolean (value, priv->completed);
119 : 0 : break;
120 : :
121 : 0 : default:
122 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
123 : : }
124 : 0 : }
125 : :
126 : : static void
127 : 18 : shumate_data_source_request_set_property (GObject *object,
128 : : guint prop_id,
129 : : const GValue *value,
130 : : GParamSpec *pspec)
131 : : {
132 : 18 : ShumateDataSourceRequest *self = SHUMATE_DATA_SOURCE_REQUEST (object);
133 : 18 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
134 : :
135 [ + + + - ]: 18 : switch (prop_id)
136 : : {
137 : 6 : case PROP_X:
138 : 6 : priv->pos.x = g_value_get_int (value);
139 : 6 : break;
140 : :
141 : 6 : case PROP_Y:
142 : 6 : priv->pos.y = g_value_get_int (value);
143 : 6 : break;
144 : :
145 : 6 : case PROP_ZOOM_LEVEL:
146 : 6 : priv->pos.zoom = g_value_get_int (value);
147 : 6 : break;
148 : :
149 : 0 : default:
150 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
151 : : }
152 : 18 : }
153 : :
154 : : static void
155 : 4 : shumate_data_source_request_class_init (ShumateDataSourceRequestClass *klass)
156 : : {
157 : 4 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
158 : :
159 : 4 : object_class->finalize = shumate_data_source_request_finalize;
160 : 4 : object_class->get_property = shumate_data_source_request_get_property;
161 : 4 : 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 : 8 : properties[PROP_X] =
171 : 4 : g_param_spec_int ("x", "x", "x",
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 : 8 : properties[PROP_Y] =
183 : 4 : g_param_spec_int ("y", "y", "y",
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 : 8 : properties[PROP_ZOOM_LEVEL] =
195 : 4 : g_param_spec_int ("zoom-level", "zoom-level", "zoom-level",
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 : 8 : properties[PROP_DATA] =
208 : 4 : g_param_spec_boxed ("data", "data", "data",
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 : 8 : properties[PROP_ERROR] =
220 : 4 : g_param_spec_boxed ("error", "error", "error",
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 : 8 : properties[PROP_COMPLETED] =
234 : 4 : g_param_spec_boolean ("completed", "completed", "completed",
235 : : FALSE,
236 : : G_PARAM_STATIC_STRINGS | G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
237 : :
238 : 4 : g_object_class_install_properties (object_class, N_PROPS, properties);
239 : 4 : }
240 : :
241 : : static void
242 : 6 : shumate_data_source_request_init (ShumateDataSourceRequest *self)
243 : : {
244 : 6 : }
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 : 3 : shumate_data_source_request_get_x (ShumateDataSourceRequest *self)
258 : : {
259 : 3 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
260 [ + - ]: 3 : g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), 0);
261 : 3 : 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 : 3 : shumate_data_source_request_get_y (ShumateDataSourceRequest *self)
276 : : {
277 : 3 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
278 [ + - ]: 3 : g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), 0);
279 : 3 : 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 : 3 : shumate_data_source_request_get_zoom_level (ShumateDataSourceRequest *self)
294 : : {
295 : 3 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
296 [ + - ]: 3 : g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), 0);
297 : 3 : 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 : 15 : shumate_data_source_request_get_data (ShumateDataSourceRequest *self)
312 : : {
313 : 15 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
314 [ + - ]: 15 : g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), NULL);
315 : 15 : 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 : 3 : shumate_data_source_request_get_error (ShumateDataSourceRequest *self)
330 : : {
331 : 3 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
332 [ + - ]: 3 : g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), NULL);
333 : 3 : 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 : 18 : shumate_data_source_request_is_completed (ShumateDataSourceRequest *self)
349 : : {
350 : 18 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
351 [ + - ]: 18 : g_return_val_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self), FALSE);
352 : 18 : 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 : 6 : shumate_data_source_request_new (int x,
372 : : int y,
373 : : int zoom_level)
374 : : {
375 : 6 : 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 : 12 : shumate_data_source_request_emit_data (ShumateDataSourceRequest *self,
398 : : GBytes *data,
399 : : gboolean complete)
400 : : {
401 : 12 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
402 : 12 : g_autofree char *profiling_desc = NULL;
403 : :
404 [ + - ]: 12 : g_return_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self));
405 [ - + ]: 12 : g_return_if_fail (data != NULL);
406 [ - + ]: 12 : g_return_if_fail (!priv->completed);
407 : :
408 [ + + + - ]: 12 : if (priv->bytes != NULL && g_bytes_equal (data, priv->bytes))
409 : : return;
410 : :
411 [ + + ]: 12 : g_clear_pointer (&priv->bytes, g_bytes_unref);
412 : 12 : priv->bytes = g_bytes_ref (data);
413 : :
414 [ + + ]: 12 : if (complete)
415 : 3 : priv->completed = TRUE;
416 : :
417 : 12 : profiling_desc = g_strdup_printf ("(%d, %d) @ %d", priv->pos.x, priv->pos.y, priv->pos.zoom);
418 : 24 : SHUMATE_PROFILE_START_NAMED (emit_data);
419 : 12 : g_object_notify_by_pspec ((GObject *)self, properties[PROP_DATA]);
420 : 12 : SHUMATE_PROFILE_END_NAMED (emit_data, profiling_desc);
421 : :
422 [ + + ]: 12 : if (complete)
423 : 3 : 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 : 3 : shumate_data_source_request_emit_error (ShumateDataSourceRequest *self,
441 : : const GError *error)
442 : : {
443 : 3 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
444 : :
445 [ + - ]: 3 : g_return_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self));
446 [ - + ]: 3 : g_return_if_fail (error != NULL);
447 [ - + ]: 3 : g_return_if_fail (!priv->completed);
448 : :
449 : 3 : g_clear_error (&priv->error);
450 : 3 : priv->error = g_error_copy (error);
451 : :
452 : 3 : priv->completed = TRUE;
453 : :
454 [ + - ]: 3 : if (priv->bytes)
455 : : {
456 : 3 : g_clear_pointer (&priv->bytes, g_bytes_unref);
457 : 3 : g_object_notify_by_pspec ((GObject *)self, properties[PROP_DATA]);
458 : : }
459 : :
460 : 3 : g_object_notify_by_pspec ((GObject *)self, properties[PROP_ERROR]);
461 : 3 : 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 : 0 : shumate_data_source_request_complete (ShumateDataSourceRequest *self)
478 : : {
479 : 0 : ShumateDataSourceRequestPrivate *priv = shumate_data_source_request_get_instance_private (self);
480 : :
481 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_DATA_SOURCE_REQUEST (self));
482 [ # # ]: 0 : g_return_if_fail (!priv->completed);
483 [ # # # # ]: 0 : g_return_if_fail (priv->bytes != NULL || priv->error != NULL);
484 : :
485 : 0 : priv->completed = TRUE;
486 : 0 : g_object_notify_by_pspec ((GObject *)self, properties[PROP_COMPLETED]);
487 : : }
|