1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * Copyright (C) 2009-2015 Richard Hughes <richard@hughsie.com>
4  *
5  * Most of this code was taken from Zif, libzif/zif-state.c
6  *
7  * Licensed under the GNU Lesser General Public License Version 2.1
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or(at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
22  */
23 
24 /**
25  * SECTION:dnf-state
26  * @short_description: Progress reporting
27  * @include: libdnf.h
28  * @stability: Unstable
29  *
30  * Objects can use dnf_state_set_percentage() if the absolute percentage
31  * is known. Percentages should always go up, not down.
32  *
33  * Modules usually set the number of steps that are expected using
34  * dnf_state_set_number_steps() and then after each section is completed,
35  * the dnf_state_done() function should be called. This will automatically
36  * call dnf_state_set_percentage() with the correct values.
37  *
38  * #DnfState allows sub-modules to be "chained up" to the parent module
39  * so that as the sub-module progresses, so does the parent.
40  * The child can be reused for each section, and chains can be deep.
41  *
42  * To get a child object, you should use dnf_state_get_child() and then
43  * use the result in any sub-process. You should ensure that the child
44  * is not re-used without calling dnf_state_done().
45  *
46  * There are a few nice touches in this module, so that if a module only has
47  * one progress step, the child progress is used for updates.
48  *
49  * <example>
50  *   <title>Using a #DnfState.</title>
51  *   <programlisting>
52  * static void
53  * _do_something(DnfState *state)
54  * {
55  *    DnfState *state_local;
56  *
57  *    // setup correct number of steps
58  *    dnf_state_set_number_steps(state, 2);
59  *
60  *    // we can't cancel this function
61  *    dnf_state_set_allow_cancel(state, FALSE);
62  *
63  *    // run a sub function
64  *    state_local = dnf_state_get_child(state);
65  *    _do_something_else1(state_local);
66  *
67  *    // this section done
68  *    dnf_state_done(state);
69  *
70  *    // run another sub function
71  *    state_local = dnf_state_get_child(state);
72  *    _do_something_else2(state_local);
73  *
74  *    // this section done(all complete)
75  *    dnf_state_done(state);
76  * }
77  *   </programlisting>
78  * </example>
79  *
80  * See also: #DnfLock
81  */
82 
83 
84 #include "catch-error.hpp"
85 #include "dnf-state.h"
86 #include "dnf-utils.h"
87 
88 #include "utils/bgettext/bgettext-lib.h"
89 
90 typedef struct
91 {
92     gboolean         allow_cancel;
93     gboolean         allow_cancel_changed_state;
94     gboolean         allow_cancel_child;
95     gboolean         enable_profile;
96     gboolean         report_progress;
97     GCancellable    *cancellable;
98     gchar            *action_hint;
99     gchar            *id;
100     gdouble          *step_profile;
101     GTimer           *timer;
102     guint64           speed;
103     guint64          *speed_data;
104     guint             current;
105     guint             last_percentage;
106     guint            *step_data;
107     guint             steps;
108     gulong            action_child_id;
109     gulong            package_progress_child_id;
110     gulong            notify_speed_child_id;
111     gulong            allow_cancel_child_id;
112     gulong            percentage_child_id;
113     DnfStateAction    action;
114     DnfStateAction    last_action;
115     DnfStateAction    child_action;
116     DnfState         *child;
117     DnfState         *parent;
118     GPtrArray        *lock_ids;
119     DnfLock          *lock;
120 } DnfStatePrivate;
121 
122 enum {
123     SIGNAL_PERCENTAGE_CHANGED,
124     SIGNAL_SUBPERCENTAGE_CHANGED,
125     SIGNAL_ALLOW_CANCEL_CHANGED,
126     SIGNAL_ACTION_CHANGED,
127     SIGNAL_PACKAGE_PROGRESS_CHANGED,
128     SIGNAL_LAST
129 };
130 
131 enum {
132     PROP_0,
133     PROP_SPEED,
134     PROP_LAST
135 };
136 
137 static guint signals [SIGNAL_LAST] = { 0 };
138 
G_DEFINE_TYPE_WITH_PRIVATE(DnfState,dnf_state,G_TYPE_OBJECT)139 G_DEFINE_TYPE_WITH_PRIVATE(DnfState, dnf_state, G_TYPE_OBJECT)
140 #define GET_PRIVATE(o) (static_cast<DnfStatePrivate *>(dnf_state_get_instance_private (o)))
141 
142 #define DNF_STATE_SPEED_SMOOTHING_ITEMS        5
143 
144 /**
145  * dnf_state_finalize:
146  **/
147 static void
148 dnf_state_finalize(GObject *object)
149 {
150     DnfState *state = DNF_STATE(object);
151     DnfStatePrivate *priv = GET_PRIVATE(state);
152 
153     /* no more locks */
154     dnf_state_release_locks(state);
155 
156     dnf_state_reset(state);
157     g_free(priv->id);
158     g_free(priv->action_hint);
159     g_free(priv->step_data);
160     g_free(priv->step_profile);
161     if (priv->cancellable != NULL)
162         g_object_unref(priv->cancellable);
163     g_timer_destroy(priv->timer);
164     g_free(priv->speed_data);
165     g_ptr_array_unref(priv->lock_ids);
166     g_object_unref(priv->lock);
167 
168     G_OBJECT_CLASS(dnf_state_parent_class)->finalize(object);
169 }
170 
171 /**
172  * dnf_state_init:
173  **/
174 static void
dnf_state_init(DnfState * state)175 dnf_state_init(DnfState *state)
176 {
177     DnfStatePrivate *priv = GET_PRIVATE(state);
178     priv->allow_cancel = TRUE;
179     priv->allow_cancel_child = TRUE;
180     priv->action = DNF_STATE_ACTION_UNKNOWN;
181     priv->last_action = DNF_STATE_ACTION_UNKNOWN;
182     priv->timer = g_timer_new();
183     priv->lock_ids = g_ptr_array_new();
184     priv->report_progress = TRUE;
185     priv->lock = dnf_lock_new();
186     priv->speed_data = g_new0(guint64, DNF_STATE_SPEED_SMOOTHING_ITEMS);
187 }
188 
189 /**
190  * dnf_state_get_property:
191  **/
192 static void
dnf_state_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)193 dnf_state_get_property(GObject *object,
194             guint prop_id,
195             GValue *value,
196             GParamSpec *pspec)
197 {
198     DnfState *state = DNF_STATE(object);
199     DnfStatePrivate *priv = GET_PRIVATE(state);
200 
201     switch(prop_id) {
202     case PROP_SPEED:
203         g_value_set_uint64(value, priv->speed);
204         break;
205     default:
206         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
207         break;
208     }
209 }
210 
211 /**
212  * dnf_state_set_property:
213  **/
214 static void
dnf_state_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)215 dnf_state_set_property(GObject *object,
216             guint prop_id,
217             const GValue *value,
218             GParamSpec *pspec)
219 {
220     DnfState *state = DNF_STATE(object);
221     DnfStatePrivate *priv = GET_PRIVATE(state);
222 
223     switch(prop_id) {
224     case PROP_SPEED:
225         priv->speed = g_value_get_uint64(value);
226         break;
227     default:
228         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
229         break;
230     }
231 }
232 
233 /**
234  * dnf_state_class_init:
235  **/
236 static void
dnf_state_class_init(DnfStateClass * klass)237 dnf_state_class_init(DnfStateClass *klass)
238 {
239     GParamSpec *pspec;
240     GObjectClass *object_class = G_OBJECT_CLASS(klass);
241     object_class->finalize = dnf_state_finalize;
242     object_class->get_property = dnf_state_get_property;
243     object_class->set_property = dnf_state_set_property;
244 
245     /**
246      * DnfState:speed:
247      **/
248     pspec = g_param_spec_uint64("speed", NULL, NULL,
249                                 0, G_MAXUINT64, 0,
250                                 G_PARAM_READABLE);
251     g_object_class_install_property(object_class, PROP_SPEED, pspec);
252 
253     signals [SIGNAL_PERCENTAGE_CHANGED] =
254         g_signal_new("percentage-changed",
255                      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
256                      G_STRUCT_OFFSET(DnfStateClass, percentage_changed),
257                      NULL, NULL, g_cclosure_marshal_VOID__UINT,
258                      G_TYPE_NONE, 1, G_TYPE_UINT);
259 
260     signals [SIGNAL_ALLOW_CANCEL_CHANGED] =
261         g_signal_new("allow-cancel-changed",
262                      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
263                      G_STRUCT_OFFSET(DnfStateClass, allow_cancel_changed),
264                      NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN,
265                      G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
266 
267     signals [SIGNAL_ACTION_CHANGED] =
268         g_signal_new("action-changed",
269                      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
270                      G_STRUCT_OFFSET(DnfStateClass, action_changed),
271                      NULL, NULL, g_cclosure_marshal_generic,
272                      G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
273 
274     signals [SIGNAL_PACKAGE_PROGRESS_CHANGED] =
275         g_signal_new("package-progress-changed",
276                      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
277                      G_STRUCT_OFFSET(DnfStateClass, package_progress_changed),
278                      NULL, NULL, g_cclosure_marshal_generic,
279                      G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT);
280 }
281 
282 /**
283  * dnf_state_set_report_progress:
284  * @state: A #DnfState
285  * @report_progress: if we care about percentage status
286  *
287  * This disables progress tracking for DnfState. This is generally a bad
288  * thing to do, except when you know you cannot guess the number of steps
289  * in the state.
290  *
291  * Using this function also reduced the amount of time spent getting a
292  * child state using dnf_state_get_child() as a refcounted version of
293  * the parent is returned instead. *
294  * Since: 0.1.0
295  **/
296 void
dnf_state_set_report_progress(DnfState * state,gboolean report_progress)297 dnf_state_set_report_progress(DnfState *state, gboolean report_progress)
298 {
299     DnfStatePrivate *priv = GET_PRIVATE(state);
300     priv->report_progress = report_progress;
301 }
302 
303 /**
304  * dnf_state_set_enable_profile:
305  * @state: A #DnfState
306  * @enable_profile: if profiling should be enabled
307  *
308  * This enables profiling of DnfState. This may be useful in development,
309  * but be warned; enabling profiling makes #DnfState very slow.
310  *
311  * Since: 0.1.0
312  **/
313 void
dnf_state_set_enable_profile(DnfState * state,gboolean enable_profile)314 dnf_state_set_enable_profile(DnfState *state, gboolean enable_profile)
315 {
316     DnfStatePrivate *priv = GET_PRIVATE(state);
317     priv->enable_profile = enable_profile;
318 }
319 
320 /**
321  * dnf_state_take_lock:
322  * @state: A #DnfState
323  * @lock_type: A #DnfLockType, e.g. %DNF_LOCK_TYPE_RPMDB
324  * @lock_mode: A #DnfLockMode, e.g. %DNF_LOCK_MODE_PROCESS
325  * @error: A #GError
326  *
327  * Takes a lock of a specified type.
328  * The lock is automatically free'd when the DnfState has been completed.
329  *
330  * You can call dnf_state_take_lock() multiple times with different or
331  * even the same @lock_type value.
332  *
333  * Returns: %FALSE if the lock is fatal, %TRUE otherwise
334  *
335  * Since: 0.1.0
336  **/
337 gboolean
dnf_state_take_lock(DnfState * state,DnfLockType lock_type,DnfLockMode lock_mode,GError ** error)338 dnf_state_take_lock(DnfState *state,
339                     DnfLockType lock_type,
340                     DnfLockMode lock_mode,
341                     GError **error) try
342 {
343     DnfStatePrivate *priv = GET_PRIVATE(state);
344     guint lock_id = 0;
345 
346     /* no custom handler */
347     lock_id = dnf_lock_take(priv->lock,
348                             lock_type,
349                             lock_mode,
350                             error);
351     if (lock_id == 0)
352         return FALSE;
353 
354     /* add the lock to an array so we can release on completion */
355     g_debug("adding lock %i", lock_id);
356     g_ptr_array_add(priv->lock_ids,
357                     GUINT_TO_POINTER(lock_id));
358     return TRUE;
359 } CATCH_TO_GERROR(FALSE)
360 
361 /**
362  * dnf_state_discrete_to_percent:
363  **/
364 static gfloat
365 dnf_state_discrete_to_percent(guint discrete, guint steps)
366 {
367     /* check we are in range */
368     if (discrete > steps)
369         return 100;
370     if (steps == 0) {
371         g_warning("steps is 0!");
372         return 0;
373     }
374     return((gfloat) ((gdouble) discrete *(100.0 /(gdouble)(steps))));
375 }
376 
377 /**
378  * dnf_state_print_parent_chain:
379  **/
380 static void
dnf_state_print_parent_chain(DnfState * state,guint level)381 dnf_state_print_parent_chain(DnfState *state, guint level)
382 {
383     DnfStatePrivate *priv = GET_PRIVATE(state);
384     if (priv->parent != NULL)
385         dnf_state_print_parent_chain(priv->parent, level + 1);
386     g_print("%i) %s(%i/%i)\n",
387             level, priv->id, priv->current, priv->steps);
388 }
389 
390 /**
391  * dnf_state_get_cancellable:
392  * @state: A #DnfState
393  *
394  * Gets the #GCancellable for this operation
395  *
396  * Returns:(transfer none): The #GCancellable or %NULL
397  *
398  * Since: 0.1.0
399  **/
400 GCancellable *
dnf_state_get_cancellable(DnfState * state)401 dnf_state_get_cancellable(DnfState *state)
402 {
403     DnfStatePrivate *priv = GET_PRIVATE(state);
404     return priv->cancellable;
405 }
406 
407 /**
408  * dnf_state_set_cancellable:
409  * @state: a #DnfState instance.
410  * @cancellable: The #GCancellable which is used to cancel tasks, or %NULL
411  *
412  * Sets the #GCancellable object to use.
413  *
414  * Since: 0.1.0
415  **/
416 void
dnf_state_set_cancellable(DnfState * state,GCancellable * cancellable)417 dnf_state_set_cancellable(DnfState *state, GCancellable *cancellable)
418 {
419     DnfStatePrivate *priv = GET_PRIVATE(state);
420     g_return_if_fail(priv->cancellable == NULL);
421     if (priv->cancellable != NULL)
422         g_clear_object(&priv->cancellable);
423     if (cancellable != NULL)
424         priv->cancellable = static_cast<GCancellable *>(g_object_ref(cancellable));
425 }
426 
427 /**
428  * dnf_state_get_allow_cancel:
429  * @state: A #DnfState
430  *
431  * Gets if the sub-task(or one of it's sub-sub-tasks) is cancellable
432  *
433  * Returns: %TRUE if the translation has a chance of being cancelled
434  *
435  * Since: 0.1.0
436  **/
437 gboolean
dnf_state_get_allow_cancel(DnfState * state)438 dnf_state_get_allow_cancel(DnfState *state)
439 {
440     DnfStatePrivate *priv = GET_PRIVATE(state);
441     return priv->allow_cancel && priv->allow_cancel_child;
442 }
443 
444 /**
445  * dnf_state_set_allow_cancel:
446  * @state: A #DnfState
447  * @allow_cancel: If this sub-task can be cancelled
448  *
449  * Set is this sub task can be cancelled safely.
450  *
451  * Since: 0.1.0
452  **/
453 void
dnf_state_set_allow_cancel(DnfState * state,gboolean allow_cancel)454 dnf_state_set_allow_cancel(DnfState *state, gboolean allow_cancel)
455 {
456     DnfStatePrivate *priv = GET_PRIVATE(state);
457 
458     priv->allow_cancel_changed_state = TRUE;
459 
460     /* quick optimisation that saves lots of signals */
461     if (priv->allow_cancel == allow_cancel)
462         return;
463     priv->allow_cancel = allow_cancel;
464 
465     /* just emit if both this and child is okay */
466     g_signal_emit(state, signals [SIGNAL_ALLOW_CANCEL_CHANGED], 0,
467                   priv->allow_cancel && priv->allow_cancel_child);
468 }
469 
470 /**
471  * dnf_state_get_speed:
472  * @state: a #DnfState instance.
473  *
474  * Gets the transaction speed in bytes per second.
475  *
476  * Returns: speed, or 0 for unknown.
477  *
478  * Since: 0.1.0
479  **/
480 guint64
dnf_state_get_speed(DnfState * state)481 dnf_state_get_speed(DnfState *state)
482 {
483     DnfStatePrivate *priv = GET_PRIVATE(state);
484     return priv->speed;
485 }
486 
487 /**
488  * dnf_state_set_speed-private:
489  **/
490 static void
dnf_state_set_speed_internal(DnfState * state,guint64 speed)491 dnf_state_set_speed_internal(DnfState *state, guint64 speed)
492 {
493     DnfStatePrivate *priv = GET_PRIVATE(state);
494     if (priv->speed == speed)
495         return;
496     priv->speed = speed;
497     g_object_notify(G_OBJECT(state), "speed");
498 }
499 
500 /**
501  * dnf_state_set_speed:
502  * @state: a #DnfState instance.
503  * @speed: The transaction speed.
504  *
505  * Sets the download or install transaction speed in bytes per second.
506  *
507  * Since: 0.1.0
508  **/
509 void
dnf_state_set_speed(DnfState * state,guint64 speed)510 dnf_state_set_speed(DnfState *state, guint64 speed)
511 {
512     DnfStatePrivate *priv = GET_PRIVATE(state);
513     guint i;
514     guint64 sum = 0;
515     guint sum_cnt = 0;
516 
517     /* move the data down one entry */
518     for (i=DNF_STATE_SPEED_SMOOTHING_ITEMS-1; i > 0; i--)
519         priv->speed_data[i] = priv->speed_data[i-1];
520     priv->speed_data[0] = speed;
521 
522     /* get the average */
523     for (i = 0; i < DNF_STATE_SPEED_SMOOTHING_ITEMS; i++) {
524         if (priv->speed_data[i] > 0) {
525             sum += priv->speed_data[i];
526             sum_cnt++;
527         }
528     }
529     if (sum_cnt > 0)
530         sum /= sum_cnt;
531 
532     dnf_state_set_speed_internal(state, sum);
533 }
534 
535 /**
536  * dnf_state_release_locks:
537  * @state: a #DnfState instance.
538  *
539  * Releases all locks used in the state.
540  *
541  * Returns: %TRUE for success, %FALSE otherwise
542  *
543  * Since: 0.1.0
544  **/
545 gboolean
dnf_state_release_locks(DnfState * state)546 dnf_state_release_locks(DnfState *state)
547 {
548     DnfStatePrivate *priv = GET_PRIVATE(state);
549     guint i;
550     guint lock_id;
551 
552     /* release children first */
553     if (priv->child != NULL)
554         dnf_state_release_locks(priv->child);
555 
556     /* release each one */
557     for (i = 0; i < priv->lock_ids->len; i++) {
558         lock_id = GPOINTER_TO_UINT(g_ptr_array_index(priv->lock_ids, i));
559         g_debug("releasing lock %i", lock_id);
560         if (!dnf_lock_release(priv->lock, lock_id, NULL))
561             return FALSE;
562     }
563     g_ptr_array_set_size(priv->lock_ids, 0);
564     return TRUE;
565 }
566 
567 /**
568  * dnf_state_set_percentage:
569  * @state: a #DnfState instance.
570  * @percentage: Percentage value between 0% and 100%
571  *
572  * Set a percentage manually.
573  * NOTE: this must be above what was previously set, or it will be rejected.
574  *
575  * Returns: %TRUE if the signal was propagated, %FALSE otherwise
576  *
577  * Since: 0.1.0
578  **/
579 gboolean
dnf_state_set_percentage(DnfState * state,guint percentage)580 dnf_state_set_percentage(DnfState *state, guint percentage)
581 {
582     DnfStatePrivate *priv = GET_PRIVATE(state);
583 
584     /* do we care */
585     if (!priv->report_progress)
586         return TRUE;
587 
588     /* is it the same */
589     if (percentage == priv->last_percentage)
590         return FALSE;
591 
592     /* is it invalid */
593     if (percentage > 100) {
594         dnf_state_print_parent_chain(state, 0);
595         g_warning("percentage %i%% is invalid on %p!",
596                   percentage, state);
597         return FALSE;
598     }
599 
600     /* is it less */
601     if (percentage < priv->last_percentage) {
602         if (priv->enable_profile) {
603             dnf_state_print_parent_chain(state, 0);
604             g_warning("percentage should not go down from %i to %i on %p!",
605                       priv->last_percentage, percentage, state);
606         }
607         return FALSE;
608     }
609 
610     /* we're done, so we're not preventing cancellation anymore */
611     if (percentage == 100 && !priv->allow_cancel) {
612         g_debug("done, so allow cancel 1 for %p", state);
613         dnf_state_set_allow_cancel(state, TRUE);
614     }
615 
616     /* automatically cancel any action */
617     if (percentage == 100 && priv->action != DNF_STATE_ACTION_UNKNOWN)
618         dnf_state_action_stop(state);
619 
620     /* speed no longer valid */
621     if (percentage == 100)
622         dnf_state_set_speed_internal(state, 0);
623 
624     /* release locks? */
625     if (percentage == 100) {
626         if (!dnf_state_release_locks(state))
627             return FALSE;
628     }
629 
630     /* save */
631     priv->last_percentage = percentage;
632 
633     /* emit */
634     g_signal_emit(state, signals [SIGNAL_PERCENTAGE_CHANGED], 0, percentage);
635 
636     /* success */
637     return TRUE;
638 }
639 
640 /**
641  * dnf_state_get_percentage:
642  * @state: a #DnfState instance.
643  *
644  * Get the percentage state.
645  *
646  * Returns: The percentage value, or %G_MAXUINT for error
647  *
648  * Since: 0.1.0
649  **/
650 guint
dnf_state_get_percentage(DnfState * state)651 dnf_state_get_percentage(DnfState *state)
652 {
653     DnfStatePrivate *priv = GET_PRIVATE(state);
654     return priv->last_percentage;
655 }
656 
657 /**
658  * dnf_state_action_start:
659  * @state: a #DnfState instance.
660  * @action: An action, e.g. %DNF_STATE_ACTION_DECOMPRESSING
661  * @action_hint: A hint on what the action is doing, e.g. "/var/cache/yum/i386/15/koji/primary.sqlite"
662  *
663  * Sets the action which is being performed. This is emitted up the chain
664  * to any parent %DnfState objects, using the action-changed signal.
665  *
666  * If a %DnfState reaches 100% then it is automatically stopped with a
667  * call to dnf_state_action_stop().
668  *
669  * It is allowed to call dnf_state_action_start() more than once for a
670  * given %DnfState instance.
671  *
672  * Returns: %TRUE if the signal was propagated, %FALSE otherwise
673  *
674  * Since: 0.1.0
675  **/
676 gboolean
dnf_state_action_start(DnfState * state,DnfStateAction action,const gchar * action_hint)677 dnf_state_action_start(DnfState *state, DnfStateAction action, const gchar *action_hint)
678 {
679     DnfStatePrivate *priv = GET_PRIVATE(state);
680 
681     /* ignore this */
682     if (action == DNF_STATE_ACTION_UNKNOWN) {
683         g_warning("cannot set action DNF_STATE_ACTION_UNKNOWN");
684         return FALSE;
685     }
686 
687     /* is different? */
688     if (priv->action == action &&
689         g_strcmp0(action_hint, priv->action_hint) == 0)
690         return FALSE;
691 
692     /* remember for stop */
693     priv->last_action = priv->action;
694 
695     /* save hint */
696     g_free(priv->action_hint);
697     priv->action_hint = g_strdup(action_hint);
698 
699     /* save */
700     priv->action = action;
701 
702     /* just emit */
703     g_signal_emit(state, signals [SIGNAL_ACTION_CHANGED], 0, action, action_hint);
704     return TRUE;
705 }
706 
707 /**
708  * dnf_state_set_package_progress:
709  * @state: a #DnfState instance.
710  * @dnf_package_get_id: A package_id
711  * @action: A #DnfStateAction
712  * @percentage: A percentage
713  *
714  * Sets any package progress.
715  *
716  * Since: 0.1.0
717  **/
718 void
dnf_state_set_package_progress(DnfState * state,const gchar * dnf_package_get_id,DnfStateAction action,guint percentage)719 dnf_state_set_package_progress(DnfState *state,
720                 const gchar *dnf_package_get_id,
721                 DnfStateAction action,
722                 guint percentage)
723 {
724     g_return_if_fail(dnf_package_get_id != NULL);
725     g_return_if_fail(action != DNF_STATE_ACTION_UNKNOWN);
726     g_return_if_fail(percentage <= 100);
727 
728     /* just emit */
729     g_signal_emit(state, signals [SIGNAL_PACKAGE_PROGRESS_CHANGED], 0,
730                dnf_package_get_id, action, percentage);
731 }
732 
733 /**
734  * dnf_state_action_stop:
735  * @state: A #DnfState
736  *
737  * Returns the DnfState to it's previous value.
738  * It is not expected you will ever need to use this function.
739  *
740  * Returns: %TRUE if the signal was propagated, %FALSE otherwise
741  *
742  * Since: 0.1.0
743  **/
744 gboolean
dnf_state_action_stop(DnfState * state)745 dnf_state_action_stop(DnfState *state)
746 {
747     DnfStatePrivate *priv = GET_PRIVATE(state);
748 
749     /* nothing ever set */
750     if (priv->action == DNF_STATE_ACTION_UNKNOWN) {
751         g_debug("cannot unset action DNF_STATE_ACTION_UNKNOWN");
752         return FALSE;
753     }
754 
755     /* pop and reset */
756     priv->action = priv->last_action;
757     priv->last_action = DNF_STATE_ACTION_UNKNOWN;
758     if (priv->action_hint != NULL) {
759         g_free(priv->action_hint);
760         priv->action_hint = NULL;
761     }
762 
763     /* just emit */
764     g_signal_emit(state, signals [SIGNAL_ACTION_CHANGED], 0, priv->action, NULL);
765     return TRUE;
766 }
767 
768 /**
769  * dnf_state_get_action_hint:
770  * @state: a #DnfState instance.
771  *
772  * Gets the action hint, which may be useful to the users.
773  *
774  * Returns: An a ction hint, e.g. "/var/cache/yum/i386/15/koji/primary.sqlite"
775  *
776  * Since: 0.1.0
777  **/
778 const gchar *
dnf_state_get_action_hint(DnfState * state)779 dnf_state_get_action_hint(DnfState *state)
780 {
781     DnfStatePrivate *priv = GET_PRIVATE(state);
782     return priv->action_hint;
783 }
784 
785 /**
786  * dnf_state_get_action:
787  * @state: a #DnfState instance.
788  *
789  * Gets the last set action value.
790  *
791  * Returns: An action, e.g. %DNF_STATE_ACTION_DECOMPRESSING
792  *
793  * Since: 0.1.0
794  **/
795 DnfStateAction
dnf_state_get_action(DnfState * state)796 dnf_state_get_action(DnfState *state)
797 {
798     DnfStatePrivate *priv = GET_PRIVATE(state);
799     return priv->action;
800 }
801 
802 /**
803  * dnf_state_child_percentage_changed_cb:
804  **/
805 static void
dnf_state_child_percentage_changed_cb(DnfState * child,guint percentage,DnfState * state)806 dnf_state_child_percentage_changed_cb(DnfState *child, guint percentage, DnfState *state)
807 {
808     DnfStatePrivate *priv = GET_PRIVATE(state);
809     gfloat offset;
810     gfloat range;
811     gfloat extra;
812     guint parent_percentage;
813 
814     /* propagate up the stack if DnfState has only one step */
815     if (priv->steps == 1) {
816         dnf_state_set_percentage(state, percentage);
817         return;
818     }
819 
820     /* did we call done on a state that did not have a size set? */
821     if (priv->steps == 0)
822         return;
823 
824     /* already at >= 100% */
825     if (priv->current >= priv->steps) {
826         g_warning("already at %i/%i steps on %p", priv->current, priv->steps, state);
827         return;
828     }
829 
830     /* we have to deal with non-linear steps */
831     if (priv->step_data != NULL) {
832         /* we don't store zero */
833         if (priv->current == 0) {
834             parent_percentage = percentage * priv->step_data[priv->current] / 100;
835         } else {
836             /* bilinearly interpolate for speed */
837             parent_percentage =(((100 - percentage) * priv->step_data[priv->current-1]) +
838                         (percentage * priv->step_data[priv->current])) / 100;
839         }
840         goto out;
841     }
842 
843     /* get the offset */
844     offset = dnf_state_discrete_to_percent(priv->current, priv->steps);
845 
846     /* get the range between the parent step and the next parent step */
847     range = dnf_state_discrete_to_percent(priv->current+1, priv->steps) - offset;
848     if (range < 0.01) {
849         g_warning("range=%f(from %i to %i), should be impossible", range, priv->current+1, priv->steps);
850         return;
851     }
852 
853     /* restore the pre-child action */
854     if (percentage == 100)
855         priv->last_action = priv->child_action;
856 
857     /* get the extra contributed by the child */
858     extra =((gfloat) percentage / 100.0f) * range;
859 
860     /* emit from the parent */
861     parent_percentage =(guint)(offset + extra);
862 out:
863     dnf_state_set_percentage(state, parent_percentage);
864 }
865 
866 /**
867  * dnf_state_child_allow_cancel_changed_cb:
868  **/
869 static void
dnf_state_child_allow_cancel_changed_cb(DnfState * child,gboolean allow_cancel,DnfState * state)870 dnf_state_child_allow_cancel_changed_cb(DnfState *child, gboolean allow_cancel, DnfState *state)
871 {
872     DnfStatePrivate *priv = GET_PRIVATE(state);
873 
874     /* save */
875     priv->allow_cancel_child = allow_cancel;
876 
877     /* just emit if both this and child is okay */
878     g_signal_emit(state, signals [SIGNAL_ALLOW_CANCEL_CHANGED], 0,
879                   priv->allow_cancel && priv->allow_cancel_child);
880 }
881 
882 /**
883  * dnf_state_child_action_changed_cb:
884  **/
885 static void
dnf_state_child_action_changed_cb(DnfState * child,DnfStateAction action,const gchar * action_hint,DnfState * state)886 dnf_state_child_action_changed_cb(DnfState *child,
887                    DnfStateAction action,
888                    const gchar *action_hint,
889                    DnfState *state)
890 {
891     DnfStatePrivate *priv = GET_PRIVATE(state);
892     /* save */
893     priv->action = action;
894 
895     /* just emit */
896     g_signal_emit(state, signals [SIGNAL_ACTION_CHANGED], 0, action, action_hint);
897 }
898 
899 /**
900  * dnf_state_child_package_progress_changed_cb:
901  **/
902 static void
dnf_state_child_package_progress_changed_cb(DnfState * child,const gchar * dnf_package_get_id,DnfStateAction action,guint progress,DnfState * state)903 dnf_state_child_package_progress_changed_cb(DnfState *child,
904                          const gchar *dnf_package_get_id,
905                          DnfStateAction action,
906                          guint progress,
907                          DnfState *state)
908 {
909     /* just emit */
910     g_signal_emit(state, signals [SIGNAL_PACKAGE_PROGRESS_CHANGED], 0,
911                   dnf_package_get_id, action, progress);
912 }
913 
914 /**
915  * dnf_state_reset:
916  * @state: a #DnfState instance.
917  *
918  * Resets the #DnfState object to unset
919  *
920  * Returns: %TRUE for success, %FALSE otherwise
921  *
922  * Since: 0.1.0
923  **/
924 gboolean
dnf_state_reset(DnfState * state)925 dnf_state_reset(DnfState *state)
926 {
927     DnfStatePrivate *priv = GET_PRIVATE(state);
928 
929     g_return_val_if_fail(DNF_IS_STATE(state), FALSE);
930 
931     /* do we care */
932     if (!priv->report_progress)
933         return TRUE;
934 
935     /* reset values */
936     priv->steps = 0;
937     priv->current = 0;
938     priv->last_percentage = 0;
939 
940     /* only use the timer if profiling; it's expensive */
941     if (priv->enable_profile)
942         g_timer_start(priv->timer);
943 
944     /* disconnect client */
945     if (priv->percentage_child_id != 0) {
946         g_signal_handler_disconnect(priv->child, priv->percentage_child_id);
947         priv->percentage_child_id = 0;
948     }
949     if (priv->allow_cancel_child_id != 0) {
950         g_signal_handler_disconnect(priv->child, priv->allow_cancel_child_id);
951         priv->allow_cancel_child_id = 0;
952     }
953     if (priv->action_child_id != 0) {
954         g_signal_handler_disconnect(priv->child, priv->action_child_id);
955         priv->action_child_id = 0;
956     }
957     if (priv->package_progress_child_id != 0) {
958         g_signal_handler_disconnect(priv->child, priv->package_progress_child_id);
959         priv->package_progress_child_id = 0;
960     }
961     if (priv->notify_speed_child_id != 0) {
962         g_signal_handler_disconnect(priv->child, priv->notify_speed_child_id);
963         priv->notify_speed_child_id = 0;
964     }
965 
966     /* unref child */
967     if (priv->child != NULL) {
968         g_object_unref(priv->child);
969         priv->child = NULL;
970     }
971 
972     /* no more locks */
973     dnf_state_release_locks(state);
974 
975     /* no more step data */
976     g_free(priv->step_data);
977     g_free(priv->step_profile);
978     priv->step_data = NULL;
979     priv->step_profile = NULL;
980     return TRUE;
981 }
982 
983 /**
984  * dnf_state_child_notify_speed_cb:
985  **/
986 static void
dnf_state_child_notify_speed_cb(DnfState * child,GParamSpec * pspec,DnfState * state)987 dnf_state_child_notify_speed_cb(DnfState *child,
988                  GParamSpec *pspec,
989                  DnfState *state)
990 {
991     dnf_state_set_speed_internal(state,
992                       dnf_state_get_speed(child));
993 }
994 
995 /**
996  * dnf_state_get_child:
997  * @state: a #DnfState instance.
998  *
999  * Monitor a child state and proxy back up to the parent state.
1000  * You should not g_object_unref() this object, it is owned by the parent.
1001  *
1002  * Returns:(transfer none): A new %DnfState or %NULL for failure
1003  *
1004  * Since: 0.1.0
1005  **/
1006 DnfState *
dnf_state_get_child(DnfState * state)1007 dnf_state_get_child(DnfState *state)
1008 {
1009     DnfState *child = NULL;
1010     DnfStatePrivate *child_priv;
1011     DnfStatePrivate *priv = GET_PRIVATE(state);
1012 
1013     g_return_val_if_fail(DNF_IS_STATE(state), NULL);
1014 
1015     /* do we care */
1016     if (!priv->report_progress)
1017         return state;
1018 
1019     /* already set child */
1020     if (priv->child != NULL) {
1021         g_signal_handler_disconnect(priv->child,
1022                                     priv->percentage_child_id);
1023         g_signal_handler_disconnect(priv->child,
1024                                     priv->allow_cancel_child_id);
1025         g_signal_handler_disconnect(priv->child,
1026                                     priv->action_child_id);
1027         g_signal_handler_disconnect(priv->child,
1028                                     priv->package_progress_child_id);
1029         g_signal_handler_disconnect(priv->child,
1030                                     priv->notify_speed_child_id);
1031         g_object_unref(priv->child);
1032     }
1033 
1034     /* connect up signals */
1035     child = dnf_state_new();
1036     child_priv = GET_PRIVATE(child);
1037     child_priv->parent = state; /* do not ref! */
1038     priv->child = child;
1039     priv->percentage_child_id =
1040         g_signal_connect(child, "percentage-changed",
1041                   G_CALLBACK(dnf_state_child_percentage_changed_cb),
1042                   state);
1043     priv->allow_cancel_child_id =
1044         g_signal_connect(child, "allow-cancel-changed",
1045                   G_CALLBACK(dnf_state_child_allow_cancel_changed_cb),
1046                   state);
1047     priv->action_child_id =
1048         g_signal_connect(child, "action-changed",
1049                   G_CALLBACK(dnf_state_child_action_changed_cb),
1050                   state);
1051     priv->package_progress_child_id =
1052         g_signal_connect(child, "package-progress-changed",
1053                   G_CALLBACK(dnf_state_child_package_progress_changed_cb),
1054                   state);
1055     priv->notify_speed_child_id =
1056         g_signal_connect(child, "notify::speed",
1057                   G_CALLBACK(dnf_state_child_notify_speed_cb),
1058                   state);
1059 
1060     /* reset child */
1061     child_priv->current = 0;
1062     child_priv->last_percentage = 0;
1063 
1064     /* save so we can recover after child has done */
1065     child_priv->action = priv->action;
1066     priv->child_action = priv->action;
1067 
1068     /* set cancellable, creating if required */
1069     if (priv->cancellable == NULL)
1070         priv->cancellable = g_cancellable_new();
1071     dnf_state_set_cancellable(child, priv->cancellable);
1072 
1073     /* set the profile state */
1074     dnf_state_set_enable_profile(child, priv->enable_profile);
1075     return child;
1076 }
1077 
1078 /**
1079  * dnf_state_set_number_steps_real:
1080  * @state: a #DnfState instance.
1081  * @steps: The number of sub-tasks in this transaction, can be 0
1082  * @strloc: Code position identifier.
1083  *
1084  * Sets the number of sub-tasks, i.e. how many times the dnf_state_done()
1085  * function will be called in the loop.
1086  *
1087  * The function will immediately return with TRUE when the number of steps is 0
1088  * or if dnf_state_set_report_progress(FALSE) was previously called.
1089  *
1090  * Returns: %TRUE for success, %FALSE otherwise
1091  *
1092  * Since: 0.1.0
1093  **/
1094 gboolean
dnf_state_set_number_steps_real(DnfState * state,guint steps,const gchar * strloc)1095 dnf_state_set_number_steps_real(DnfState *state, guint steps, const gchar *strloc)
1096 {
1097     DnfStatePrivate *priv = GET_PRIVATE(state);
1098 
1099     g_return_val_if_fail(state != NULL, FALSE);
1100 
1101     /* nothing to do for 0 steps */
1102     if (steps == 0)
1103         return TRUE;
1104 
1105     /* do we care */
1106     if (!priv->report_progress)
1107         return TRUE;
1108 
1109     /* did we call done on a state that did not have a size set? */
1110     if (priv->steps != 0) {
1111         g_warning("steps already set to %i, can't set %i! [%s]",
1112                   priv->steps, steps, strloc);
1113         dnf_state_print_parent_chain(state, 0);
1114         return FALSE;
1115     }
1116 
1117     /* set id */
1118     g_free(priv->id);
1119     priv->id = g_strdup_printf("%s", strloc);
1120 
1121     /* only use the timer if profiling; it's expensive */
1122     if (priv->enable_profile)
1123         g_timer_start(priv->timer);
1124 
1125     /* set steps */
1126     priv->steps = steps;
1127 
1128     /* success */
1129     return TRUE;
1130 }
1131 
1132 /**
1133  * dnf_state_set_steps_real:
1134  * @state: a #DnfState instance.
1135  * @error: A #GError, or %NULL
1136  * @strloc: the code location
1137  * @value: A step weighting variable argument array
1138  * @...: -1 terminated step values
1139  *
1140  * This sets the step weighting, which you will want to do if one action
1141  * will take a bigger chunk of time than another.
1142  *
1143  * All the values must add up to 100, and the list must end with -1.
1144  * Do not use this function directly, instead use the dnf_state_set_steps() macro.
1145  *
1146  * Returns: %TRUE for success, %FALSE otherwise
1147  *
1148  * Since: 0.1.0
1149  **/
1150 gboolean
dnf_state_set_steps_real(DnfState * state,GError ** error,const gchar * strloc,gint value,...)1151 dnf_state_set_steps_real(DnfState *state, GError **error, const gchar *strloc, gint value, ...) try
1152 {
1153     DnfStatePrivate *priv = GET_PRIVATE(state);
1154     va_list args;
1155     guint i;
1156     gint value_temp;
1157     guint total;
1158 
1159     g_return_val_if_fail(state != NULL, FALSE);
1160     g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1161 
1162     /* do we care */
1163     if (!priv->report_progress)
1164         return TRUE;
1165 
1166     /* we must set at least one thing */
1167     total = value;
1168 
1169     /* process the valist */
1170     va_start(args, value);
1171     for (i = 0;; i++) {
1172         value_temp = va_arg(args, gint);
1173         if (value_temp == -1)
1174             break;
1175         total +=(guint) value_temp;
1176     }
1177     va_end(args);
1178 
1179     /* does not sum to 100% */
1180     if (total != 100) {
1181         g_set_error(error,
1182                     DNF_ERROR,
1183                     DNF_ERROR_INTERNAL_ERROR,
1184                     _("percentage not 100: %i"),
1185                     total);
1186         return FALSE;
1187     }
1188 
1189     /* set step number */
1190     if (!dnf_state_set_number_steps_real(state, i+1, strloc)) {
1191         g_set_error(error,
1192                     DNF_ERROR,
1193                     DNF_ERROR_INTERNAL_ERROR,
1194                     _("failed to set number steps: %i"),
1195                     i+1);
1196         return FALSE;
1197     }
1198 
1199     /* save this data */
1200     total = value;
1201     priv->step_data = g_new0(guint, i+2);
1202     priv->step_profile = g_new0(gdouble, i+2);
1203     priv->step_data[0] = total;
1204     va_start(args, value);
1205     for (i = 0;; i++) {
1206         value_temp = va_arg(args, gint);
1207         if (value_temp == -1)
1208             break;
1209 
1210         /* we pre-add the data to make access simpler */
1211         total +=(guint) value_temp;
1212         priv->step_data[i+1] = total;
1213     }
1214     va_end(args);
1215 
1216     /* success */
1217     return TRUE;
1218 } CATCH_TO_GERROR(FALSE)
1219 
1220 /**
1221  * dnf_state_show_profile:
1222  **/
1223 static void
1224 dnf_state_show_profile(DnfState *state)
1225 {
1226     DnfStatePrivate *priv = GET_PRIVATE(state);
1227     gdouble division;
1228     gdouble total_time = 0.0f;
1229     GString *result;
1230     guint i;
1231     guint uncumalitive = 0;
1232 
1233     /* get the total time */
1234     for (i = 0; i < priv->steps; i++)
1235         total_time += priv->step_profile[i];
1236     if (total_time < 0.01)
1237         return;
1238 
1239     /* get the total time so we can work out the divisor */
1240     result = g_string_new("Raw timing data was { ");
1241     for (i = 0; i < priv->steps; i++) {
1242         g_string_append_printf(result, "%.3f, ",
1243                                priv->step_profile[i]);
1244     }
1245     if (priv->steps > 0)
1246         g_string_set_size(result, result->len - 2);
1247     g_string_append(result, " }\n");
1248 
1249     /* what we set */
1250     g_string_append(result, "steps were set as [ ");
1251     for (i = 0; i < priv->steps; i++) {
1252         g_string_append_printf(result, "%i, ",
1253                                priv->step_data[i] - uncumalitive);
1254         uncumalitive = priv->step_data[i];
1255     }
1256 
1257     /* what we _should_ have set */
1258     g_string_append_printf(result, "-1 ] but should have been: [ ");
1259     division = total_time / 100.0f;
1260     for (i = 0; i < priv->steps; i++) {
1261         g_string_append_printf(result, "%.0f, ",
1262                                priv->step_profile[i] / division);
1263     }
1264     g_string_append(result, "-1 ]");
1265     g_printerr("\n\n%s at %s\n\n", result->str, priv->id);
1266     g_string_free(result, TRUE);
1267 }
1268 
1269 /**
1270  * dnf_state_check:
1271  * @state: a #DnfState instance.
1272  * @error: A #GError or %NULL
1273  *
1274  * Do any checks to see if the task has been cancelled.
1275  *
1276  * Returns: %TRUE for success, %FALSE otherwise
1277  *
1278  * Since: 0.1.0
1279  **/
1280 gboolean
dnf_state_check(DnfState * state,GError ** error)1281 dnf_state_check(DnfState *state, GError **error) try
1282 {
1283     DnfStatePrivate *priv = GET_PRIVATE(state);
1284 
1285     g_return_val_if_fail(state != NULL, FALSE);
1286     g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1287 
1288     /* are we cancelled */
1289     if (g_cancellable_is_cancelled(priv->cancellable)) {
1290         g_set_error_literal(error,
1291                             DNF_ERROR,
1292                             DNF_ERROR_CANCELLED,
1293                             _("cancelled by user action"));
1294         return FALSE;
1295     }
1296     return TRUE;
1297 } CATCH_TO_GERROR(FALSE)
1298 
1299 /**
1300  * dnf_state_done_real:
1301  * @state: a #DnfState instance.
1302  * @error: A #GError or %NULL
1303  * @strloc: Code position identifier.
1304  *
1305  * Called when the current sub-task has finished.
1306  *
1307  * Returns: %TRUE for success, %FALSE otherwise
1308  *
1309  * Since: 0.1.0
1310  **/
1311 gboolean
1312 dnf_state_done_real(DnfState *state, GError **error, const gchar *strloc) try
1313 {
1314     DnfStatePrivate *priv = GET_PRIVATE(state);
1315     gdouble elapsed;
1316     gfloat percentage;
1317 
1318     g_return_val_if_fail(state != NULL, FALSE);
1319     g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1320 
1321     /* check */
1322     if (!dnf_state_check(state, error))
1323         return FALSE;
1324 
1325     /* do we care */
1326     if (!priv->report_progress)
1327         return TRUE;
1328 
1329     /* did we call done on a state that did not have a size set? */
1330     if (priv->steps == 0) {
1331         g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR,
1332                     _("done on a state %1$p that did not have a size set! [%2$s]"),
1333                     state, strloc);
1334         dnf_state_print_parent_chain(state, 0);
1335         return FALSE;
1336     }
1337 
1338     /* check the interval was too big in allow_cancel false mode */
1339     if (priv->enable_profile) {
1340         elapsed = g_timer_elapsed(priv->timer, NULL);
1341         if (!priv->allow_cancel_changed_state && priv->current > 0) {
1342             if (elapsed > 0.1f) {
1343                 g_warning("%.1fms between dnf_state_done() and no dnf_state_set_allow_cancel()", elapsed * 1000);
1344                 dnf_state_print_parent_chain(state, 0);
1345             }
1346         }
1347 
1348         /* save the duration in the array */
1349         if (priv->step_profile != NULL)
1350             priv->step_profile[priv->current] = elapsed;
1351         g_timer_start(priv->timer);
1352     }
1353 
1354     /* is already at 100%? */
1355     if (priv->current >= priv->steps) {
1356         g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR,
1357                     _("already at 100%% state [%s]"), strloc);
1358         dnf_state_print_parent_chain(state, 0);
1359         return FALSE;
1360     }
1361 
1362     /* is child not at 100%? */
1363     if (priv->child != NULL) {
1364         DnfStatePrivate *child_priv = GET_PRIVATE(priv->child);
1365         if (child_priv->current != child_priv->steps) {
1366             g_print("child is at %i/%i steps and parent done [%s]\n",
1367                     child_priv->current, child_priv->steps, strloc);
1368             dnf_state_print_parent_chain(priv->child, 0);
1369             /* do not abort, as we want to clean this up */
1370         }
1371     }
1372 
1373     /* we just checked for cancel, so it's not true to say we're blocking */
1374     dnf_state_set_allow_cancel(state, TRUE);
1375 
1376     /* another */
1377     priv->current++;
1378 
1379     /* find new percentage */
1380     if (priv->step_data == NULL) {
1381         percentage = dnf_state_discrete_to_percent(priv->current,
1382                                                    priv->steps);
1383     } else {
1384         /* this is cumalative, for speedy access */
1385         percentage = priv->step_data[priv->current - 1];
1386     }
1387     dnf_state_set_percentage(state,(guint) percentage);
1388 
1389     /* show any profiling stats */
1390     if (priv->enable_profile &&
1391         priv->current == priv->steps &&
1392         priv->step_profile != NULL) {
1393         dnf_state_show_profile(state);
1394     }
1395 
1396     /* reset child if it exists */
1397     if (priv->child != NULL)
1398         dnf_state_reset(priv->child);
1399     return TRUE;
CATCH_TO_GERROR(FALSE)1400 } CATCH_TO_GERROR(FALSE)
1401 
1402 /**
1403  * dnf_state_finished_real:
1404  * @state: A #DnfState
1405  * @error: A #GError or %NULL
1406  * @strloc: Code position identifier.
1407  *
1408  * Called when the current sub-task has finished.
1409  *
1410  * Returns: %TRUE for success, %FALSE otherwise
1411  *
1412  * Since: 0.1.0
1413  **/
1414 gboolean
1415 dnf_state_finished_real(DnfState *state, GError **error, const gchar *strloc) try
1416 {
1417     DnfStatePrivate *priv = GET_PRIVATE(state);
1418 
1419     g_return_val_if_fail(state != NULL, FALSE);
1420     g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1421 
1422     /* check */
1423     if (!dnf_state_check(state, error))
1424         return FALSE;
1425 
1426     /* is already at 100%? */
1427     if (priv->current == priv->steps)
1428         return TRUE;
1429 
1430     /* all done */
1431     priv->current = priv->steps;
1432 
1433     /* set new percentage */
1434     dnf_state_set_percentage(state, 100);
1435     return TRUE;
1436 } CATCH_TO_GERROR(FALSE)
1437 
1438 /**
1439  * dnf_state_new:
1440  *
1441  * Creates a new #DnfState.
1442  *
1443  * Returns:(transfer full): a #DnfState
1444  *
1445  * Since: 0.1.0
1446  **/
1447 DnfState *
1448 dnf_state_new(void)
1449 {
1450     return DNF_STATE(g_object_new(DNF_TYPE_STATE, NULL));
1451 }
1452