Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 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/gi18n-lib.h>
26 : : #include <glib.h>
27 : : #include <glib-object.h>
28 : : #include <gio/gio.h>
29 : : #include <libmogwai-tariff/period.h>
30 : : #include <libmogwai-tariff/tariff-builder.h>
31 : : #include <libmogwai-tariff/tariff.h>
32 : :
33 : :
34 : : static void mwt_tariff_builder_dispose (GObject *object);
35 : :
36 : : /**
37 : : * MwtTariffBuilder:
38 : : *
39 : : * A helper object for constructing an #MwtTariff and serialising it to a
40 : : * #GBytes which can be transmitted or stored. See #MwtTariffLoader for the
41 : : * inverse operation.
42 : : *
43 : : * When using a #MwtTariffBuilder, all the required properties of the tariff
44 : : * must be set (including at least one period), then
45 : : * mwt_tariff_builder_get_tariff() can be used to get the resulting #MwtTariff
46 : : * object (similarly for the variants to return it as bytes or a #GVariant).
47 : : * Before then, mwt_tariff_builder_get_tariff() will return %NULL.
48 : : *
49 : : * A #MwtTariffBuilder may be used multiple times, or an in-progress tariff may
50 : : * be destroyed by using mwt_tariff_builder_reset().
51 : : *
52 : : * Since: 0.1.0
53 : : */
54 : : struct _MwtTariffBuilder
55 : : {
56 : : GObject parent;
57 : :
58 : : gchar *name; /* (owned) */
59 : : GPtrArray *periods; /* (element-type MwtPeriod) (owned) */
60 : :
61 : : MwtTariff *final_tariff; /* (nullable) (owned) */
62 : : GVariant *final_variant; /* (nullable) (owned) */
63 : : };
64 : :
65 [ + + + - : 86 : G_DEFINE_TYPE (MwtTariffBuilder, mwt_tariff_builder, G_TYPE_OBJECT)
+ + ]
66 : :
67 : : static void
68 : 5 : mwt_tariff_builder_class_init (MwtTariffBuilderClass *klass)
69 : : {
70 : 5 : GObjectClass *object_class = (GObjectClass *) klass;
71 : :
72 : 5 : object_class->dispose = mwt_tariff_builder_dispose;
73 : 5 : }
74 : :
75 : : static void
76 : 8 : mwt_tariff_builder_init (MwtTariffBuilder *self)
77 : : {
78 : 8 : self->periods = g_ptr_array_new_with_free_func (g_object_unref);
79 : 8 : }
80 : :
81 : : static void
82 : 8 : mwt_tariff_builder_dispose (GObject *object)
83 : : {
84 : 8 : MwtTariffBuilder *self = MWT_TARIFF_BUILDER (object);
85 : :
86 [ + + ]: 8 : g_clear_pointer (&self->name, g_free);
87 [ + - ]: 8 : g_clear_pointer (&self->periods, g_ptr_array_unref);
88 [ + + ]: 8 : g_clear_object (&self->final_tariff);
89 [ + + ]: 8 : g_clear_pointer (&self->final_variant, g_variant_unref);
90 : :
91 : : /* Chain up to the parent class */
92 : 8 : G_OBJECT_CLASS (mwt_tariff_builder_parent_class)->dispose (object);
93 : 8 : }
94 : :
95 : : /**
96 : : * mwt_tariff_builder_new:
97 : : *
98 : : * Create a new, empty #MwtTariffBuilder.
99 : : *
100 : : * Returns: (transfer full): a new #MwtTariffBuilder
101 : : * Since: 0.1.0
102 : : */
103 : : MwtTariffBuilder *
104 : 8 : mwt_tariff_builder_new (void)
105 : : {
106 : 8 : return g_object_new (MWT_TYPE_TARIFF_BUILDER, NULL);
107 : : }
108 : :
109 : : /**
110 : : * mwt_tariff_builder_reset:
111 : : * @self: a #MwtTariffBuilder
112 : : *
113 : : * Reset the state of the builder, clearing any completed or in-progress
114 : : * tariffs.
115 : : *
116 : : * Since: 0.1.0
117 : : */
118 : : void
119 : 3 : mwt_tariff_builder_reset (MwtTariffBuilder *self)
120 : : {
121 [ - + ]: 3 : g_return_if_fail (MWT_IS_TARIFF_BUILDER (self));
122 : :
123 [ + + ]: 3 : g_clear_pointer (&self->name, g_free);
124 : 3 : g_ptr_array_set_size (self->periods, 0);
125 [ - + ]: 3 : g_clear_object (&self->final_tariff);
126 [ - + ]: 3 : g_clear_pointer (&self->final_variant, g_variant_unref);
127 : : }
128 : :
129 : : /**
130 : : * mwt_tariff_builder_set_name:
131 : : * @self: a #MwtTariffBuilder
132 : : * @name: name for the new tariff
133 : : *
134 : : * Set the name for the tariff under construction. See #MwtTariff:name for
135 : : * details of valid names.
136 : : *
137 : : * Since: 0.1.0
138 : : */
139 : : void
140 : 7 : mwt_tariff_builder_set_name (MwtTariffBuilder *self,
141 : : const gchar *name)
142 : : {
143 [ - + ]: 7 : g_return_if_fail (MWT_IS_TARIFF_BUILDER (self));
144 [ - + ]: 7 : g_return_if_fail (mwt_tariff_validate_name (name));
145 : :
146 : 7 : g_free (self->name);
147 : 7 : self->name = g_strdup (name);
148 : : }
149 : :
150 : : /**
151 : : * mwt_tariff_builder_add_period:
152 : : * @self: a #MwtTariffBuilder
153 : : * @period: (transfer none): a #MwtPeriod to add to the tariff
154 : : *
155 : : * Add the given #MwtPeriod to the tariff under construction. This may be called
156 : : * multiple times for a given tariff, and must be called at least once per valid
157 : : * tariff.
158 : : *
159 : : * Periods may be added in any order; they will be sorted before the tariff is
160 : : * generated.
161 : : *
162 : : * Since: 0.1.0
163 : : */
164 : : void
165 : 9 : mwt_tariff_builder_add_period (MwtTariffBuilder *self,
166 : : MwtPeriod *period)
167 : : {
168 [ - + ]: 9 : g_return_if_fail (MWT_IS_TARIFF_BUILDER (self));
169 [ - + ]: 9 : g_return_if_fail (MWT_IS_PERIOD (period));
170 : :
171 : 9 : g_ptr_array_add (self->periods, g_object_ref (period));
172 : : }
173 : :
174 : : /* Order by decreasing span, then by increasing start date/time. */
175 : : static gint
176 : 3 : periods_sort_cb (gconstpointer a,
177 : : gconstpointer b)
178 : : {
179 : 3 : MwtPeriod *p1 = *((MwtPeriod **) a);
180 : 3 : MwtPeriod *p2 = *((MwtPeriod **) b);
181 : :
182 : 3 : GDateTime *p1_start = mwt_period_get_start (p1);
183 : 3 : GDateTime *p1_end = mwt_period_get_end (p2);
184 : 3 : GDateTime *p2_start = mwt_period_get_start (p1);
185 : 3 : GDateTime *p2_end = mwt_period_get_end (p2);
186 : :
187 : 3 : GTimeSpan p1_span = g_date_time_difference (p1_end, p1_start);
188 : 3 : GTimeSpan p2_span = g_date_time_difference (p2_end, p2_start);
189 : :
190 [ + - ]: 3 : if (p1_span == p2_span)
191 : 3 : return g_date_time_compare (p1_start, p2_start);
192 : :
193 [ # # ]: 0 : return (p1_span > p2_span) ? -1 : 1;
194 : : }
195 : :
196 : : /**
197 : : * mwt_tariff_builder_get_tariff:
198 : : * @self: a #MwtTariffBuilder
199 : : *
200 : : * Get the newly constructed #MwtTariff, or %NULL if the builder is incomplete,
201 : : * has been reset, or if there was an error building the tariff. The tariff can
202 : : * be retrieved multiple times from this function; the builder is not reset
203 : : * after this function is called.
204 : : *
205 : : * Returns: (transfer full) (nullable): the constructed #MwtTariff, or %NULL if
206 : : * one is not currently constructed
207 : : * Since: 0.1.0
208 : : */
209 : : MwtTariff *
210 : 19 : mwt_tariff_builder_get_tariff (MwtTariffBuilder *self)
211 : : {
212 : 19 : g_autoptr(GError) local_error = NULL;
213 : :
214 [ - + ]: 19 : g_return_val_if_fail (MWT_IS_TARIFF_BUILDER (self), NULL);
215 : :
216 : : /* If we haven’t constructed the final tariff yet, try. If it fails, just
217 : : * return %NULL since either the builder is unused, or the developer has made
218 : : * an error. */
219 [ + + ]: 19 : if (self->final_tariff == NULL)
220 : : {
221 : : /* Ensure the periods are in order. */
222 : 17 : g_ptr_array_sort (self->periods, periods_sort_cb);
223 : :
224 [ + + ]: 17 : if (!mwt_tariff_validate (self->name, self->periods, &local_error))
225 : : {
226 : 12 : g_debug ("Invalid tariff: %s", local_error->message);
227 : 12 : return NULL;
228 : : }
229 : 5 : self->final_tariff = mwt_tariff_new (self->name, self->periods);
230 : : }
231 : :
232 : 7 : return g_object_ref (self->final_tariff);
233 : : }
234 : :
235 : : /* Returns a new floating variant. */
236 : : static GVariant *
237 : 5 : mwt_tariff_builder_build_tariff_variant (const gchar *name,
238 : : MwtTariff *tariff)
239 : : {
240 : 5 : const guint16 format_version = 2;
241 : 5 : const gchar *format_magic = "Mogwai tariff";
242 : 10 : g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(sa(ttssqut))"));
243 : :
244 : 5 : g_variant_builder_add (&builder, "s", name);
245 : :
246 : : /* Periods. mwt_tariff_get_periods() guarantees it’s in order. */
247 : 5 : GPtrArray *periods = mwt_tariff_get_periods (tariff);
248 : 5 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(ttssqut)"));
249 : :
250 [ + + ]: 13 : for (gsize i = 0; i < periods->len; i++)
251 : : {
252 : 8 : MwtPeriod *period = g_ptr_array_index (periods, i);
253 : 8 : GDateTime *start = mwt_period_get_start (period);
254 : 8 : GTimeZone *start_tz = g_date_time_get_timezone (start);
255 : 8 : GDateTime *end = mwt_period_get_end (period);
256 : 8 : GTimeZone *end_tz = g_date_time_get_timezone (end);
257 : 8 : guint64 start_unix = g_date_time_to_unix (start);
258 : 8 : guint64 end_unix = g_date_time_to_unix (end);
259 : :
260 : 8 : g_variant_builder_open (&builder, G_VARIANT_TYPE ("(ttssqut)"));
261 : 8 : g_variant_builder_add (&builder, "t", start_unix);
262 : 8 : g_variant_builder_add (&builder, "t", end_unix);
263 : 8 : g_variant_builder_add (&builder, "s", g_time_zone_get_identifier (start_tz));
264 : 8 : g_variant_builder_add (&builder, "s", g_time_zone_get_identifier (end_tz));
265 : 8 : g_variant_builder_add (&builder, "q", (guint16) mwt_period_get_repeat_type (period));
266 : 8 : g_variant_builder_add (&builder, "u", (guint32) mwt_period_get_repeat_period (period));
267 : 8 : g_variant_builder_add (&builder, "t", mwt_period_get_capacity_limit (period));
268 : 8 : g_variant_builder_close (&builder);
269 : : }
270 : :
271 : 5 : g_variant_builder_close (&builder);
272 : :
273 : : /* Add the file format version, which also acts as a byte order mark (so
274 : : * explicitly don’t convert it to a known endianness). The magic bytes allow
275 : : * content type detection. */
276 : 5 : return g_variant_new ("(sqv)",
277 : : format_magic, format_version,
278 : : g_variant_builder_end (&builder));
279 : : }
280 : :
281 : : /**
282 : : * mwt_tariff_builder_get_tariff_as_variant:
283 : : * @self: a #MwtTariffBuilder
284 : : *
285 : : * Get the newly constructed tariff as a #GVariant. This will return %NULL in
286 : : * exactly the same situations as when mwt_tariff_builder_get_tariff() returns
287 : : * %NULL.
288 : : *
289 : : * The returned #GVariant is guaranteed to be in normal form, and a non-floating
290 : : * reference will be returned.
291 : : *
292 : : * Returns: (transfer full) (nullable): a new, non-floating ref to the
293 : : * constructed tariff as a #GVariant, or %NULL if one is not currently
294 : : * constructed
295 : : * Since: 0.1.0
296 : : */
297 : : GVariant *
298 : 14 : mwt_tariff_builder_get_tariff_as_variant (MwtTariffBuilder *self)
299 : : {
300 [ - + ]: 14 : g_return_val_if_fail (MWT_IS_TARIFF_BUILDER (self), NULL);
301 : :
302 [ + + ]: 14 : if (self->final_variant == NULL)
303 : : {
304 [ + + ]: 26 : g_autoptr(MwtTariff) tariff = mwt_tariff_builder_get_tariff (self);
305 [ + + ]: 13 : if (tariff == NULL)
306 : 8 : return NULL;
307 : :
308 : 5 : g_autoptr(GVariant) variant = mwt_tariff_builder_build_tariff_variant (self->name, tariff);
309 : 5 : self->final_variant = g_variant_ref_sink (g_steal_pointer (&variant));
310 : : }
311 : :
312 : 6 : return g_variant_ref (self->final_variant);
313 : : }
314 : :
315 : : /**
316 : : * mwt_tariff_builder_get_tariff_as_bytes:
317 : : * @self: a #MwtTariffBuilder
318 : : *
319 : : * Get the newly constructed tariff as a #GBytes. This will return %NULL in
320 : : * exactly the same situations as when mwt_tariff_builder_get_tariff() returns
321 : : * %NULL.
322 : : *
323 : : * The returned #GBytes is suitable to be written to a file or sent over the
324 : : * network. Its byte ordering is encoded so it may be loaded on a system with
325 : : * a different byte ordering.
326 : : *
327 : : * Returns: (transfer full) (nullable): the constructed tariff as a #GBytes,
328 : : * or %NULL if one is not currently constructed
329 : : * Since: 0.1.0
330 : : */
331 : : GBytes *
332 : 8 : mwt_tariff_builder_get_tariff_as_bytes (MwtTariffBuilder *self)
333 : : {
334 [ - + ]: 8 : g_return_val_if_fail (MWT_IS_TARIFF_BUILDER (self), NULL);
335 : :
336 : 16 : g_autoptr(GVariant) variant = mwt_tariff_builder_get_tariff_as_variant (self);
337 [ + + ]: 8 : if (variant == NULL)
338 : 4 : return NULL;
339 : :
340 : 4 : return g_variant_get_data_as_bytes (variant);
341 : : }
|