Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2019 Endless Mobile, Inc.
4 : : * Copyright 2025 GNOME Foundation, Inc.
5 : : *
6 : : * SPDX-License-Identifier: LGPL-2.1-or-later
7 : : *
8 : : * This library is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU Lesser General Public
10 : : * License as published by the Free Software Foundation; either
11 : : * version 2.1 of the License, or (at your option) any later version.
12 : : *
13 : : * This library is distributed in the hope that it will be useful,
14 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : : * Lesser General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU Lesser General Public
19 : : * License along with this library; if not, write to the Free Software
20 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 : : *
22 : : * Authors:
23 : : * - Philip Withnall <withnall@endlessm.com>
24 : : */
25 : :
26 : : #include "config.h"
27 : :
28 : : #include <glib.h>
29 : : #include <glib-object.h>
30 : : #include <glib/gi18n-lib.h>
31 : : #include <gio/gio.h>
32 : : #include <libmalcontent/manager.h>
33 : : #include <libmalcontent/session-limits.h>
34 : :
35 : : #include "libmalcontent/session-limits-private.h"
36 : :
37 : :
38 : : /* struct _MctSessionLimits is defined in session-limits-private.h */
39 : :
40 [ + - + - : 6 : G_DEFINE_BOXED_TYPE (MctSessionLimits, mct_session_limits,
+ - ]
41 : : mct_session_limits_ref, mct_session_limits_unref)
42 : :
43 : : /**
44 : : * mct_session_limits_ref:
45 : : * @limits: (transfer none): a [struct@Malcontent.SessionLimits]
46 : : *
47 : : * Increment the reference count of @limits, and return the same pointer to it.
48 : : *
49 : : * Returns: (transfer full): the same pointer as @limits
50 : : * Since: 0.5.0
51 : : */
52 : : MctSessionLimits *
53 : 4 : mct_session_limits_ref (MctSessionLimits *limits)
54 : : {
55 : 4 : g_return_val_if_fail (limits != NULL, NULL);
56 : 4 : g_return_val_if_fail (limits->ref_count >= 1, NULL);
57 : 4 : g_return_val_if_fail (limits->ref_count <= G_MAXINT - 1, NULL);
58 : :
59 : 4 : limits->ref_count++;
60 : 4 : return limits;
61 : : }
62 : :
63 : : /**
64 : : * mct_session_limits_unref:
65 : : * @limits: (transfer full): a [struct@Malcontent.SessionLimits]
66 : : *
67 : : * Decrement the reference count of @limits. If the reference count reaches
68 : : * zero, free the @limits and all its resources.
69 : : *
70 : : * Since: 0.5.0
71 : : */
72 : : void
73 : 81 : mct_session_limits_unref (MctSessionLimits *limits)
74 : : {
75 : 81 : g_return_if_fail (limits != NULL);
76 : 81 : g_return_if_fail (limits->ref_count >= 1);
77 : :
78 : 81 : limits->ref_count--;
79 : :
80 [ + + ]: 81 : if (limits->ref_count <= 0)
81 : : {
82 : 77 : g_free (limits);
83 : : }
84 : : }
85 : :
86 : : /**
87 : : * mct_session_limits_get_user_id:
88 : : * @limits: a [struct@Malcontent.SessionLimits]
89 : : *
90 : : * Get the user ID of the user this [struct@Malcontent.SessionLimits] is for.
91 : : *
92 : : * Returns: user ID of the relevant user, or `(uid_t) -1` if unknown
93 : : * Since: 0.5.0
94 : : */
95 : : uid_t
96 : 3 : mct_session_limits_get_user_id (MctSessionLimits *limits)
97 : : {
98 : 3 : g_return_val_if_fail (limits != NULL, (uid_t) -1);
99 : 3 : g_return_val_if_fail (limits->ref_count >= 1, (uid_t) -1);
100 : :
101 : 3 : return limits->user_id;
102 : : }
103 : :
104 : : /**
105 : : * mct_session_limits_is_enabled:
106 : : * @limits: a [struct@Malcontent.SessionLimits]
107 : : *
108 : : * Check whether any session limits are enabled and are going to impose at least
109 : : * one restriction on the user.
110 : : *
111 : : * This gives a high level view of whether session limit parental controls are
112 : : * ‘enabled’ for the given user.
113 : : *
114 : : * This function is equivalent to the value returned by the
115 : : * `time_limit_enabled_out` argument of
116 : : * [method@Malcontent.SessionLimits.check_time_remaining].
117 : : *
118 : : * Returns: true if the session limits object contains at least one restrictive
119 : : * session limit, false if there are no limits in place
120 : : * Since: 0.7.0
121 : : */
122 : : gboolean
123 : 5 : mct_session_limits_is_enabled (MctSessionLimits *limits)
124 : : {
125 : 5 : g_return_val_if_fail (limits != NULL, FALSE);
126 : 5 : g_return_val_if_fail (limits->ref_count >= 1, FALSE);
127 : :
128 : 5 : return (limits->limit_type != MCT_SESSION_LIMITS_TYPE_NONE);
129 : : }
130 : :
131 : : /**
132 : : * mct_session_limits_get_daily_schedule:
133 : : * @limits: a [struct@Malcontent.SessionLimits]
134 : : * @out_start_time_secs: (out) (optional): return location for the earliest
135 : : * allowable session start time for the user, in seconds since midnight
136 : : * @out_end_time_secs: (out) (optional): return location for the latest
137 : : * allowable session end time for the user, in seconds since midnight
138 : : *
139 : : * Get the daily schedule session limits, if set.
140 : : *
141 : : * If set, sessions are allowed between the given start and end time every day.
142 : : * The times are given as offsets from the start of the day, in seconds.
143 : : *
144 : : * @out_end_time_secs is guaranteed to be greater than @out_start_time_secs, if
145 : : * they are set. @out_end_time_secs is guaranteed to be at most `24 * 60 * 60`
146 : : * if set.
147 : : *
148 : : * Returns: true if a daily schedule is set, false otherwise
149 : : * Since: 0.14.0
150 : : */
151 : : gboolean
152 : 16 : mct_session_limits_get_daily_schedule (MctSessionLimits *limits,
153 : : unsigned int *out_start_time_secs,
154 : : unsigned int *out_end_time_secs)
155 : : {
156 : 16 : g_return_val_if_fail (limits != NULL, FALSE);
157 : 16 : g_return_val_if_fail (limits->ref_count >= 1, FALSE);
158 : :
159 [ + - ]: 16 : if (out_start_time_secs != NULL)
160 [ + + ]: 16 : *out_start_time_secs = (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE) ? limits->daily_start_time : 0;
161 [ + - ]: 16 : if (out_end_time_secs != NULL)
162 [ + + ]: 16 : *out_end_time_secs = (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE) ? limits->daily_end_time : 0;
163 : :
164 : 16 : return (limits->limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE);
165 : : }
166 : :
167 : : /**
168 : : * mct_session_limits_get_daily_limit:
169 : : * @limits: a [struct@Malcontent.SessionLimits]
170 : : * @out_daily_limit_secs: (out) (optional): return location for the maximum
171 : : * amount of active session time allowed per day for the user, in seconds
172 : : *
173 : : * Get the daily limit, if set.
174 : : *
175 : : * If set, sessions are allowed to be up to the given limit in length every day.
176 : : *
177 : : * @out_daily_limit_secs is guaranteed to be at most `24 * 60 * 60` if set.
178 : : *
179 : : * Returns: true if a daily limit is set, false otherwise
180 : : * Since: 0.14.0
181 : : */
182 : : gboolean
183 : 4 : mct_session_limits_get_daily_limit (MctSessionLimits *limits,
184 : : unsigned int *out_daily_limit_secs)
185 : : {
186 : 4 : g_return_val_if_fail (limits != NULL, FALSE);
187 : 4 : g_return_val_if_fail (limits->ref_count >= 1, FALSE);
188 : :
189 [ + - ]: 4 : if (out_daily_limit_secs != NULL)
190 [ + - ]: 4 : *out_daily_limit_secs = (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT) ? limits->daily_limit_secs : 0;
191 : :
192 : 4 : return (limits->limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT);
193 : : }
194 : :
195 : : /**
196 : : * mct_session_limits_check_time_remaining:
197 : : * @limits: a [struct@Malcontent.SessionLimits]
198 : : * @now_dt: current time in the user’s timezone, typically queried using
199 : : * `g_date_time_new_now_local()`
200 : : * @active_session_time_today_secs: total time the user has spent in an active
201 : : * session so far today, in seconds
202 : : * @time_remaining_secs_out: (out) (optional): return location for the number
203 : : * of seconds remaining before the user’s session has to end, if limits are
204 : : * in force
205 : : * @time_limit_enabled_out: (out) (optional): return location for whether time
206 : : * limits are enabled for this user
207 : : *
208 : : * Check whether the user has time remaining in which they are allowed to use
209 : : * the computer.
210 : : *
211 : : * This assumes that @now_dt is the current time and
212 : : * @active_session_time_today_secs is the total amount of time the user has
213 : : * spent in an active session up to that point today, and applies the
214 : : * session limit policy from @limits to them.
215 : : *
216 : : * This will return whether the user is allowed to use the computer now; further
217 : : * information about the policy and remaining time is provided in
218 : : * @time_remaining_secs_out and @time_limit_enabled_out.
219 : : *
220 : : * Returns: true if the user this @limits corresponds to is allowed to be in
221 : : * an active session at the given time; false otherwise
222 : : * Since: 0.14.0
223 : : */
224 : : gboolean
225 : 143 : mct_session_limits_check_time_remaining (MctSessionLimits *limits,
226 : : GDateTime *now_dt,
227 : : uint64_t active_session_time_today_secs,
228 : : guint64 *time_remaining_secs_out,
229 : : gboolean *time_limit_enabled_out)
230 : : {
231 : : guint64 time_remaining_secs;
232 : 143 : gboolean time_limit_enabled = FALSE;
233 : 143 : gboolean user_allowed_now = TRUE;
234 : : guint64 now_time_of_day_secs;
235 : :
236 : 143 : g_return_val_if_fail (limits != NULL, FALSE);
237 : 143 : g_return_val_if_fail (limits->ref_count >= 1, FALSE);
238 : 143 : g_return_val_if_fail (now_dt != NULL, FALSE);
239 : :
240 : : /* Helper calculations. If we end up with a @now_dt before the UNIX epoch,
241 : : * the caller has provided a date very far in the future which we don’t yet
242 : : * support.
243 : : *
244 : : * This needs to be in the user’s local timezone, because `DAILY_SCHEDULE`
245 : : * limits are essentially in the local timezone by virtue of being wall clock
246 : : * times. */
247 [ + + ]: 143 : if (g_date_time_to_unix (now_dt) < 0)
248 : : {
249 : 2 : time_remaining_secs = 0;
250 : 2 : time_limit_enabled = TRUE;
251 : 2 : user_allowed_now = FALSE;
252 : 2 : goto out;
253 : : }
254 : :
255 : 141 : now_time_of_day_secs = ((g_date_time_get_hour (now_dt) * 60 +
256 : 141 : g_date_time_get_minute (now_dt)) * 60 +
257 : 141 : g_date_time_get_second (now_dt));
258 : 141 : time_remaining_secs = 24 * 60 * 60 - now_time_of_day_secs;
259 : :
260 : : /* Work out the limits. */
261 [ + + ]: 141 : if (limits->limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
262 : : {
263 [ + - ]: 178 : user_allowed_now = user_allowed_now &&
264 [ + + ]: 89 : (now_time_of_day_secs >= limits->daily_start_time &&
265 [ + + ]: 59 : now_time_of_day_secs < limits->daily_end_time);
266 [ + + ]: 89 : time_remaining_secs = user_allowed_now ? MIN (time_remaining_secs, limits->daily_end_time - now_time_of_day_secs) : 0;
267 : 89 : time_limit_enabled = TRUE;
268 : :
269 : 89 : g_debug ("%s: Daily schedule limit allowed in %u–%u (now is %"
270 : : G_GUINT64_FORMAT "); %" G_GUINT64_FORMAT " seconds remaining",
271 : : G_STRFUNC, limits->daily_start_time, limits->daily_end_time,
272 : : now_time_of_day_secs, time_remaining_secs);
273 : : }
274 : :
275 [ + + ]: 141 : if (limits->limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT)
276 : : {
277 [ + + ]: 52 : user_allowed_now = (user_allowed_now &&
278 [ + + ]: 24 : active_session_time_today_secs < limits->daily_limit_secs);
279 [ + + ]: 28 : time_remaining_secs = user_allowed_now ? MIN (time_remaining_secs, limits->daily_limit_secs - active_session_time_today_secs) : 0;
280 : 28 : time_limit_enabled = TRUE;
281 : :
282 : 28 : g_debug ("%s: Daily limit allowed up to %u (currently used %"
283 : : G_GUINT64_FORMAT "); %" G_GUINT64_FORMAT " seconds remaining",
284 : : G_STRFUNC, limits->daily_limit_secs,
285 : : active_session_time_today_secs, time_remaining_secs);
286 : : }
287 : :
288 [ + + ]: 141 : if (limits->limit_type == MCT_SESSION_LIMITS_TYPE_NONE)
289 : : {
290 : 40 : user_allowed_now = TRUE;
291 : 40 : time_remaining_secs = G_MAXUINT64;
292 : 40 : time_limit_enabled = FALSE;
293 : :
294 : 40 : g_debug ("%s: No limit enabled", G_STRFUNC);
295 : : }
296 : :
297 : 101 : out:
298 : : /* Postconditions. */
299 : 143 : g_assert (!user_allowed_now || time_remaining_secs > 0);
300 : 143 : g_assert (user_allowed_now || time_remaining_secs == 0);
301 : 143 : g_assert (time_limit_enabled || time_remaining_secs == G_MAXUINT64);
302 : :
303 : : /* Output. */
304 [ + + ]: 143 : if (time_remaining_secs_out != NULL)
305 : 43 : *time_remaining_secs_out = time_remaining_secs;
306 [ + + ]: 143 : if (time_limit_enabled_out != NULL)
307 : 43 : *time_limit_enabled_out = time_limit_enabled;
308 : :
309 : 143 : return user_allowed_now;
310 : : }
311 : :
312 : : /**
313 : : * mct_session_limits_serialize:
314 : : * @limits: a [struct@Malcontent.SessionLimits]
315 : : *
316 : : * Build a [struct@GLib.Variant] which contains the session limits from @limits,
317 : : * in an opaque variant format.
318 : : *
319 : : * This format may change in future, but
320 : : * [func@Malcontent.SessionLimits.deserialize] is guaranteed to always be able
321 : : * to load any variant produced by the current or any previous version of
322 : : * [method@Malcontent.SessionLimits.serialize].
323 : : *
324 : : * Returns: (transfer floating): a new, floating [struct@GLib.Variant]
325 : : * containing the session limits
326 : : * Since: 0.7.0
327 : : */
328 : : GVariant *
329 : 9 : mct_session_limits_serialize (MctSessionLimits *limits)
330 : : {
331 : 18 : g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{sv}"));
332 : :
333 : 9 : g_return_val_if_fail (limits != NULL, NULL);
334 : 9 : g_return_val_if_fail (limits->ref_count >= 1, NULL);
335 : :
336 : : /* The serialisation format is exactly the
337 : : * `com.endlessm.ParentalControls.SessionLimits` D-Bus interface. */
338 [ + + ]: 9 : if (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
339 : 4 : g_variant_builder_add (&builder, "{sv}", "DailySchedule",
340 : : g_variant_new ("(uu)",
341 : : limits->daily_start_time,
342 : : limits->daily_end_time));
343 : :
344 [ - + ]: 9 : if (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT)
345 : 0 : g_variant_builder_add (&builder, "{sv}", "DailyLimit",
346 : : g_variant_new ("u", limits->daily_limit_secs));
347 : :
348 : 9 : g_variant_builder_add (&builder, "{sv}", "LimitType",
349 : 9 : g_variant_new_uint32 (limits->limit_type));
350 : :
351 : 9 : return g_variant_builder_end (&builder);
352 : : }
353 : :
354 : : /**
355 : : * mct_session_limits_deserialize:
356 : : * @variant: a serialized session limits variant
357 : : * @user_id: the ID of the user the session limits relate to
358 : : * @error: return location for a [type@GLib.Error], or `NULL`
359 : : *
360 : : * Deserialize a set of session limits previously serialized with
361 : : * [method@Malcontent.SessionLimits.serialize].
362 : : *
363 : : * This function guarantees to be able to deserialize any serialized form from
364 : : * this version or older versions of libmalcontent.
365 : : *
366 : : * If deserialization fails, [error@Malcontent.ManagerError.INVALID_DATA] will
367 : : * be returned.
368 : : *
369 : : * Returns: (transfer full): deserialized session limits
370 : : * Since: 0.7.0
371 : : */
372 : : MctSessionLimits *
373 : 43 : mct_session_limits_deserialize (GVariant *variant,
374 : : uid_t user_id,
375 : : GError **error)
376 : : {
377 : 43 : g_autoptr(MctSessionLimits) session_limits = NULL;
378 : : guint32 limit_type;
379 : 43 : MctSessionLimitsType set_limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
380 : : guint32 daily_start_time, daily_end_time;
381 : : uint32_t daily_limit_secs;
382 : :
383 : 43 : g_return_val_if_fail (variant != NULL, NULL);
384 : 43 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
385 : :
386 : : /* Check the overall type. */
387 [ + + ]: 43 : if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("a{sv}")))
388 : : {
389 : 4 : g_set_error (error, MCT_MANAGER_ERROR,
390 : : MCT_MANAGER_ERROR_INVALID_DATA,
391 : : _("Session limit for user %u was in an unrecognized format"),
392 : : (guint) user_id);
393 : 4 : return NULL;
394 : : }
395 : :
396 : : /* Extract the properties we care about. The default values here should be
397 : : * kept in sync with those in the `com.endlessm.ParentalControls.SessionLimits`
398 : : * D-Bus interface. */
399 [ + + ]: 39 : if (!g_variant_lookup (variant, "LimitType", "u",
400 : : &limit_type))
401 : : {
402 : : /* Default value. */
403 : 20 : limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
404 : : }
405 : :
406 : : /* Check that the limit type is something we support. */
407 : : G_STATIC_ASSERT (sizeof (limit_type) >= sizeof (MctSessionLimitsType));
408 : :
409 [ + + ]: 39 : if (((guint) limit_type & ~MCT_SESSION_LIMITS_TYPE_MASK) != 0)
410 : : {
411 : 2 : g_set_error (error, MCT_MANAGER_ERROR,
412 : : MCT_MANAGER_ERROR_INVALID_DATA,
413 : : _("Session limit for user %u has an unrecognized type ‘%u’"),
414 : : (guint) user_id, limit_type);
415 : 2 : return NULL;
416 : : }
417 : :
418 : : /* Daily schedule */
419 [ + + ]: 37 : if (!g_variant_lookup (variant, "DailySchedule", "(uu)",
420 : : &daily_start_time, &daily_end_time))
421 : : {
422 : : /* Default value. */
423 : 16 : daily_start_time = 0;
424 : 16 : daily_end_time = 24 * 60 * 60;
425 : : }
426 : : else
427 : : {
428 : 21 : set_limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE;
429 : : }
430 : :
431 [ + + ]: 37 : if (daily_start_time >= daily_end_time ||
432 [ + + ]: 35 : daily_end_time > 24 * 60 * 60)
433 : : {
434 : 4 : g_set_error (error, MCT_MANAGER_ERROR,
435 : : MCT_MANAGER_ERROR_INVALID_DATA,
436 : : _("Session limit for user %u has invalid daily schedule %u–%u"),
437 : : (guint) user_id, daily_start_time, daily_end_time);
438 : 4 : return NULL;
439 : : }
440 : :
441 : : /* Daily limit */
442 [ + + ]: 33 : if (!g_variant_lookup (variant, "DailyLimit", "u", &daily_limit_secs))
443 : : {
444 : : /* Default value. */
445 : 21 : daily_limit_secs = 24 * 60 * 60;
446 : : }
447 : : else
448 : : {
449 : 12 : set_limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT;
450 : : }
451 : :
452 [ + + ]: 33 : if (daily_limit_secs > 24 * 60 * 60)
453 : : {
454 : 2 : g_set_error (error, MCT_MANAGER_ERROR,
455 : : MCT_MANAGER_ERROR_INVALID_DATA,
456 : : _("Session limit for user %u has invalid daily limit %u"),
457 : : (guint) user_id, daily_limit_secs);
458 : 2 : return NULL;
459 : : }
460 : :
461 : : /* Ensure @set_limit_type is equal to, or a superset of, @limit_type. */
462 : 31 : set_limit_type |= limit_type;
463 : :
464 : : /* Success. Create an `MctSessionLimits` object to contain the results. */
465 : 31 : session_limits = g_new0 (MctSessionLimits, 1);
466 : 31 : session_limits->ref_count = 1;
467 : 31 : session_limits->user_id = user_id;
468 : 31 : session_limits->limit_type = limit_type;
469 : 31 : session_limits->set_limit_type = set_limit_type;
470 : 31 : session_limits->daily_start_time = daily_start_time;
471 : 31 : session_limits->daily_end_time = daily_end_time;
472 : 31 : session_limits->daily_limit_secs = daily_limit_secs;
473 : :
474 : 31 : return g_steal_pointer (&session_limits);
475 : : }
476 : :
477 : : /**
478 : : * mct_session_limits_equal:
479 : : * @a: (not nullable): a session limits configuration
480 : : * @b: (not nullable): a session limits configuration
481 : : *
482 : : * Check whether session limits configurations @a and @b are equal.
483 : : *
484 : : * Returns: true if @a and @b are equal, false otherwise
485 : : * Since: 0.14.0
486 : : */
487 : : gboolean
488 : 134 : mct_session_limits_equal (MctSessionLimits *a,
489 : : MctSessionLimits *b)
490 : : {
491 : 134 : g_return_val_if_fail (a != NULL, FALSE);
492 : 134 : g_return_val_if_fail (a->ref_count >= 1, FALSE);
493 : 134 : g_return_val_if_fail (b != NULL, FALSE);
494 : 134 : g_return_val_if_fail (b->ref_count >= 1, FALSE);
495 : :
496 : 240 : return (a->user_id == b->user_id &&
497 [ + + ]: 106 : a->limit_type == b->limit_type &&
498 [ + + ]: 50 : a->set_limit_type == b->set_limit_type &&
499 [ + - ]: 30 : a->daily_start_time == b->daily_start_time &&
500 [ + + + + ]: 262 : a->daily_end_time == b->daily_end_time &&
501 [ + - ]: 22 : a->daily_limit_secs == b->daily_limit_secs);
502 : : }
503 : :
504 : : /*
505 : : * Actual implementation of `MctSessionLimitsBuilder`.
506 : : *
507 : : * All members are `NULL` if un-initialised, cleared, or ended.
508 : : */
509 : : typedef struct
510 : : {
511 : : MctSessionLimitsType limit_type;
512 : : MctSessionLimitsType set_limit_type;
513 : :
514 : : struct
515 : : {
516 : : guint start_time; /* seconds since midnight */
517 : : guint end_time; /* seconds since midnight */
518 : : } daily_schedule;
519 : :
520 : : unsigned int daily_limit_secs;
521 : :
522 : : /*< private >*/
523 : : gpointer padding[9];
524 : : } MctSessionLimitsBuilderReal;
525 : :
526 : : G_STATIC_ASSERT (sizeof (MctSessionLimitsBuilderReal) ==
527 : : sizeof (MctSessionLimitsBuilder));
528 : : G_STATIC_ASSERT (__alignof__ (MctSessionLimitsBuilderReal) ==
529 : : __alignof__ (MctSessionLimitsBuilder));
530 : :
531 [ + - + - : 6 : G_DEFINE_BOXED_TYPE (MctSessionLimitsBuilder, mct_session_limits_builder,
+ - ]
532 : : mct_session_limits_builder_copy, mct_session_limits_builder_free)
533 : :
534 : : /**
535 : : * mct_session_limits_builder_init:
536 : : * @builder: an uninitialised [struct@Malcontent.SessionLimitsBuilder]
537 : : *
538 : : * Initialise the given @builder so it can be used to construct a new
539 : : * [struct@Malcontent.SessionLimits].
540 : : *
541 : : * @builder must have been allocated on the stack, and must not already be
542 : : * initialised.
543 : : *
544 : : * Construct the [struct@Malcontent.SessionLimits] by calling methods on
545 : : * @builder, followed by [method@Malcontent.SessionLimitsBuilder.end]. To abort
546 : : * construction, use [method@Malcontent.SessionLimitsBuilder.clear].
547 : : *
548 : : * Since: 0.5.0
549 : : */
550 : : void
551 : 26 : mct_session_limits_builder_init (MctSessionLimitsBuilder *builder)
552 : : {
553 : 26 : MctSessionLimitsBuilder local_builder = MCT_SESSION_LIMITS_BUILDER_INIT ();
554 : 26 : MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
555 : :
556 : 26 : g_return_if_fail (_builder != NULL);
557 : 26 : g_return_if_fail (_builder->limit_type == MCT_SESSION_LIMITS_TYPE_NONE);
558 : 26 : g_return_if_fail (_builder->set_limit_type == MCT_SESSION_LIMITS_TYPE_NONE);
559 : :
560 : 26 : memcpy (builder, &local_builder, sizeof (local_builder));
561 : : }
562 : :
563 : : /**
564 : : * mct_session_limits_builder_clear:
565 : : * @builder: a [struct@Malcontent.SessionLimitsBuilder]
566 : : *
567 : : * Clear @builder, freeing any internal state in it.
568 : : *
569 : : * This will not free the top-level storage for @builder itself, which is
570 : : * assumed to be allocated on the stack.
571 : : *
572 : : * If called on an already-cleared [struct@Malcontent.SessionLimitsBuilder],
573 : : * this function is idempotent.
574 : : *
575 : : * Since: 0.5.0
576 : : */
577 : : void
578 : 100 : mct_session_limits_builder_clear (MctSessionLimitsBuilder *builder)
579 : : {
580 : 100 : MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
581 : :
582 : 100 : g_return_if_fail (_builder != NULL);
583 : :
584 : : /* Nothing to free here for now. */
585 : 100 : _builder->limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
586 : 100 : _builder->set_limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
587 : : }
588 : :
589 : : /**
590 : : * mct_session_limits_builder_new:
591 : : *
592 : : * Construct a new [struct@Malcontent.SessionLimitsBuilder] on the heap.
593 : : *
594 : : * This is intended for language bindings. The returned builder must eventually
595 : : * be freed with [method@Malcontent.SessionLimitsBuilder.free], but can be
596 : : * cleared zero or more times with
597 : : * [method@Malcontent.SessionLimitsBuilder.clear] first.
598 : : *
599 : : * Returns: (transfer full): a new heap-allocated
600 : : * [struct@Malcontent.SessionLimitsBuilder]
601 : : * Since: 0.5.0
602 : : */
603 : : MctSessionLimitsBuilder *
604 : 18 : mct_session_limits_builder_new (void)
605 : : {
606 : 18 : g_autoptr(MctSessionLimitsBuilder) builder = NULL;
607 : :
608 : 18 : builder = g_new0 (MctSessionLimitsBuilder, 1);
609 : 18 : mct_session_limits_builder_init (builder);
610 : :
611 : 18 : return g_steal_pointer (&builder);
612 : : }
613 : :
614 : : /**
615 : : * mct_session_limits_builder_copy:
616 : : * @builder: a [struct@Malcontent.SessionLimitsBuilder]
617 : : *
618 : : * Copy the given @builder to a newly-allocated
619 : : * [struct@Malcontent.SessionLimitsBuilder] on the heap.
620 : : *
621 : : * This is safe to use with cleared, stack-allocated
622 : : * [struct@Malcontent.SessionLimitsBuilder]s.
623 : : *
624 : : * Returns: (transfer full): a copy of @builder
625 : : * Since: 0.5.0
626 : : */
627 : : MctSessionLimitsBuilder *
628 : 4 : mct_session_limits_builder_copy (MctSessionLimitsBuilder *builder)
629 : : {
630 : 4 : MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
631 : 4 : g_autoptr(MctSessionLimitsBuilder) copy = NULL;
632 : : MctSessionLimitsBuilderReal *_copy;
633 : :
634 : 4 : g_return_val_if_fail (builder != NULL, NULL);
635 : :
636 : 4 : copy = mct_session_limits_builder_new ();
637 : 4 : _copy = (MctSessionLimitsBuilderReal *) copy;
638 : :
639 : 4 : mct_session_limits_builder_clear (copy);
640 : 4 : _copy->limit_type = _builder->limit_type;
641 : 4 : _copy->set_limit_type = _builder->set_limit_type;
642 : :
643 [ + + ]: 4 : if (_builder->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
644 : : {
645 : 2 : _copy->daily_schedule.start_time = _builder->daily_schedule.start_time;
646 : 2 : _copy->daily_schedule.end_time = _builder->daily_schedule.end_time;
647 : : }
648 : :
649 [ - + ]: 4 : if (_builder->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT)
650 : 0 : _copy->daily_limit_secs = _builder->daily_limit_secs;
651 : :
652 : 4 : return g_steal_pointer (©);
653 : : }
654 : :
655 : : /**
656 : : * mct_session_limits_builder_free:
657 : : * @builder: a heap-allocated [struct@Malcontent.SessionLimitsBuilder]
658 : : *
659 : : * Free an [struct@Malcontent.SessionLimitsBuilder] originally allocated using
660 : : * [ctor@Malcontent.SessionLimitsBuilder.new].
661 : : *
662 : : * This must not be called on stack-allocated builders initialised using
663 : : * [method@Malcontent.SessionLimitsBuilder.init].
664 : : *
665 : : * Since: 0.5.0
666 : : */
667 : : void
668 : 18 : mct_session_limits_builder_free (MctSessionLimitsBuilder *builder)
669 : : {
670 : 18 : g_return_if_fail (builder != NULL);
671 : :
672 : 18 : mct_session_limits_builder_clear (builder);
673 : 18 : g_free (builder);
674 : : }
675 : :
676 : : /**
677 : : * mct_session_limits_builder_end:
678 : : * @builder: an initialised [struct@Malcontent.SessionLimitsBuilder]
679 : : *
680 : : * Finish constructing an [struct@Malcontent.SessionLimits] with the given
681 : : * @builder, and return it.
682 : : *
683 : : * The [struct@Malcontent.SessionLimitsBuilder] will be cleared as if
684 : : * [method@Malcontent.SessionLimitsBuilder.clear] had been called.
685 : : *
686 : : * Returns: (transfer full): a newly constructed
687 : : * [struct@Malcontent.SessionLimits]
688 : : * Since: 0.5.0
689 : : */
690 : : MctSessionLimits *
691 : 46 : mct_session_limits_builder_end (MctSessionLimitsBuilder *builder)
692 : : {
693 : 46 : MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
694 : 46 : g_autoptr(MctSessionLimits) session_limits = NULL;
695 : :
696 : 46 : g_return_val_if_fail (_builder != NULL, NULL);
697 : :
698 : : /* Build the `MctSessionLimits`. */
699 : 46 : session_limits = g_new0 (MctSessionLimits, 1);
700 : 46 : session_limits->ref_count = 1;
701 : 46 : session_limits->user_id = -1;
702 : 46 : session_limits->limit_type = _builder->limit_type;
703 : 46 : session_limits->set_limit_type = _builder->limit_type;
704 : :
705 [ + + ]: 46 : if (_builder->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
706 : : {
707 : 25 : session_limits->daily_start_time = _builder->daily_schedule.start_time;
708 : 25 : session_limits->daily_end_time = _builder->daily_schedule.end_time;
709 : : }
710 : : else
711 : : {
712 : : /* Defaults: */
713 : 21 : session_limits->daily_start_time = 0;
714 : 21 : session_limits->daily_end_time = 24 * 60 * 60;
715 : : }
716 : :
717 [ + + ]: 46 : if (_builder->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT)
718 : 6 : session_limits->daily_limit_secs = _builder->daily_limit_secs;
719 : : else
720 : 40 : session_limits->daily_limit_secs = 24 * 60 * 60; /* default */
721 : :
722 : 46 : mct_session_limits_builder_clear (builder);
723 : :
724 : 46 : return g_steal_pointer (&session_limits);
725 : : }
726 : :
727 : : /**
728 : : * mct_session_limits_builder_set_none:
729 : : * @builder: an initialised [struct@Malcontent.SessionLimitsBuilder]
730 : : *
731 : : * Unset any session limits currently set in the @builder.
732 : : *
733 : : * Since: 0.5.0
734 : : */
735 : : void
736 : 2 : mct_session_limits_builder_set_none (MctSessionLimitsBuilder *builder)
737 : : {
738 : 2 : MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
739 : :
740 : 2 : g_return_if_fail (_builder != NULL);
741 : :
742 : : /* This will need to free other limit types’ data first in future. */
743 : 2 : _builder->limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
744 : : }
745 : :
746 : : /**
747 : : * mct_session_limits_builder_set_daily_schedule:
748 : : * @builder: an initialised [struct@Malcontent.SessionLimitsBuilder]
749 : : * @enforced: true if the limit type is to be enforced, false if the value is
750 : : * just being stored for future use without being enforced
751 : : * @start_time_secs: number of seconds since midnight when the user’s session
752 : : * can first start
753 : : * @end_time_secs: number of seconds since midnight when the user’s session can
754 : : * last end
755 : : *
756 : : * Set the session limits in @builder to be a daily schedule, where sessions are
757 : : * allowed between @start_time_secs and @end_time_secs every day.
758 : : *
759 : : * @start_time_secs and @end_time_secs are given as offsets from the start of
760 : : * the day, in seconds. @end_time_secs must be greater than @start_time_secs.
761 : : * @end_time_secs must be at most `24 * 60 * 60`.
762 : : *
763 : : * This will act in addition to any other session limits.
764 : : *
765 : : * Since: 0.14.0
766 : : */
767 : : void
768 : 27 : mct_session_limits_builder_set_daily_schedule (MctSessionLimitsBuilder *builder,
769 : : gboolean enforced,
770 : : guint start_time_secs,
771 : : guint end_time_secs)
772 : : {
773 : 27 : MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
774 : :
775 : 27 : g_return_if_fail (_builder != NULL);
776 : 27 : g_return_if_fail (start_time_secs < end_time_secs);
777 : 27 : g_return_if_fail (end_time_secs <= 24 * 60 * 60);
778 : :
779 : 27 : _builder->set_limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE;
780 [ + - ]: 27 : if (enforced)
781 : 27 : _builder->limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE;
782 : 27 : _builder->daily_schedule.start_time = start_time_secs;
783 : 27 : _builder->daily_schedule.end_time = end_time_secs;
784 : : }
785 : :
786 : : /**
787 : : * mct_session_limits_builder_set_daily_limit:
788 : : * @builder: an initialised [struct@Malcontent.SessionLimitsBuilder]
789 : : * @enforced: true if the limit type is to be enforced, false if the value is
790 : : * just being stored for future use without being enforced
791 : : * @daily_limit_secs: maximum length for the user’s active session time each
792 : : * day, in seconds
793 : : *
794 : : * Set the session limits in @builder to be a daily limit, where the total
795 : : * active session time for the user has a given limit each day.
796 : : *
797 : : * @daily_limit_secs must be at most `24 * 60 * 60`.
798 : : *
799 : : * This will act in addition to any other session limits.
800 : : *
801 : : * Since: 0.14.0
802 : : */
803 : : void
804 : 6 : mct_session_limits_builder_set_daily_limit (MctSessionLimitsBuilder *builder,
805 : : gboolean enforced,
806 : : unsigned int daily_limit_secs)
807 : : {
808 : 6 : MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
809 : :
810 : 6 : g_return_if_fail (_builder != NULL);
811 : 6 : g_return_if_fail (daily_limit_secs <= 24 * 60 * 60);
812 : :
813 : 6 : _builder->set_limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT;
814 [ + - ]: 6 : if (enforced)
815 : 6 : _builder->limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT;
816 : 6 : _builder->daily_limit_secs = daily_limit_secs;
817 : : }
|