LCOV - code coverage report
Current view: top level - libmogwai-schedule - scheduler.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 368 384 95.8 %
Date: 2022-06-30 20:59:16 Functions: 30 30 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 195 255 76.5 %

           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/clock.h>
      30                 :            : #include <libmogwai-schedule/connection-monitor.h>
      31                 :            : #include <libmogwai-schedule/peer-manager.h>
      32                 :            : #include <libmogwai-schedule/schedule-entry.h>
      33                 :            : #include <libmogwai-schedule/scheduler.h>
      34                 :            : 
      35                 :            : 
      36                 :            : /* These errors do go over the bus, and are registered in schedule-service.c. */
      37         [ +  + ]:         14 : G_DEFINE_QUARK (MwsSchedulerError, mws_scheduler_error)
      38                 :            : 
      39                 :            : /* Cached state for a schedule entry, including its current active state, and
      40                 :            :  * any calculated state which is not trivially derivable from the properties of
      41                 :            :  * the #MwsScheduleEntry itself. */
      42                 :            : typedef struct
      43                 :            : {
      44                 :            :   gboolean is_active;
      45                 :            : } EntryData;
      46                 :            : 
      47                 :            : static EntryData *entry_data_new  (void);
      48                 :            : static void       entry_data_free (EntryData *data);
      49                 :            : 
      50         [ -  + ]:        116 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (EntryData, entry_data_free);
      51                 :            : 
      52                 :            : /* Create a new #EntryData struct with default values. */
      53                 :            : static EntryData *
      54                 :         58 : entry_data_new (void)
      55                 :            : {
      56                 :        116 :   g_autoptr(EntryData) data = g_new0 (EntryData, 1);
      57                 :         58 :   return g_steal_pointer (&data);
      58                 :            : }
      59                 :            : 
      60                 :            : static void
      61                 :         58 : entry_data_free (EntryData *data)
      62                 :            : {
      63                 :         58 :   g_free (data);
      64                 :         58 : }
      65                 :            : 
      66                 :            : static void mws_scheduler_constructed  (GObject      *object);
      67                 :            : static void mws_scheduler_dispose      (GObject      *object);
      68                 :            : 
      69                 :            : static void mws_scheduler_get_property (GObject      *object,
      70                 :            :                                         guint         property_id,
      71                 :            :                                         GValue        *value,
      72                 :            :                                         GParamSpec   *pspec);
      73                 :            : static void mws_scheduler_set_property (GObject      *object,
      74                 :            :                                         guint         property_id,
      75                 :            :                                         const GValue *value,
      76                 :            :                                         GParamSpec   *pspec);
      77                 :            : 
      78                 :            : static void connection_monitor_connections_changed_cb        (MwsConnectionMonitor *connection_monitor,
      79                 :            :                                                               GPtrArray            *added,
      80                 :            :                                                               GPtrArray            *removed,
      81                 :            :                                                               gpointer              user_data);
      82                 :            : static void connection_monitor_connection_details_changed_cb (MwsConnectionMonitor *connection_monitor,
      83                 :            :                                                               const gchar          *connection_id,
      84                 :            :                                                               gpointer              user_data);
      85                 :            : static void peer_manager_peer_vanished_cb                    (MwsPeerManager       *manager,
      86                 :            :                                                               const gchar          *name,
      87                 :            :                                                               gpointer              user_data);
      88                 :            : static void clock_offset_changed_cb                          (MwsClock             *clock,
      89                 :            :                                                               gpointer              user_data);
      90                 :            : 
      91                 :            : /**
      92                 :            :  * MwsScheduler:
      93                 :            :  *
      94                 :            :  * A scheduler object which stores a set of #MwsScheduleEntrys and allows
      95                 :            :  * managing them using bulk add and remove operations. It looks at their
      96                 :            :  * properties and the current network status and schedules them appropriately.
      97                 :            :  *
      98                 :            :  * Since: 0.1.0
      99                 :            :  */
     100                 :            : struct _MwsScheduler
     101                 :            : {
     102                 :            :   GObject parent;
     103                 :            : 
     104                 :            :   /* Scheduling data sources. */
     105                 :            :   MwsConnectionMonitor *connection_monitor;  /* (owned) */
     106                 :            :   MwsPeerManager *peer_manager;  /* (owned) */
     107                 :            :   MwsClock *clock;  /* (owned) */
     108                 :            : 
     109                 :            :   /* Time tracking. */
     110                 :            :   guint reschedule_alarm_id;  /* 0 when no reschedule is scheduled */
     111                 :            : 
     112                 :            :   /* Mapping from entry ID to (not nullable) entry. */
     113                 :            :   GHashTable *entries;  /* (owned) (element-type utf8 MwsScheduleEntry) */
     114                 :            :   gsize max_entries;
     115                 :            : 
     116                 :            :   /* Mapping from entry ID to (not nullable) entry data. We can’t use the same
     117                 :            :    * hash table as @entries since we need to be able to return that one in
     118                 :            :    * mws_scheduler_get_entries(). Always has the same set of keys as @entries. */
     119                 :            :   GHashTable *entries_data;  /* (owned) (element-type utf8 EntryData) */
     120                 :            : 
     121                 :            :   /* Maximum number of downloads allowed to be active at the same time. */
     122                 :            :   guint max_active_entries;
     123                 :            : 
     124                 :            :   /* Cache of some of the connection data used by our properties. */
     125                 :            :   gboolean cached_allow_downloads;
     126                 :            : 
     127                 :            :   /* Sanity check that we don’t reschedule re-entrantly. */
     128                 :            :   gboolean in_reschedule;
     129                 :            : };
     130                 :            : 
     131                 :            : /* Arbitrarily chosen. */
     132                 :            : static const gsize DEFAULT_MAX_ENTRIES = 1024;
     133                 :            : /* Chosen for a few reasons:
     134                 :            :  *  1. OSTree app updates and installs take ungodly amounts of I/O and CPU —
     135                 :            :  *     doing more than one of these at a time in the background is an
     136                 :            :  *     aggressively bad UX
     137                 :            :  *  2. Over-parallelisation causes bandwidth hogging and reduces the amount of
     138                 :            :  *     bandwidth available for foreground applications or user interactivity
     139                 :            :  *  3. We don’t want head-of-line blocking by large OS updates to block smaller,
     140                 :            :  *     more-regular content updates.
     141                 :            :  */
     142                 :            : static const guint DEFAULT_MAX_ACTIVE_ENTRIES = 1;
     143                 :            : 
     144                 :            : typedef enum
     145                 :            : {
     146                 :            :   PROP_ENTRIES = 1,
     147                 :            :   PROP_MAX_ENTRIES,
     148                 :            :   PROP_CONNECTION_MONITOR,
     149                 :            :   PROP_MAX_ACTIVE_ENTRIES,
     150                 :            :   PROP_PEER_MANAGER,
     151                 :            :   PROP_ALLOW_DOWNLOADS,
     152                 :            :   PROP_CLOCK,
     153                 :            : } MwsSchedulerProperty;
     154                 :            : 
     155   [ +  +  +  -  :       1087 : G_DEFINE_TYPE (MwsScheduler, mws_scheduler, G_TYPE_OBJECT)
                   +  + ]
     156                 :            : 
     157                 :            : static void
     158                 :          2 : mws_scheduler_class_init (MwsSchedulerClass *klass)
     159                 :            : {
     160                 :          2 :   GObjectClass *object_class = (GObjectClass *) klass;
     161                 :          2 :   GParamSpec *props[PROP_CLOCK + 1] = { NULL, };
     162                 :            : 
     163                 :          2 :   object_class->constructed = mws_scheduler_constructed;
     164                 :          2 :   object_class->dispose = mws_scheduler_dispose;
     165                 :          2 :   object_class->get_property = mws_scheduler_get_property;
     166                 :          2 :   object_class->set_property = mws_scheduler_set_property;
     167                 :            : 
     168                 :            :   /**
     169                 :            :    * MwsScheduler:entries: (type GHashTable(utf8,MwsScheduleEntry)) (transfer none)
     170                 :            :    *
     171                 :            :    * Set of schedule entries known to the scheduler, which might be empty. It is
     172                 :            :    * a mapping from entry ID to #MwsScheduleEntry instance. Use
     173                 :            :    * mws_scheduler_update_entries() to modify the mapping.
     174                 :            :    *
     175                 :            :    * Since: 0.1.0
     176                 :            :    */
     177                 :          2 :   props[PROP_ENTRIES] =
     178                 :          2 :       g_param_spec_boxed ("entries", "Entries",
     179                 :            :                           "Set of schedule entries known to the scheduler.",
     180                 :            :                           G_TYPE_HASH_TABLE,
     181                 :            :                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
     182                 :            : 
     183                 :            :   /**
     184                 :            :    * MwsScheduler:max-entries:
     185                 :            :    *
     186                 :            :    * Maximum number of schedule entries which can be present in the scheduler at
     187                 :            :    * any time. This is not a limit on the number of active schedule entries. It
     188                 :            :    * exists to make explicit the avoidance of array bounds overflows in the
     189                 :            :    * scheduler. It should be considered high enough to not be reached apart from
     190                 :            :    * in exception circumstances.
     191                 :            :    *
     192                 :            :    * Since: 0.1.0
     193                 :            :    */
     194                 :          2 :   props[PROP_MAX_ENTRIES] =
     195                 :          2 :       g_param_spec_uint ("max-entries", "Max. Entries",
     196                 :            :                          "Maximum number of schedule entries present in the scheduler.",
     197                 :            :                          0, G_MAXUINT, DEFAULT_MAX_ENTRIES,
     198                 :            :                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
     199                 :            : 
     200                 :            :   /**
     201                 :            :    * MwsScheduler:connection-monitor:
     202                 :            :    *
     203                 :            :    * A #MwsConnectionMonitor instance to provide information about the currently
     204                 :            :    * active network connections which is relevant to scheduling downloads, such
     205                 :            :    * as whether they are currently metered, how much of their capacity has been
     206                 :            :    * used in the current time period, or any user-provided policies for them.
     207                 :            :    *
     208                 :            :    * Since: 0.1.0
     209                 :            :    */
     210                 :          2 :   props[PROP_CONNECTION_MONITOR] =
     211                 :          2 :       g_param_spec_object ("connection-monitor", "Connection Monitor",
     212                 :            :                            "A #MwsConnectionMonitor instance to provide "
     213                 :            :                            "information about the currently active network connections.",
     214                 :            :                            MWS_TYPE_CONNECTION_MONITOR,
     215                 :            :                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
     216                 :            : 
     217                 :            :   /**
     218                 :            :    * MwsScheduler:max-active-entries:
     219                 :            :    *
     220                 :            :    * Maximum number of schedule entries which can be active at any time. This
     221                 :            :    * effectively limits the parallelisation of the scheduler. In contrast with
     222                 :            :    * #MwsScheduler:max-entries, this limit is expected to be reached routinely.
     223                 :            :    *
     224                 :            :    * Since: 0.1.0
     225                 :            :    */
     226                 :          2 :   props[PROP_MAX_ACTIVE_ENTRIES] =
     227                 :          2 :       g_param_spec_uint ("max-active-entries", "Max. Active Entries",
     228                 :            :                          "Maximum number of schedule entries which can be active at any time.",
     229                 :            :                          1, G_MAXUINT, DEFAULT_MAX_ACTIVE_ENTRIES,
     230                 :            :                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
     231                 :            : 
     232                 :            :   /**
     233                 :            :    * MwsScheduler:peer-manager:
     234                 :            :    *
     235                 :            :    * A #MwsPeerManager instance to provide information about the peers who are
     236                 :            :    * adding schedule entries to the scheduler. (Typically, these are D-Bus peers
     237                 :            :    * using the scheduler’s D-Bus interface.)
     238                 :            :    *
     239                 :            :    * Since: 0.1.0
     240                 :            :    */
     241                 :          2 :   props[PROP_PEER_MANAGER] =
     242                 :          2 :       g_param_spec_object ("peer-manager", "Peer Manager",
     243                 :            :                            "A #MwsPeerManager instance to provide information "
     244                 :            :                            "about the peers who are adding schedule entries to "
     245                 :            :                            "the scheduler.",
     246                 :            :                            MWS_TYPE_PEER_MANAGER,
     247                 :            :                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
     248                 :            : 
     249                 :            :   /**
     250                 :            :    * MwsScheduler:allow-downloads:
     251                 :            :    *
     252                 :            :    * Whether any of the currently active network connections are configured to
     253                 :            :    * allow any large downloads. It is up to the clients which use Mogwai to
     254                 :            :    * decide what ’large’ is.
     255                 :            :    *
     256                 :            :    * This is not a guarantee that a schedule entry
     257                 :            :    * will be scheduled; it is a reflection of the user’s intent for the use of
     258                 :            :    * the currently active network connections, intended to be used in UIs to
     259                 :            :    * remind the user of how they have configured the network.
     260                 :            :    *
     261                 :            :    * Programs must not use this value to check whether to schedule an entry.
     262                 :            :    * Schedule the entry unconditionally; the scheduler will work out whether
     263                 :            :    * (and when) to download the entry.
     264                 :            :    *
     265                 :            :    * Since: 0.1.0
     266                 :            :    */
     267                 :          2 :   props[PROP_ALLOW_DOWNLOADS] =
     268                 :          2 :       g_param_spec_boolean ("allow-downloads", "Allow Downloads",
     269                 :            :                             "Whether any of the currently active network "
     270                 :            :                             "connections are configured to allow any large "
     271                 :            :                             "downloads.",
     272                 :            :                             TRUE,
     273                 :            :                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
     274                 :            : 
     275                 :            :   /**
     276                 :            :    * MwsScheduler:clock:
     277                 :            :    *
     278                 :            :    * A #MwsClock instance to provide wall clock timing and alarms for time-based
     279                 :            :    * scheduling. Typically, this is provided by a #MwsClockSystem, using the
     280                 :            :    * system clock.
     281                 :            :    *
     282                 :            :    * Since: 0.1.0
     283                 :            :    */
     284                 :          2 :   props[PROP_CLOCK] =
     285                 :          2 :       g_param_spec_object ("clock", "Clock",
     286                 :            :                            "A #MwsClock instance to provide wall clock timing "
     287                 :            :                            "and alarms for time-based scheduling.",
     288                 :            :                            MWS_TYPE_CLOCK,
     289                 :            :                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
     290                 :            : 
     291                 :          2 :   g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
     292                 :            : 
     293                 :            :   /**
     294                 :            :    * MwsScheduler::entries-changed:
     295                 :            :    * @self: a #MwsScheduler
     296                 :            :    * @added: (element-type MwsScheduleEntry) (nullable): potentially empty or
     297                 :            :    *     %NULL array of added entries (a %NULL array is equivalent to an empty one)
     298                 :            :    * @removed: (element-type MwsScheduleEntry) (nullable): potentially empty or
     299                 :            :    *     %NULL array of removed entries (a %NULL array is equivalent to an empty one)
     300                 :            :    *
     301                 :            :    * Emitted when the set of schedule entries known to the scheduler changes. It
     302                 :            :    * is emitted at the same time as #GObject::notify for the
     303                 :            :    * #MwsScheduler:entries property, but contains the delta of which
     304                 :            :    * entries have been added and removed.
     305                 :            :    *
     306                 :            :    * There will be at least one entry in one of the arrays.
     307                 :            :    *
     308                 :            :    * Since: 0.1.0
     309                 :            :    */
     310                 :          2 :   g_signal_new ("entries-changed", G_TYPE_FROM_CLASS (klass),
     311                 :            :                 G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
     312                 :            :                 G_TYPE_NONE, 2, G_TYPE_PTR_ARRAY, G_TYPE_PTR_ARRAY);
     313                 :            : 
     314                 :            :   /**
     315                 :            :    * MwsScheduler::active-entries-changed:
     316                 :            :    * @self: a #MwsScheduler
     317                 :            :    * @added: (element-type MwsScheduleEntry) (nullable): potentially empty or
     318                 :            :    *     %NULL array of newly-active entries (a %NULL array is equivalent to an
     319                 :            :    *     empty one)
     320                 :            :    * @removed: (element-type MwsScheduleEntry) (nullable): potentially empty or
     321                 :            :    *     %NULL array of newly-inactive entries (a %NULL array is equivalent to
     322                 :            :    *     an empty one)
     323                 :            :    *
     324                 :            :    * Emitted when the set of active entries changes; i.e. when an entry is
     325                 :            :    * allowed to start downloading, or when one is requested to stop downloading.
     326                 :            :    *
     327                 :            :    * There will be at least one entry in one of the arrays.
     328                 :            :    *
     329                 :            :    * Since: 0.1.0
     330                 :            :    */
     331                 :          2 :   g_signal_new ("active-entries-changed", G_TYPE_FROM_CLASS (klass),
     332                 :            :                 G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
     333                 :            :                 G_TYPE_NONE, 2, G_TYPE_PTR_ARRAY, G_TYPE_PTR_ARRAY);
     334                 :          2 : }
     335                 :            : 
     336                 :            : static void
     337                 :         48 : mws_scheduler_init (MwsScheduler *self)
     338                 :            : {
     339                 :         48 :   self->entries = g_hash_table_new_full (g_str_hash, g_str_equal,
     340                 :            :                                          NULL, g_object_unref);
     341                 :         48 :   self->max_entries = DEFAULT_MAX_ENTRIES;
     342                 :         48 :   self->entries_data = g_hash_table_new_full (g_str_hash, g_str_equal,
     343                 :            :                                               NULL, (GDestroyNotify) entry_data_free);
     344                 :         48 :   self->max_active_entries = DEFAULT_MAX_ACTIVE_ENTRIES;
     345                 :         48 : }
     346                 :            : 
     347                 :            : static void
     348                 :         48 : mws_scheduler_constructed (GObject *object)
     349                 :            : {
     350                 :         48 :   MwsScheduler *self = MWS_SCHEDULER (object);
     351                 :            : 
     352                 :         48 :   G_OBJECT_CLASS (mws_scheduler_parent_class)->constructed (object);
     353                 :            : 
     354                 :            :   /* Check we have our construction properties. */
     355         [ -  + ]:         48 :   g_assert (MWS_IS_CONNECTION_MONITOR (self->connection_monitor));
     356         [ -  + ]:         48 :   g_assert (MWS_IS_PEER_MANAGER (self->peer_manager));
     357         [ -  + ]:         48 :   g_assert (MWS_IS_CLOCK (self->clock));
     358                 :            : 
     359                 :            :   /* Connect to signals from the connection monitor, which will trigger
     360                 :            :    * rescheduling. */
     361                 :         48 :   g_signal_connect (self->connection_monitor, "connections-changed",
     362                 :            :                     (GCallback) connection_monitor_connections_changed_cb, self);
     363                 :         48 :   g_signal_connect (self->connection_monitor, "connection-details-changed",
     364                 :            :                     (GCallback) connection_monitor_connection_details_changed_cb, self);
     365                 :            : 
     366                 :            :   /* Connect to signals from the peer manager, which will trigger removal of
     367                 :            :    * entries when a peer disappears. */
     368                 :         48 :   g_signal_connect (self->peer_manager, "peer-vanished",
     369                 :            :                     (GCallback) peer_manager_peer_vanished_cb, self);
     370                 :            : 
     371                 :            :   /* Connect to signals from the clock, which will trigger rescheduling when
     372                 :            :    * the clock offset changes. */
     373                 :         48 :   g_signal_connect (self->clock, "offset-changed",
     374                 :            :                     (GCallback) clock_offset_changed_cb, self);
     375                 :            : 
     376                 :            :   /* Initialise self->cached_allow_downloads. */
     377                 :         48 :   mws_scheduler_reschedule (self);
     378                 :         48 : }
     379                 :            : 
     380                 :            : static void
     381                 :         47 : mws_scheduler_dispose (GObject *object)
     382                 :            : {
     383                 :         47 :   MwsScheduler *self = MWS_SCHEDULER (object);
     384                 :            : 
     385         [ +  - ]:         47 :   g_clear_pointer (&self->entries, g_hash_table_unref);
     386         [ +  - ]:         47 :   g_clear_pointer (&self->entries_data, g_hash_table_unref);
     387                 :            : 
     388         [ +  - ]:         47 :   if (self->connection_monitor != NULL)
     389                 :            :     {
     390                 :         47 :       g_signal_handlers_disconnect_by_func (self->connection_monitor,
     391                 :            :                                             connection_monitor_connections_changed_cb,
     392                 :            :                                             self);
     393                 :         47 :       g_signal_handlers_disconnect_by_func (self->connection_monitor,
     394                 :            :                                             connection_monitor_connection_details_changed_cb,
     395                 :            :                                             self);
     396                 :            :     }
     397                 :            : 
     398         [ +  - ]:         47 :   g_clear_object (&self->connection_monitor);
     399                 :            : 
     400         [ +  - ]:         47 :   if (self->peer_manager != NULL)
     401                 :            :     {
     402                 :         47 :       g_signal_handlers_disconnect_by_func (self->peer_manager,
     403                 :            :                                             peer_manager_peer_vanished_cb,
     404                 :            :                                             self);
     405                 :            :     }
     406                 :            : 
     407         [ +  - ]:         47 :   g_clear_object (&self->peer_manager);
     408                 :            : 
     409   [ +  -  -  + ]:         47 :   if (self->clock != NULL && self->reschedule_alarm_id != 0)
     410                 :            :     {
     411                 :          0 :       mws_clock_remove_alarm (self->clock, self->reschedule_alarm_id);
     412                 :          0 :       self->reschedule_alarm_id = 0;
     413                 :            :     }
     414                 :            : 
     415         [ +  - ]:         47 :   if (self->clock != NULL)
     416                 :            :     {
     417                 :         47 :       g_signal_handlers_disconnect_by_func (self->clock,
     418                 :            :                                             clock_offset_changed_cb,
     419                 :            :                                             self);
     420                 :            :     }
     421                 :            : 
     422         [ +  - ]:         47 :   g_clear_object (&self->clock);
     423                 :            : 
     424         [ -  + ]:         47 :   g_assert (!self->in_reschedule);
     425                 :            : 
     426                 :            :   /* Chain up to the parent class */
     427                 :         47 :   G_OBJECT_CLASS (mws_scheduler_parent_class)->dispose (object);
     428                 :         47 : }
     429                 :            : 
     430                 :            : static void
     431                 :         10 : mws_scheduler_get_property (GObject    *object,
     432                 :            :                             guint       property_id,
     433                 :            :                             GValue     *value,
     434                 :            :                             GParamSpec *pspec)
     435                 :            : {
     436                 :         10 :   MwsScheduler *self = MWS_SCHEDULER (object);
     437                 :            : 
     438   [ +  +  +  +  :         10 :   switch ((MwsSchedulerProperty) property_id)
             +  +  +  - ]
     439                 :            :     {
     440                 :          1 :     case PROP_ENTRIES:
     441                 :          1 :       g_value_set_boxed (value, self->entries);
     442                 :          1 :       break;
     443                 :          2 :     case PROP_MAX_ENTRIES:
     444                 :          2 :       g_value_set_uint (value, self->max_entries);
     445                 :          2 :       break;
     446                 :          1 :     case PROP_CONNECTION_MONITOR:
     447                 :          1 :       g_value_set_object (value, self->connection_monitor);
     448                 :          1 :       break;
     449                 :          3 :     case PROP_MAX_ACTIVE_ENTRIES:
     450                 :          3 :       g_value_set_uint (value, self->max_active_entries);
     451                 :          3 :       break;
     452                 :          1 :     case PROP_PEER_MANAGER:
     453                 :          1 :       g_value_set_object (value, self->peer_manager);
     454                 :          1 :       break;
     455                 :          1 :     case PROP_ALLOW_DOWNLOADS:
     456                 :          1 :       g_value_set_boolean (value, mws_scheduler_get_allow_downloads (self));
     457                 :          1 :       break;
     458                 :          1 :     case PROP_CLOCK:
     459                 :          1 :       g_value_set_object (value, self->clock);
     460                 :          1 :       break;
     461                 :          0 :     default:
     462                 :          0 :       g_assert_not_reached ();
     463                 :            :     }
     464                 :         10 : }
     465                 :            : 
     466                 :            : static void
     467                 :        240 : mws_scheduler_set_property (GObject      *object,
     468                 :            :                             guint         property_id,
     469                 :            :                             const GValue *value,
     470                 :            :                             GParamSpec   *pspec)
     471                 :            : {
     472                 :        240 :   MwsScheduler *self = MWS_SCHEDULER (object);
     473                 :            : 
     474   [ -  +  +  +  :        240 :   switch ((MwsSchedulerProperty) property_id)
                +  +  - ]
     475                 :            :     {
     476                 :          0 :     case PROP_ENTRIES:
     477                 :            :     case PROP_ALLOW_DOWNLOADS:
     478                 :            :       /* Read only. */
     479                 :          0 :       g_assert_not_reached ();
     480                 :            :       break;
     481                 :         48 :     case PROP_MAX_ENTRIES:
     482                 :            :       /* Construct only. */
     483                 :         48 :       self->max_entries = g_value_get_uint (value);
     484                 :         48 :       break;
     485                 :         48 :     case PROP_CONNECTION_MONITOR:
     486                 :            :       /* Construct only. */
     487         [ -  + ]:         48 :       g_assert (self->connection_monitor == NULL);
     488                 :         48 :       self->connection_monitor = g_value_dup_object (value);
     489                 :         48 :       break;
     490                 :         48 :     case PROP_MAX_ACTIVE_ENTRIES:
     491                 :            :       /* Construct only. */
     492                 :         48 :       self->max_active_entries = g_value_get_uint (value);
     493                 :         48 :       break;
     494                 :         48 :     case PROP_PEER_MANAGER:
     495                 :            :       /* Construct only. */
     496         [ -  + ]:         48 :       g_assert (self->peer_manager == NULL);
     497                 :         48 :       self->peer_manager = g_value_dup_object (value);
     498                 :         48 :       break;
     499                 :         48 :     case PROP_CLOCK:
     500                 :            :       /* Construct only. */
     501         [ -  + ]:         48 :       g_assert (self->clock == NULL);
     502                 :         48 :       self->clock = g_value_dup_object (value);
     503                 :         48 :       break;
     504                 :          0 :     default:
     505                 :          0 :       g_assert_not_reached ();
     506                 :            :     }
     507                 :        240 : }
     508                 :            : 
     509                 :            : static void
     510                 :         27 : connection_monitor_connections_changed_cb (MwsConnectionMonitor *connection_monitor,
     511                 :            :                                            GPtrArray            *added,
     512                 :            :                                            GPtrArray            *removed,
     513                 :            :                                            gpointer              user_data)
     514                 :            : {
     515                 :         27 :   MwsScheduler *self = MWS_SCHEDULER (user_data);
     516                 :            : 
     517                 :            :   /* This needs to update self->cached_allow_downloads too. */
     518   [ +  -  +  - ]:         27 :   g_debug ("%s: Connections changed (%u added, %u removed)",
     519                 :            :            G_STRFUNC, (added != NULL) ? added->len : 0,
     520                 :            :            (removed != NULL) ? removed->len : 0);
     521                 :         27 :   mws_scheduler_reschedule (self);
     522                 :         27 : }
     523                 :            : 
     524                 :            : static void
     525                 :         70 : connection_monitor_connection_details_changed_cb (MwsConnectionMonitor *connection_monitor,
     526                 :            :                                                   const gchar          *connection_id,
     527                 :            :                                                   gpointer              user_data)
     528                 :            : {
     529                 :         70 :   MwsScheduler *self = MWS_SCHEDULER (user_data);
     530                 :            : 
     531                 :            :   /* This needs to update self->cached_allow_downloads too. */
     532                 :         70 :   g_debug ("%s: Connection ‘%s’ changed details", G_STRFUNC, connection_id);
     533                 :         70 :   mws_scheduler_reschedule (self);
     534                 :         70 : }
     535                 :            : 
     536                 :            : static void
     537                 :          2 : peer_manager_peer_vanished_cb (MwsPeerManager *manager,
     538                 :            :                                const gchar    *name,
     539                 :            :                                gpointer        user_data)
     540                 :            : {
     541                 :          2 :   MwsScheduler *self = MWS_SCHEDULER (user_data);
     542                 :          2 :   g_autoptr(GError) local_error = NULL;
     543                 :            : 
     544                 :            :   /* Remove the schedule entries for this peer. */
     545         [ -  + ]:          2 :   if (!mws_scheduler_remove_entries_for_owner (self, name, &local_error))
     546                 :            :     {
     547                 :          0 :       g_debug ("Failed to remove schedule entries for owner ‘%s’: %s",
     548                 :            :                name, local_error->message);
     549                 :            :     }
     550                 :          2 : }
     551                 :            : 
     552                 :            : static void
     553                 :         21 : clock_offset_changed_cb (MwsClock *clock,
     554                 :            :                          gpointer  user_data)
     555                 :            : {
     556                 :         21 :   MwsScheduler *self = MWS_SCHEDULER (user_data);
     557                 :            : 
     558                 :         42 :   g_autoptr(GDateTime) now = mws_clock_get_now_local (clock);
     559                 :         42 :   g_autofree gchar *now_str = g_date_time_format (now, "%FT%T%:::z");
     560                 :            : 
     561                 :         21 :   g_debug ("%s: Clock offset changed; time is now %s", G_STRFUNC, now_str);
     562                 :         21 :   mws_scheduler_reschedule (self);
     563                 :         21 : }
     564                 :            : 
     565                 :            : /**
     566                 :            :  * mws_scheduler_new:
     567                 :            :  * @connection_monitor: (transfer none): a #MwsConnectionMonitor to provide
     568                 :            :  *    information about network connections to the scheduler
     569                 :            :  * @peer_manager: (transfer none): a #MwsPeerManager to provide information
     570                 :            :  *    about peers which are adding schedule entries to the scheduler
     571                 :            :  * @clock: (transfer none): a #MwsClock to provide timing information
     572                 :            :  *
     573                 :            :  * Create a new #MwsScheduler instance, with no schedule entries to begin
     574                 :            :  * with.
     575                 :            :  *
     576                 :            :  * Returns: (transfer full): a new #MwsScheduler
     577                 :            :  * Since: 0.1.0
     578                 :            :  */
     579                 :            : MwsScheduler *
     580                 :          1 : mws_scheduler_new (MwsConnectionMonitor *connection_monitor,
     581                 :            :                    MwsPeerManager       *peer_manager,
     582                 :            :                    MwsClock             *clock)
     583                 :            : {
     584         [ -  + ]:          1 :   g_return_val_if_fail (MWS_IS_CONNECTION_MONITOR (connection_monitor), NULL);
     585         [ -  + ]:          1 :   g_return_val_if_fail (MWS_IS_PEER_MANAGER (peer_manager), NULL);
     586         [ -  + ]:          1 :   g_return_val_if_fail (MWS_IS_CLOCK (clock), NULL);
     587                 :            : 
     588                 :          1 :   return g_object_new (MWS_TYPE_SCHEDULER,
     589                 :            :                        "connection-monitor", connection_monitor,
     590                 :            :                        "peer-manager", peer_manager,
     591                 :            :                        "clock", clock,
     592                 :            :                        NULL);
     593                 :            : }
     594                 :            : 
     595                 :            : /**
     596                 :            :  * mws_scheduler_get_peer_manager:
     597                 :            :  * @self: a #MwsScheduler
     598                 :            :  *
     599                 :            :  * Get the value of #MwsScheduler:peer-manager.
     600                 :            :  *
     601                 :            :  * Returns: (transfer none): the peer manager for the scheduler
     602                 :            :  * Since: 0.1.0
     603                 :            :  */
     604                 :            : MwsPeerManager *
     605                 :         31 : mws_scheduler_get_peer_manager (MwsScheduler *self)
     606                 :            : {
     607         [ -  + ]:         31 :   g_return_val_if_fail (MWS_IS_SCHEDULER (self), NULL);
     608                 :            : 
     609                 :         31 :   return self->peer_manager;
     610                 :            : }
     611                 :            : 
     612                 :            : /**
     613                 :            :  * mws_scheduler_update_entries:
     614                 :            :  * @self: a #MwsScheduler
     615                 :            :  * @added: (nullable) (transfer none) (element-type MwsScheduleEntry): set of
     616                 :            :  *    #MwsScheduleEntry instances to add to the scheduler
     617                 :            :  * @removed: (nullable) (transfer none) (element-type utf8): set of entry IDs
     618                 :            :  *    to remove from the scheduler
     619                 :            :  * @error: return location for a #GError, or %NULL
     620                 :            :  *
     621                 :            :  * Update the set of schedule entries in the scheduler, adding all entries in
     622                 :            :  * @added, and removing all those in @removed.
     623                 :            :  *
     624                 :            :  * Entries in @added which are already in the scheduler, and entries in @removed
     625                 :            :  * which are not in the scheduler, are ignored.
     626                 :            :  *
     627                 :            :  * If adding any of @added to the scheduler would cause it to exceed
     628                 :            :  * #MwsScheduler:max-entries, %MWS_SCHEDULER_ERROR_FULL will be returned and
     629                 :            :  * the scheduler will not be modified to add or remove any of @added or
     630                 :            :  * @removed.
     631                 :            :  *
     632                 :            :  * Returns: %TRUE on success, %FALSE otherwise
     633                 :            :  * Since: 0.1.0
     634                 :            :  */
     635                 :            : gboolean
     636                 :         64 : mws_scheduler_update_entries (MwsScheduler  *self,
     637                 :            :                               GPtrArray     *added,
     638                 :            :                               GPtrArray     *removed,
     639                 :            :                               GError       **error)
     640                 :            : {
     641         [ -  + ]:         64 :   g_return_val_if_fail (MWS_IS_SCHEDULER (self), FALSE);
     642   [ +  -  -  + ]:         64 :   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
     643                 :            : 
     644                 :         64 :   g_autoptr(GPtrArray) actually_added = NULL;  /* (element-type MwsScheduleEntry) */
     645                 :         64 :   actually_added = g_ptr_array_new_with_free_func (g_object_unref);
     646                 :         64 :   g_autoptr(GPtrArray) actually_removed = NULL;  /* (element-type MwsScheduleEntry) */
     647                 :         64 :   actually_removed = g_ptr_array_new_with_free_func (g_object_unref);
     648                 :         64 :   g_autoptr(GPtrArray) actually_removed_active = NULL;  /* (element-type MwsScheduleEntry) */
     649                 :         64 :   actually_removed_active = g_ptr_array_new_with_free_func (g_object_unref);
     650                 :            : 
     651                 :            :   /* Check resource limits. */
     652         [ +  + ]:         64 :   if (added != NULL &&
     653         [ +  + ]:         40 :       added->len > self->max_entries - g_hash_table_size (self->entries))
     654                 :            :     {
     655                 :          1 :       g_set_error (error, MWS_SCHEDULER_ERROR, MWS_SCHEDULER_ERROR_FULL,
     656                 :            :                    _("Too many ongoing downloads already."));
     657                 :          1 :       return FALSE;
     658                 :            :     }
     659                 :            : 
     660                 :            :   /* Remove and add entries. Throughout, we need to ensure that @entries and
     661                 :            :    * @entries_data always have identical sets of keys; that reduces the number
     662                 :            :    * of checks needed in other places in the code. */
     663   [ +  +  +  + ]:         88 :   for (gsize i = 0; removed != NULL && i < removed->len; i++)
     664                 :            :     {
     665                 :         25 :       const gchar *entry_id = removed->pdata[i];
     666         [ -  + ]:         25 :       g_return_val_if_fail (mws_schedule_entry_id_is_valid (entry_id), FALSE);
     667                 :            : 
     668                 :         25 :       g_debug ("Removing schedule entry ‘%s’.", entry_id);
     669                 :            : 
     670                 :            :       /* FIXME: Upstream a g_hash_table_steal_extended() function which combines these two.
     671                 :            :        * See: https://bugzilla.gnome.org/show_bug.cgi?id=795302 */
     672                 :            :       gpointer value;
     673         [ +  + ]:         25 :       if (g_hash_table_lookup_extended (self->entries, entry_id, NULL, &value))
     674                 :            :         {
     675                 :         24 :           gboolean was_active = mws_scheduler_is_entry_active (self, MWS_SCHEDULE_ENTRY (value));
     676                 :            : 
     677                 :         24 :           g_autoptr(MwsScheduleEntry) entry = value;
     678                 :         24 :           g_hash_table_steal (self->entries, entry_id);
     679         [ -  + ]:         24 :           g_assert (g_hash_table_remove (self->entries_data, entry_id));
     680                 :            : 
     681         [ +  + ]:         24 :           if (was_active)
     682                 :         19 :             g_ptr_array_add (actually_removed_active, g_object_ref (entry));
     683                 :         24 :           g_ptr_array_add (actually_removed, g_steal_pointer (&entry));
     684                 :            :         }
     685                 :            :       else
     686                 :            :         {
     687                 :          1 :           g_debug ("Schedule entry ‘%s’ did not exist in MwsScheduler %p.",
     688                 :            :                    entry_id, self);
     689         [ -  + ]:          1 :           g_assert (g_hash_table_lookup (self->entries_data, entry_id) == NULL);
     690                 :            :         }
     691                 :            :     }
     692                 :            : 
     693   [ +  +  +  + ]:        122 :   for (gsize i = 0; added != NULL && i < added->len; i++)
     694                 :            :     {
     695                 :         59 :       MwsScheduleEntry *entry = added->pdata[i];
     696                 :         59 :       const gchar *entry_id = mws_schedule_entry_get_id (entry);
     697         [ -  + ]:         59 :       g_return_val_if_fail (MWS_IS_SCHEDULE_ENTRY (entry), FALSE);
     698                 :            : 
     699                 :         59 :       g_debug ("Adding schedule entry ‘%s’.", entry_id);
     700                 :            : 
     701         [ +  + ]:         59 :       if (g_hash_table_replace (self->entries,
     702                 :            :                                 (gpointer) entry_id, g_object_ref (entry)))
     703                 :            :         {
     704                 :         58 :           g_hash_table_replace (self->entries_data,
     705                 :         58 :                                 (gpointer) entry_id, entry_data_new ());
     706                 :         58 :           g_ptr_array_add (actually_added, g_object_ref (entry));
     707                 :            :         }
     708                 :            :       else
     709                 :            :         {
     710                 :          1 :           g_debug ("Schedule entry ‘%s’ already existed in MwsScheduler %p.",
     711                 :            :                    entry_id, self);
     712         [ -  + ]:          1 :           g_assert (g_hash_table_lookup (self->entries_data, entry_id) != NULL);
     713                 :            :         }
     714                 :            :     }
     715                 :            : 
     716         [ +  + ]:         63 :   if (actually_removed_active->len > 0)
     717                 :            :     {
     718                 :         19 :       g_debug ("%s: Emitting active-entries-changed with %u added, %u removed",
     719                 :            :                G_STRFUNC, (guint) 0, actually_removed_active->len);
     720                 :         19 :       g_signal_emit_by_name (G_OBJECT (self), "active-entries-changed",
     721                 :            :                              NULL, actually_removed_active);
     722                 :            :     }
     723                 :            : 
     724   [ +  +  +  + ]:         63 :   if (actually_added->len > 0 || actually_removed->len > 0)
     725                 :            :     {
     726                 :         58 :       g_debug ("%s: Emitting entries-changed with %u added, %u removed",
     727                 :            :                G_STRFUNC, actually_added->len, actually_removed->len);
     728                 :         58 :       g_object_notify (G_OBJECT (self), "entries");
     729                 :         58 :       g_signal_emit_by_name (G_OBJECT (self), "entries-changed",
     730                 :            :                              actually_added, actually_removed);
     731                 :            : 
     732                 :            :       /* Trigger a reschedule due to the new or removed entries. */
     733                 :         58 :       mws_scheduler_reschedule (self);
     734                 :            :     }
     735                 :            : 
     736                 :         63 :   return TRUE;
     737                 :            : }
     738                 :            : 
     739                 :            : /**
     740                 :            :  * mws_scheduler_remove_entries_for_owner:
     741                 :            :  * @self: a #MwsScheduler
     742                 :            :  * @owner: the D-Bus unique name of the peer to remove entries for
     743                 :            :  * @error: return location for a #GError, or %NULL
     744                 :            :  *
     745                 :            :  * Remove all schedule entries from the #MwsScheduler whose owner is @owner.
     746                 :            :  * Possible errors are the same as for mws_scheduler_update_entries().
     747                 :            :  *
     748                 :            :  * Returns: %TRUE on success, %FALSE otherwise
     749                 :            :  * Since: 0.1.0
     750                 :            :  */
     751                 :            : gboolean
     752                 :          5 : mws_scheduler_remove_entries_for_owner (MwsScheduler  *self,
     753                 :            :                                         const gchar   *owner,
     754                 :            :                                         GError       **error)
     755                 :            : {
     756         [ -  + ]:          5 :   g_return_val_if_fail (MWS_IS_SCHEDULER (self), FALSE);
     757         [ -  + ]:          5 :   g_return_val_if_fail (g_dbus_is_unique_name (owner), FALSE);
     758   [ +  -  -  + ]:          5 :   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
     759                 :            : 
     760                 :         10 :   g_autoptr(GPtrArray) entries_to_remove = g_ptr_array_new_with_free_func (NULL);
     761                 :            : 
     762                 :            :   GHashTableIter iter;
     763                 :            :   gpointer value;
     764                 :            : 
     765                 :          5 :   g_hash_table_iter_init (&iter, self->entries);
     766         [ +  + ]:         14 :   while (g_hash_table_iter_next (&iter, NULL, &value))
     767                 :            :     {
     768                 :          9 :       MwsScheduleEntry *entry = MWS_SCHEDULE_ENTRY (value);
     769                 :            : 
     770         [ +  + ]:          9 :       if (g_str_equal (mws_schedule_entry_get_owner (entry), owner))
     771                 :          6 :         g_ptr_array_add (entries_to_remove,
     772                 :          6 :                          (gpointer) mws_schedule_entry_get_id (entry));
     773                 :            :     }
     774                 :            : 
     775                 :          5 :   return mws_scheduler_update_entries (self, NULL, entries_to_remove, error);
     776                 :            : }
     777                 :            : 
     778                 :            : /**
     779                 :            :  * mws_scheduler_get_entry:
     780                 :            :  * @self: a #MwsScheduler
     781                 :            :  * @entry_id: ID of the schedule entry to look up
     782                 :            :  *
     783                 :            :  * Look up the given @entry_id in the scheduler and return it if found;
     784                 :            :  * otherwise return %NULL.
     785                 :            :  *
     786                 :            :  * Returns: (transfer none) (nullable): the found entry, or %NULL
     787                 :            :  * Since: 0.1.0
     788                 :            :  */
     789                 :            : MwsScheduleEntry *
     790                 :          1 : mws_scheduler_get_entry (MwsScheduler *self,
     791                 :            :                          const gchar  *entry_id)
     792                 :            : {
     793         [ -  + ]:          1 :   g_return_val_if_fail (MWS_IS_SCHEDULER (self), NULL);
     794         [ -  + ]:          1 :   g_return_val_if_fail (mws_schedule_entry_id_is_valid (entry_id), NULL);
     795                 :            : 
     796                 :          1 :   return g_hash_table_lookup (self->entries, entry_id);
     797                 :            : }
     798                 :            : 
     799                 :            : /**
     800                 :            :  * mws_scheduler_get_entries:
     801                 :            :  * @self: a #MwsScheduler
     802                 :            :  *
     803                 :            :  * Get the complete set of schedule entries known to the scheduler, as a map of
     804                 :            :  * #MwsScheduleEntry instances indexed by entry ID.
     805                 :            :  *
     806                 :            :  * Returns: (transfer none) (element-type utf8 MwsScheduleEntry): mapping of
     807                 :            :  *    entry IDs to entries
     808                 :            :  * Since: 0.1.0
     809                 :            :  */
     810                 :            : GHashTable *
     811                 :         70 : mws_scheduler_get_entries (MwsScheduler *self)
     812                 :            : {
     813         [ -  + ]:         70 :   g_return_val_if_fail (MWS_IS_SCHEDULER (self), NULL);
     814                 :            : 
     815                 :         70 :   return self->entries;
     816                 :            : }
     817                 :            : 
     818                 :            : /**
     819                 :            :  * mws_scheduler_is_entry_active:
     820                 :            :  * @self: a #MwsScheduler
     821                 :            :  * @entry: the entry
     822                 :            :  *
     823                 :            :  * Checks whether the given entry is currently allowed to be downloaded. This
     824                 :            :  * only checks cached state: it does not recalculate the scheduler state.
     825                 :            :  *
     826                 :            :  * It is an error to call this on an @entry which is not currently in the
     827                 :            :  * scheduler.
     828                 :            :  *
     829                 :            :  * Returns: %TRUE if entry can be downloaded now, %FALSE otherwise
     830                 :            :  * Since: 0.1.0
     831                 :            :  */
     832                 :            : gboolean
     833                 :         40 : mws_scheduler_is_entry_active (MwsScheduler     *self,
     834                 :            :                                MwsScheduleEntry *entry)
     835                 :            : {
     836         [ -  + ]:         40 :   g_return_val_if_fail (MWS_IS_SCHEDULER (self), FALSE);
     837         [ -  + ]:         40 :   g_return_val_if_fail (MWS_IS_SCHEDULE_ENTRY (entry), FALSE);
     838                 :            : 
     839                 :         40 :   const gchar *entry_id = mws_schedule_entry_get_id (entry);
     840                 :         40 :   const EntryData *data = g_hash_table_lookup (self->entries_data, entry_id);
     841         [ -  + ]:         40 :   g_return_val_if_fail (data != NULL, FALSE);
     842                 :            : 
     843         [ +  + ]:         40 :   g_debug ("%s: Entry ‘%s’, active: %s",
     844                 :            :            G_STRFUNC, entry_id, data->is_active ? "yes" : "no");
     845                 :            : 
     846                 :         40 :   return data->is_active;
     847                 :            : }
     848                 :            : 
     849                 :            : static gboolean
     850                 :         11 : reschedule_cb (gpointer user_data)
     851                 :            : {
     852                 :         11 :   MwsScheduler *self = MWS_SCHEDULER (user_data);
     853                 :         11 :   mws_scheduler_reschedule (self);
     854                 :         11 :   return G_SOURCE_REMOVE;
     855                 :            : }
     856                 :            : 
     857                 :            : /* Get the priority of a given peer. Higher returned numbers indicate more
     858                 :            :  * important peers. */
     859                 :            : static gint
     860                 :        146 : get_peer_priority (MwsScheduler     *self,
     861                 :            :                    MwsScheduleEntry *entry)
     862                 :            : {
     863                 :        146 :   const gchar *owner = mws_schedule_entry_get_owner (entry);
     864                 :            :   const gchar *owner_path =
     865                 :        146 :       mws_peer_manager_get_peer_credentials (self->peer_manager, owner);
     866                 :            : 
     867                 :            :   /* If we haven’t got credentials for this peer (which would be unexpected and
     868                 :            :    * indicate a serious problem), give it a low priority. */
     869         [ +  + ]:        146 :   if (owner_path == NULL)
     870                 :         53 :     return G_MININT;
     871                 :            : 
     872                 :            :   /* The OS and app updaters are equally as important as each other. The actual
     873                 :            :    * priority numbers chosen here are fairly arbitrary; it’s the partial order
     874                 :            :    * over them which is important. */
     875   [ +  +  +  + ]:        174 :   if (g_str_equal (owner_path, "/usr/libexec/eos-updater") ||
     876                 :         81 :       g_str_equal (owner_path, "/usr/bin/gnome-software"))
     877                 :         21 :     return G_MAXINT;
     878                 :            : 
     879                 :            :   /* Anything else goes in the range (G_MININT, G_MAXINT). */
     880                 :         72 :   gint priority = g_str_hash (owner_path) + G_MININT;
     881         [ -  + ]:         72 :   if (priority == G_MININT)
     882                 :          0 :     priority += 1;
     883         [ -  + ]:         72 :   if (priority == G_MAXINT)
     884                 :          0 :     priority -= 1;
     885                 :         72 :   return priority;
     886                 :            : }
     887                 :            : 
     888                 :            : /* Compare entries to give a total order by scheduling priority, with the most
     889                 :            :  * important entries for scheduling listed first. i.e. Given entries @a and @b,
     890                 :            :  * this will return a negative number if @a should be scheduled before @b.
     891                 :            :  * This is one of the core parts of the scheduling algorithm; earlier stages
     892                 :            :  * trim any entries which can’t be scheduled (for example, due to wanting to use
     893                 :            :  * a metered connection when the user has disallowed it); later stages select
     894                 :            :  * the most important N entries to actually schedule, according to
     895                 :            :  * parallelisation limits. */
     896                 :            : static gint
     897                 :         73 : entry_compare_cb (gconstpointer a_,
     898                 :            :                   gconstpointer b_,
     899                 :            :                   gpointer      user_data)
     900                 :            : {
     901                 :         73 :   MwsScheduler *self = MWS_SCHEDULER (user_data);
     902                 :         73 :   MwsScheduleEntry *a = MWS_SCHEDULE_ENTRY (*((MwsScheduleEntry **) a_));
     903                 :         73 :   MwsScheduleEntry *b = MWS_SCHEDULE_ENTRY (*((MwsScheduleEntry **) b_));
     904                 :            : 
     905                 :            :   /* As per https://phabricator.endlessm.com/T21327, we want the following
     906                 :            :    * priority order (most important first):
     907                 :            :    *  1. App extensions to com.endlessm.* apps
     908                 :            :    *  2. OS updates
     909                 :            :    *  3. App updates and app extensions to non-Endless apps
     910                 :            :    *  4. Anything else
     911                 :            :    *
     912                 :            :    * We currently have two inputs to base this on: the identity of the peer who
     913                 :            :    * owns a #MwsScheduleEntry, and the priority they set for the entry.
     914                 :            :    * Priorities are scoped to an owner, not global.
     915                 :            :    *
     916                 :            :    * We can implement what we want by hard-coding it so that the scopes for
     917                 :            :    * gnome-software and eos-updater are merged, and both given priority over any
     918                 :            :    * other peer. Then getting the ordering we want is a matter of coordinating
     919                 :            :    * the priorities set by gnome-software and eos-updater on their schedule
     920                 :            :    * entries, which is possible since we control all of that code. */
     921                 :            : 
     922                 :            :   /* Sort by peer first. */
     923                 :         73 :   gint a_peer_priority = get_peer_priority (self, a);
     924                 :         73 :   gint b_peer_priority = get_peer_priority (self, b);
     925                 :            : 
     926         [ +  + ]:         73 :   if (a_peer_priority != b_peer_priority)
     927                 :            :     {
     928                 :         29 :       g_debug ("%s: Comparing schedule entries ‘%s’ and ‘%s’ by peer priority: %d vs %d",
     929                 :            :                G_STRFUNC, mws_schedule_entry_get_id (a),
     930                 :            :                mws_schedule_entry_get_id (b), a_peer_priority, b_peer_priority);
     931         [ +  + ]:         29 :       return (b_peer_priority > a_peer_priority) ? 1 : -1;
     932                 :            :     }
     933                 :            : 
     934                 :            :   /* Within the peer, sort by the priority assigned by that peer to the entry. */
     935                 :         44 :   guint32 a_entry_priority = mws_schedule_entry_get_priority (a);
     936                 :         44 :   guint32 b_entry_priority = mws_schedule_entry_get_priority (b);
     937                 :            : 
     938         [ +  + ]:         44 :   if (a_entry_priority != b_entry_priority)
     939                 :            :     {
     940                 :         36 :       g_debug ("%s: Comparing schedule entries ‘%s’ and ‘%s’ by entry priority: %u vs %u",
     941                 :            :                G_STRFUNC, mws_schedule_entry_get_id (a),
     942                 :            :                mws_schedule_entry_get_id (b), a_entry_priority,
     943                 :            :                b_entry_priority);
     944         [ +  + ]:         36 :       return (b_entry_priority > a_entry_priority) ? 1 : -1;
     945                 :            :     }
     946                 :            : 
     947                 :            :   /* Arbitrarily break ties using the entries’ IDs, which should always be
     948                 :            :    * different. */
     949                 :          8 :   g_debug ("%s: Comparing schedule entries ‘%s’ and ‘%s’ by entry ID",
     950                 :            :            G_STRFUNC, mws_schedule_entry_get_id (a),
     951                 :            :            mws_schedule_entry_get_id (b));
     952                 :          8 :   return g_strcmp0 (mws_schedule_entry_get_id (a),
     953                 :          8 :                     mws_schedule_entry_get_id (b));
     954                 :            : }
     955                 :            : 
     956                 :            : /**
     957                 :            :  * mws_scheduler_reschedule:
     958                 :            :  * @self: a #MwsScheduler
     959                 :            :  *
     960                 :            :  * Calculate an updated download schedule for all currently active entries, and
     961                 :            :  * update the set of active entries if necessary. Changes to the set of active
     962                 :            :  * entries will be signalled using #MwsScheduler::active-entries-changed.
     963                 :            :  *
     964                 :            :  * This is called automatically when the set of entries in the scheduler
     965                 :            :  * changes, or when any relevant input to the scheduler changes; so should not
     966                 :            :  * normally need to be called manually. It is exposed mainly for unit testing.
     967                 :            :  *
     968                 :            :  * Since: 0.1.0
     969                 :            :  */
     970                 :            : void
     971                 :        235 : mws_scheduler_reschedule (MwsScheduler *self)
     972                 :            : {
     973         [ -  + ]:        323 :   g_return_if_fail (MWS_IS_SCHEDULER (self));
     974                 :            : 
     975         [ -  + ]:        235 :   g_assert (!self->in_reschedule);
     976                 :        235 :   self->in_reschedule = TRUE;
     977                 :            : 
     978                 :        235 :   g_debug ("%s: Rescheduling %u entries",
     979                 :            :            G_STRFUNC, g_hash_table_size (self->entries));
     980                 :            : 
     981                 :            :   /* Sanity checks. */
     982         [ -  + ]:        235 :   g_assert (g_hash_table_size (self->entries) ==
     983                 :            :             g_hash_table_size (self->entries_data));
     984                 :            : 
     985                 :            :   /* Clear any pending reschedule. */
     986         [ -  + ]:        235 :   if (self->reschedule_alarm_id != 0)
     987                 :            :     {
     988                 :          0 :       mws_clock_remove_alarm (self->clock, self->reschedule_alarm_id);
     989                 :          0 :       self->reschedule_alarm_id = 0;
     990                 :            :     }
     991                 :            : 
     992                 :            :   /* Preload information from the connection monitor. */
     993                 :        235 :   const gchar * const *all_connection_ids = NULL;
     994                 :        235 :   all_connection_ids = mws_connection_monitor_get_connection_ids (self->connection_monitor);
     995                 :        235 :   gsize n_connections = g_strv_length ((gchar **) all_connection_ids);
     996                 :            : 
     997         [ +  + ]:        470 :   g_autoptr(GArray) all_connection_details = g_array_sized_new (FALSE, FALSE,
     998                 :            :                                                                 sizeof (MwsConnectionDetails),
     999                 :            :                                                                 n_connections);
    1000                 :        235 :   g_array_set_clear_func (all_connection_details,
    1001                 :            :                           (GDestroyNotify) mws_connection_details_clear);
    1002                 :        235 :   g_array_set_size (all_connection_details, n_connections);
    1003                 :            : 
    1004                 :        235 :   gboolean cached_allow_downloads = TRUE;
    1005                 :            : 
    1006         [ +  + ]:        437 :   for (gsize i = 0; all_connection_ids[i] != NULL; i++)
    1007                 :            :     {
    1008                 :        202 :       MwsConnectionDetails *out_details = &g_array_index (all_connection_details,
    1009                 :            :                                                           MwsConnectionDetails, i);
    1010                 :            : 
    1011         [ -  + ]:        202 :       if (!mws_connection_monitor_get_connection_details (self->connection_monitor,
    1012                 :        202 :                                                           all_connection_ids[i],
    1013                 :            :                                                           out_details))
    1014                 :            :         {
    1015                 :            :           /* Fill the details with dummy values. */
    1016                 :          0 :           g_debug ("%s: Failed to get details for connection ‘%s’.",
    1017                 :            :                    G_STRFUNC, all_connection_ids[i]);
    1018                 :          0 :           mws_connection_details_clear (out_details);
    1019                 :          0 :           continue;
    1020                 :            :         }
    1021                 :            : 
    1022                 :            :       /* FIXME: See FIXME below by `can_be_active` about allowing clients to
    1023                 :            :        * specify whether they support downloading from selective connections.
    1024                 :            :        * If that logic changes, so does this. */
    1025   [ +  +  +  + ]:        202 :       cached_allow_downloads = cached_allow_downloads && out_details->allow_downloads;
    1026                 :            :     }
    1027                 :            : 
    1028         [ +  + ]:        235 :   if (self->cached_allow_downloads != cached_allow_downloads)
    1029                 :            :     {
    1030                 :         51 :       g_debug ("%s: Updating cached_allow_downloads from %u to %u",
    1031                 :            :                G_STRFUNC, (guint) self->cached_allow_downloads,
    1032                 :            :                (guint) cached_allow_downloads);
    1033                 :         51 :       self->cached_allow_downloads = cached_allow_downloads;
    1034                 :         51 :       g_object_notify (G_OBJECT (self), "allow-downloads");
    1035                 :            :     }
    1036                 :            : 
    1037                 :            :   /* Fast path. We still have to load the connection monitor information above,
    1038                 :            :    * though, so that we can update self->cached_allow_downloads. */
    1039         [ +  + ]:        235 :   if (g_hash_table_size (self->entries) == 0)
    1040                 :            :     {
    1041                 :         88 :       self->in_reschedule = FALSE;
    1042                 :         88 :       return;
    1043                 :            :     }
    1044                 :            : 
    1045                 :            :   /* As we iterate over all the entries, see when the earliest time we next need
    1046                 :            :    * to reschedule is. */
    1047                 :        294 :   g_autoptr(GDateTime) now = mws_clock_get_now_local (self->clock);
    1048                 :        147 :   g_autoptr(GDateTime) next_reschedule = NULL;
    1049                 :            : 
    1050                 :        294 :   g_autofree gchar *now_str = g_date_time_format (now, "%FT%T%:::z");
    1051                 :        147 :   g_debug ("%s: Considering now = %s", G_STRFUNC, now_str);
    1052                 :            : 
    1053                 :            :   /* For each entry, see if it’s permissible to start downloading it. For the
    1054                 :            :    * moment, we only use whether the network is metered as a basis for this
    1055                 :            :    * calculation. In future, we can factor in the tariff on each connection,
    1056                 :            :    * bandwidth usage, capacity limits, etc. */
    1057                 :        294 :   g_autoptr(GPtrArray) entries_now_active = g_ptr_array_new_with_free_func (NULL);
    1058                 :        294 :   g_autoptr(GPtrArray) entries_were_active = g_ptr_array_new_with_free_func (NULL);
    1059                 :            : 
    1060                 :            :   /* An array of the entries which can be active within the user’s preferences
    1061                 :            :    * for cost. Not necessarily all of them will be chosen to be active, though,
    1062                 :            :    * based on parallelisation limits and prioritisation. */
    1063                 :        294 :   g_autoptr(GPtrArray) entries_can_be_active = g_ptr_array_new_with_free_func (NULL);
    1064                 :            : 
    1065                 :        147 :   g_autoptr(GPtrArray) safe_connections = g_ptr_array_new_full (n_connections, NULL);
    1066                 :            : 
    1067                 :            :   GHashTableIter iter;
    1068                 :            :   gpointer key, value;
    1069                 :        147 :   g_hash_table_iter_init (&iter, self->entries);
    1070                 :            : 
    1071         [ +  + ]:        340 :   while (g_hash_table_iter_next (&iter, &key, &value))
    1072                 :            :     {
    1073                 :        193 :       const gchar *entry_id = key;
    1074                 :        193 :       MwsScheduleEntry *entry = value;
    1075                 :        193 :       EntryData *data = g_hash_table_lookup (self->entries_data, entry_id);
    1076         [ -  + ]:        193 :       g_assert (data != NULL);
    1077                 :            : 
    1078                 :        193 :       g_debug ("%s: Scheduling entry ‘%s’", G_STRFUNC, entry_id);
    1079                 :            : 
    1080                 :            :       /* Work out which connections this entry could be downloaded on safely. */
    1081                 :        193 :       g_ptr_array_set_size (safe_connections, 0);
    1082                 :            : 
    1083         [ +  + ]:        360 :       for (gsize i = 0; all_connection_ids[i] != NULL; i++)
    1084                 :            :         {
    1085                 :            :           /* FIXME: Support multi-path properly by allowing each
    1086                 :            :            * #MwsScheduleEntry to specify which connections it might download
    1087                 :            :            * over. Currently we assume the client might download over any
    1088                 :            :            * active connection. (Typically only one connection will ever be
    1089                 :            :            * active anyway.) */
    1090                 :        167 :           const MwsConnectionDetails *details = &g_array_index (all_connection_details,
    1091                 :            :                                                                 MwsConnectionDetails, i);
    1092                 :            : 
    1093                 :            :           /* If this connection has a tariff specified, work out whether we’ve
    1094                 :            :            * hit any of the limits for the current tariff period. */
    1095                 :        167 :           MwtPeriod *tariff_period = NULL;
    1096                 :        167 :           gboolean tariff_period_reached_capacity_limit = FALSE;
    1097                 :            : 
    1098         [ +  + ]:        167 :           if (details->tariff != NULL)
    1099                 :            :             {
    1100                 :         98 :               tariff_period = mwt_tariff_lookup_period (details->tariff, now);
    1101                 :            :             }
    1102                 :            : 
    1103         [ +  + ]:        167 :           if (tariff_period != NULL)
    1104                 :            :             {
    1105                 :         94 :               g_autofree gchar *tariff_period_start_str =
    1106                 :         94 :                   g_date_time_format (mwt_period_get_start (tariff_period), "%FT%T%:::z");
    1107                 :         94 :               g_autofree gchar *tariff_period_end_str =
    1108                 :         94 :                   g_date_time_format (mwt_period_get_end (tariff_period), "%FT%T%:::z");
    1109                 :         94 :               g_debug ("%s: Considering tariff period %p: %s to %s",
    1110                 :            :                        G_STRFUNC, tariff_period, tariff_period_start_str,
    1111                 :            :                        tariff_period_end_str);
    1112                 :            :             }
    1113                 :            :           else
    1114                 :            :             {
    1115                 :         73 :               g_debug ("%s: No tariff period found", G_STRFUNC);
    1116                 :            :             }
    1117                 :            : 
    1118         [ +  + ]:        167 :           if (tariff_period != NULL)
    1119                 :            :             {
    1120                 :            :               /* FIXME: For the moment, we can only see if the capacity limit is
    1121                 :            :                * hard-coded to zero to indicate a period when downloads are
    1122                 :            :                * banned. In future, we will need to query the amount of data
    1123                 :            :                * downloaded in the current period and check it against the
    1124                 :            :                * limit (plus do a reschedule when the amount of data downloaded
    1125                 :            :                * does reach the limit). */
    1126                 :         94 :               tariff_period_reached_capacity_limit =
    1127                 :         94 :                   (mwt_period_get_capacity_limit (tariff_period) == 0);
    1128                 :            :             }
    1129                 :            : 
    1130                 :            :           /* Is it safe to schedule this entry on this connection now? */
    1131                 :        369 :           gboolean is_safe = ((details->metered == MWS_METERED_NO ||
    1132         [ +  - ]:         35 :                                details->metered == MWS_METERED_GUESS_NO ||
    1133         [ +  + ]:         35 :                                details->allow_downloads_when_metered) &&
    1134   [ +  +  +  +  :        202 :                               details->allow_downloads &&
                   +  + ]
    1135                 :            :                               !tariff_period_reached_capacity_limit);
    1136   [ +  +  +  +  :        167 :           g_debug ("%s: Connection ‘%s’ is %s to download entry ‘%s’ on "
             +  +  +  + ]
    1137                 :            :                    "(metered: %s, allow-downloads-when-metered: %s, "
    1138                 :            :                    "allow-downloads: %s, tariff-period-reached-capacity-limit: %s).",
    1139                 :            :                    G_STRFUNC, all_connection_ids[i],
    1140                 :            :                    is_safe ? "safe" : "not safe", entry_id,
    1141                 :            :                    mws_metered_to_string (details->metered),
    1142                 :            :                    details->allow_downloads_when_metered ? "yes" : "no",
    1143                 :            :                    details->allow_downloads ? "yes" : "no",
    1144                 :            :                    tariff_period_reached_capacity_limit ? "yes" : "no");
    1145                 :            : 
    1146         [ +  + ]:        167 :           if (is_safe)
    1147                 :         85 :             g_ptr_array_add (safe_connections, (gpointer) all_connection_ids[i]);
    1148                 :            : 
    1149                 :            :           /* Work out when to do the next reschedule due to this tariff changing
    1150                 :            :            * periods. */
    1151         [ +  + ]:        167 :           if (details->tariff != NULL)
    1152                 :            :             {
    1153                 :         98 :               g_autoptr(GDateTime) next_transition = NULL;
    1154                 :         98 :               next_transition = mwt_tariff_get_next_transition (details->tariff, now,
    1155                 :            :                                                                 NULL, NULL);
    1156                 :            : 
    1157                 :         98 :               g_autofree gchar *next_transition_str = NULL;
    1158         [ +  + ]:         98 :               if (next_transition != NULL)
    1159                 :         96 :                 next_transition_str = g_date_time_format (next_transition, "%FT%T%:::z");
    1160                 :            :               else
    1161                 :          2 :                 next_transition_str = g_strdup ("never");
    1162                 :         98 :               g_debug ("%s: Connection ‘%s’ next transition is %s",
    1163                 :            :                        G_STRFUNC, all_connection_ids[i], next_transition_str);
    1164                 :            : 
    1165   [ +  +  +  - ]:        194 :               if (next_transition != NULL &&
    1166                 :         96 :                   g_date_time_compare (now, next_transition) < 0 &&
    1167   [ +  +  +  + ]:        112 :                   (next_reschedule == NULL ||
    1168                 :         16 :                    g_date_time_compare (next_transition, next_reschedule) < 0))
    1169                 :            :                 {
    1170         [ +  + ]:         88 :                   g_clear_pointer (&next_reschedule, g_date_time_unref);
    1171                 :         88 :                   next_reschedule = g_date_time_ref (next_transition);
    1172                 :            :                 }
    1173                 :            :             }
    1174                 :            :         }
    1175                 :            : 
    1176                 :            :       /* If all the active connections are safe for this entry, it can be made
    1177                 :            :        * active. We assume that the client cannot support downloading over a
    1178                 :            :        * particular connection and ignoring another: all active connections have
    1179                 :            :        * to be safe to start a download.
    1180                 :            :        * FIXME: Allow clients to specify whether they support downloading from
    1181                 :            :        * selective connections. If so, their downloads could be made active
    1182                 :            :        * without all active connections having to be safe. */
    1183                 :        193 :       gboolean can_be_active = (safe_connections->len == n_connections);
    1184         [ +  + ]:        193 :       g_debug ("%s: Entry ‘%s’ %s (%u of %" G_GSIZE_FORMAT " connections are safe)",
    1185                 :            :                G_STRFUNC, entry_id,
    1186                 :            :                can_be_active ? "can be active" : "cannot be active",
    1187                 :            :                safe_connections->len, n_connections);
    1188                 :            : 
    1189         [ +  + ]:        193 :       if (can_be_active)
    1190                 :            :         {
    1191                 :        125 :           g_ptr_array_add (entries_can_be_active, entry);
    1192                 :            :         }
    1193                 :            :       else
    1194                 :            :         {
    1195                 :            :           /* Accounting for the signal emission at the end of the function. */
    1196         [ +  + ]:         68 :           if (data->is_active)
    1197                 :         12 :             g_ptr_array_add (entries_were_active, entry);
    1198                 :            : 
    1199                 :            :           /* Update this entry’s status. */
    1200                 :         68 :           data->is_active = FALSE;
    1201                 :            :         }
    1202                 :            :     }
    1203                 :            : 
    1204                 :            :   /* Order the potentially-active entries by priority. */
    1205                 :        147 :   g_ptr_array_sort_with_data (entries_can_be_active, entry_compare_cb, self);
    1206                 :            : 
    1207                 :            :   /* Take the most important N potentially-active entries and actually mark them
    1208                 :            :    * as active; mark the rest as not active. N is the maximum number of active
    1209                 :            :    * entries set at construction time for the scheduler. */
    1210         [ +  + ]:        272 :   for (gsize i = 0; i < entries_can_be_active->len; i++)
    1211                 :            :     {
    1212                 :        125 :       MwsScheduleEntry *entry = MWS_SCHEDULE_ENTRY (g_ptr_array_index (entries_can_be_active, i));
    1213                 :        125 :       const gchar *entry_id = mws_schedule_entry_get_id (entry);
    1214                 :        125 :       EntryData *data = g_hash_table_lookup (self->entries_data, entry_id);
    1215         [ -  + ]:        125 :       g_assert (data != NULL);
    1216                 :            : 
    1217                 :        125 :       gboolean active = (i < self->max_active_entries);
    1218         [ +  + ]:        125 :       g_debug ("%s: Entry ‘%s’ %s (index %" G_GSIZE_FORMAT " of %u sorted "
    1219                 :            :                "entries which can be active; limit of %u which will be active)",
    1220                 :            :                G_STRFUNC, entry_id,
    1221                 :            :                active ? "will be active" : "will not be active",
    1222                 :            :                i, entries_can_be_active->len, self->max_active_entries);
    1223                 :            : 
    1224                 :            :       /* Accounting for the signal emission at the end of the function. */
    1225   [ +  +  +  + ]:        125 :       if (data->is_active && !active)
    1226                 :          1 :         g_ptr_array_add (entries_were_active, entry);
    1227   [ +  +  +  + ]:        124 :       else if (!data->is_active && active)
    1228                 :         48 :         g_ptr_array_add (entries_now_active, entry);
    1229                 :            : 
    1230                 :            :       /* Update this entry’s status. */
    1231                 :        125 :       data->is_active = active;
    1232                 :            :     }
    1233                 :            : 
    1234                 :            :   /* Signal the changes. */
    1235   [ +  +  +  + ]:        147 :   if (entries_now_active->len > 0 || entries_were_active->len > 0)
    1236                 :            :     {
    1237                 :         59 :       g_debug ("%s: Emitting active-entries-changed with %u now active, %u no longer active",
    1238                 :            :                G_STRFUNC, entries_now_active->len, entries_were_active->len);
    1239                 :         59 :       g_signal_emit_by_name (G_OBJECT (self), "active-entries-changed",
    1240                 :            :                              entries_now_active, entries_were_active);
    1241                 :            :     }
    1242                 :            : 
    1243                 :            :   /* Set up the next scheduling run. */
    1244         [ +  + ]:        147 :   if (next_reschedule != NULL)
    1245                 :            :     {
    1246                 :            :       /* FIXME: This doesn’t take into account the difference between monotonic
    1247                 :            :        * and wall clock time: if the computer suspends, or the timezone changes,
    1248                 :            :        * the reschedule will happen at the wrong time. We probably want to split
    1249                 :            :        * this out into a separate interface, with an implementation which can
    1250                 :            :        * monitor org.freedesktop.timedate1. */
    1251                 :         80 :       GTimeSpan interval = g_date_time_difference (next_reschedule, now);
    1252         [ -  + ]:         80 :       g_assert (interval >= 0);
    1253                 :            : 
    1254                 :         80 :       mws_clock_add_alarm (self->clock, next_reschedule, reschedule_cb, self, NULL);
    1255                 :            : 
    1256                 :         80 :       g_autofree gchar *next_reschedule_str = NULL;
    1257                 :         80 :       next_reschedule_str = g_date_time_format (next_reschedule, "%FT%T%:::z");
    1258                 :         80 :       g_debug ("%s: Setting next reschedule for %s (in %" G_GUINT64_FORMAT " seconds)",
    1259                 :            :                G_STRFUNC, next_reschedule_str, (guint64) interval / G_USEC_PER_SEC);
    1260                 :            :     }
    1261                 :            :   else
    1262                 :            :     {
    1263                 :         67 :       g_debug ("%s: Setting next reschedule to never", G_STRFUNC);
    1264                 :            :     }
    1265                 :            : 
    1266                 :        147 :   self->in_reschedule = FALSE;
    1267                 :            : }
    1268                 :            : 
    1269                 :            : /**
    1270                 :            :  * mws_scheduler_get_allow_downloads:
    1271                 :            :  * @self: a #MwsScheduler
    1272                 :            :  *
    1273                 :            :  * Get the value of #MwsScheduler:allow-downloads.
    1274                 :            :  *
    1275                 :            :  * Returns: %TRUE if the user has indicated that at least one of the active
    1276                 :            :  *    network connections should be used for large downloads, %FALSE otherwise
    1277                 :            :  * Since: 0.1.0
    1278                 :            :  */
    1279                 :            : gboolean
    1280                 :         28 : mws_scheduler_get_allow_downloads (MwsScheduler *self)
    1281                 :            : {
    1282         [ -  + ]:         28 :   g_return_val_if_fail (MWS_IS_SCHEDULER (self), TRUE);
    1283                 :            : 
    1284                 :         28 :   return self->cached_allow_downloads;
    1285                 :            : }

Generated by: LCOV version 1.16