1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2009-2012 Richard Hughes <richard@hughsie.com>
4 *
5 * Licensed under the GNU General Public License Version 2
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #include <glib.h>
27 #include <signal.h>
28 #include <gio/gio.h>
29
30 #include "cd-state.h"
31
32 #define CD_STATE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CD_TYPE_STATE, CdStatePrivate))
33
34 struct _CdStatePrivate
35 {
36 gboolean enable_profile;
37 gboolean process_event_sources;
38 gchar *id;
39 gdouble global_share;
40 gdouble *step_profile;
41 GTimer *timer;
42 guint current;
43 guint last_percentage;
44 guint *step_data;
45 guint steps;
46 gulong percentage_child_id;
47 gulong subpercentage_child_id;
48 CdState *child;
49 CdState *parent;
50 };
51
52 enum {
53 SIGNAL_PERCENTAGE_CHANGED,
54 SIGNAL_SUBPERCENTAGE_CHANGED,
55 SIGNAL_LAST
56 };
57
58 enum {
59 PROP_0,
60 PROP_SPEED,
61 PROP_LAST
62 };
63
64 static guint signals [SIGNAL_LAST] = { 0 };
65
G_DEFINE_TYPE(CdState,cd_state,G_TYPE_OBJECT)66 G_DEFINE_TYPE (CdState, cd_state, G_TYPE_OBJECT)
67
68 #define CD_STATE_SPEED_SMOOTHING_ITEMS 5
69
70 /**
71 * cd_state_error_quark:
72 **/
73 GQuark
74 cd_state_error_quark (void)
75 {
76 static GQuark quark = 0;
77 if (!quark)
78 quark = g_quark_from_static_string ("cd_state_error");
79 return quark;
80 }
81
82 /**
83 * cd_state_set_enable_profile:
84 **/
85 void
cd_state_set_enable_profile(CdState * state,gboolean enable_profile)86 cd_state_set_enable_profile (CdState *state, gboolean enable_profile)
87 {
88 g_return_if_fail (CD_IS_STATE (state));
89 state->priv->enable_profile = enable_profile;
90 }
91
92 /**
93 * cd_state_discrete_to_percent:
94 **/
95 static gfloat
cd_state_discrete_to_percent(guint discrete,guint steps)96 cd_state_discrete_to_percent (guint discrete, guint steps)
97 {
98 /* check we are in range */
99 if (discrete > steps)
100 return 100;
101 if (steps == 0) {
102 g_warning ("steps is 0!");
103 return 0;
104 }
105 return ((gfloat) discrete * (100.0f / (gfloat) (steps)));
106 }
107
108 /**
109 * cd_state_print_parent_chain:
110 **/
111 static void
cd_state_print_parent_chain(CdState * state,guint level)112 cd_state_print_parent_chain (CdState *state, guint level)
113 {
114 if (state->priv->parent != NULL)
115 cd_state_print_parent_chain (state->priv->parent, level + 1);
116 g_print ("%u) %s (%u/%u)\n",
117 level, state->priv->id, state->priv->current, state->priv->steps);
118 }
119
120 /**
121 * cd_state_set_percentage:
122 **/
123 gboolean
cd_state_set_percentage(CdState * state,guint percentage)124 cd_state_set_percentage (CdState *state, guint percentage)
125 {
126 /* is it the same */
127 if (percentage == state->priv->last_percentage)
128 return FALSE;
129
130 /* is it invalid */
131 if (percentage > 100) {
132 cd_state_print_parent_chain (state, 0);
133 g_warning ("percentage %u%% is invalid on %p!",
134 percentage, state);
135 return FALSE;
136 }
137
138 /* is it less */
139 if (percentage < state->priv->last_percentage) {
140 if (state->priv->enable_profile) {
141 cd_state_print_parent_chain (state, 0);
142 g_critical ("percentage should not go down from %u to %u on %p!",
143 state->priv->last_percentage, percentage, state);
144 }
145 return FALSE;
146 }
147
148 /* save */
149 state->priv->last_percentage = percentage;
150
151 /* are we so low we don't care */
152 if (state->priv->global_share < 0.001)
153 return FALSE;
154
155 /* emit */
156 g_signal_emit (state, signals [SIGNAL_PERCENTAGE_CHANGED], 0, percentage);
157 return TRUE;
158 }
159
160 /**
161 * cd_state_get_percentage:
162 **/
163 guint
cd_state_get_percentage(CdState * state)164 cd_state_get_percentage (CdState *state)
165 {
166 return state->priv->last_percentage;
167 }
168
169 /**
170 * cd_state_set_subpercentage:
171 **/
172 static gboolean
cd_state_set_subpercentage(CdState * state,guint percentage)173 cd_state_set_subpercentage (CdState *state, guint percentage)
174 {
175 /* are we so low we don't care */
176 if (state->priv->global_share < 0.01)
177 return TRUE;
178
179 /* just emit */
180 g_signal_emit (state, signals [SIGNAL_SUBPERCENTAGE_CHANGED], 0, percentage);
181 return TRUE;
182 }
183
184 /**
185 * cd_state_child_percentage_changed_cb:
186 **/
187 static void
cd_state_child_percentage_changed_cb(CdState * child,guint percentage,CdState * state)188 cd_state_child_percentage_changed_cb (CdState *child, guint percentage, CdState *state)
189 {
190 gfloat offset;
191 gfloat range;
192 gfloat extra;
193 guint parent_percentage;
194
195 /* propagate up the stack if CdState has only one step */
196 if (state->priv->steps == 1) {
197 cd_state_set_percentage (state, percentage);
198 return;
199 }
200
201 /* did we call done on a state that did not have a size set? */
202 if (state->priv->steps == 0)
203 return;
204
205 /* always provide two levels of signals */
206 cd_state_set_subpercentage (state, percentage);
207
208 /* already at >= 100% */
209 if (state->priv->current >= state->priv->steps) {
210 g_warning ("already at %u/%u steps on %p", state->priv->current, state->priv->steps, state);
211 return;
212 }
213
214 /* we have to deal with non-linear steps */
215 if (state->priv->step_data != NULL) {
216 /* we don't store zero */
217 if (state->priv->current == 0) {
218 parent_percentage = percentage * state->priv->step_data[state->priv->current] / 100;
219 } else {
220 /* bilinearly interpolate for XXXXXXXXXXXXXXXXXXXX */
221 parent_percentage = (((100 - percentage) * state->priv->step_data[state->priv->current-1]) +
222 (percentage * state->priv->step_data[state->priv->current])) / 100;
223 }
224 goto out;
225 }
226
227 /* get the offset */
228 offset = cd_state_discrete_to_percent (state->priv->current, state->priv->steps);
229
230 /* get the range between the parent step and the next parent step */
231 range = cd_state_discrete_to_percent (state->priv->current+1, state->priv->steps) - offset;
232 if (range < 0.01) {
233 g_warning ("range=%f (from %u to %u), should be impossible", range, state->priv->current+1, state->priv->steps);
234 return;
235 }
236
237 /* get the extra contributed by the child */
238 extra = ((gfloat) percentage / 100.0f) * range;
239
240 /* emit from the parent */
241 parent_percentage = (guint) (offset + extra);
242 out:
243 cd_state_set_percentage (state, parent_percentage);
244 }
245
246 /**
247 * cd_state_child_subpercentage_changed_cb:
248 **/
249 static void
cd_state_child_subpercentage_changed_cb(CdState * child,guint percentage,CdState * state)250 cd_state_child_subpercentage_changed_cb (CdState *child, guint percentage, CdState *state)
251 {
252 /* discard this, unless the CdState has only one step */
253 if (state->priv->steps != 1)
254 return;
255
256 /* propagate up the stack as if the parent didn't exist */
257 cd_state_set_subpercentage (state, percentage);
258 }
259
260 /**
261 * cd_state_reset:
262 **/
263 gboolean
cd_state_reset(CdState * state)264 cd_state_reset (CdState *state)
265 {
266 g_return_val_if_fail (CD_IS_STATE (state), FALSE);
267
268 /* reset values */
269 state->priv->steps = 0;
270 state->priv->current = 0;
271 state->priv->last_percentage = 0;
272
273 /* only use the timer if profiling; it's expensive */
274 if (state->priv->enable_profile)
275 g_timer_start (state->priv->timer);
276
277 /* disconnect client */
278 if (state->priv->percentage_child_id != 0) {
279 g_signal_handler_disconnect (state->priv->child,
280 state->priv->percentage_child_id);
281 state->priv->percentage_child_id = 0;
282 }
283 if (state->priv->subpercentage_child_id != 0) {
284 g_signal_handler_disconnect (state->priv->child,
285 state->priv->subpercentage_child_id);
286 state->priv->subpercentage_child_id = 0;
287 }
288
289 /* unref child */
290 if (state->priv->child != NULL) {
291 g_object_unref (state->priv->child);
292 state->priv->child = NULL;
293 }
294
295 /* no more step data */
296 g_free (state->priv->step_data);
297 g_free (state->priv->step_profile);
298 state->priv->step_data = NULL;
299 state->priv->step_profile = NULL;
300 return TRUE;
301 }
302
303 /**
304 * cd_state_set_global_share:
305 **/
306 static void
cd_state_set_global_share(CdState * state,gdouble global_share)307 cd_state_set_global_share (CdState *state, gdouble global_share)
308 {
309 state->priv->global_share = global_share;
310 }
311
312 /**
313 * cd_state_get_child:
314 **/
315 CdState *
cd_state_get_child(CdState * state)316 cd_state_get_child (CdState *state)
317 {
318 CdState *child = NULL;
319
320 g_return_val_if_fail (CD_IS_STATE (state), NULL);
321
322 /* already set child */
323 if (state->priv->child != NULL) {
324 g_signal_handler_disconnect (state->priv->child,
325 state->priv->percentage_child_id);
326 g_signal_handler_disconnect (state->priv->child,
327 state->priv->subpercentage_child_id);
328 g_object_unref (state->priv->child);
329 }
330
331 /* connect up signals */
332 child = cd_state_new ();
333 child->priv->parent = state; /* do not ref! */
334 state->priv->child = child;
335 state->priv->percentage_child_id =
336 g_signal_connect (child, "percentage-changed",
337 G_CALLBACK (cd_state_child_percentage_changed_cb),
338 state);
339 state->priv->subpercentage_child_id =
340 g_signal_connect (child, "subpercentage-changed",
341 G_CALLBACK (cd_state_child_subpercentage_changed_cb),
342 state);
343 /* reset child */
344 child->priv->current = 0;
345 child->priv->last_percentage = 0;
346
347 /* set the global share on the new child */
348 cd_state_set_global_share (child, state->priv->global_share);
349
350 /* set the profile state */
351 cd_state_set_enable_profile (child,
352 state->priv->enable_profile);
353
354 return child;
355 }
356
357 /**
358 * cd_state_set_number_steps_real:
359 **/
360 gboolean
cd_state_set_number_steps_real(CdState * state,guint steps,const gchar * strloc)361 cd_state_set_number_steps_real (CdState *state, guint steps, const gchar *strloc)
362 {
363 g_return_val_if_fail (state != NULL, FALSE);
364
365 /* nothing to do for 0 steps */
366 if (steps == 0)
367 return TRUE;
368
369 /* did we call done on a state that did not have a size set? */
370 if (state->priv->steps != 0) {
371 g_warning ("steps already set to %u, can't set %u! [%s]",
372 state->priv->steps, steps, strloc);
373 cd_state_print_parent_chain (state, 0);
374 return FALSE;
375 }
376
377 /* set id */
378 g_free (state->priv->id);
379 state->priv->id = g_strdup_printf ("%s", strloc);
380
381 /* only use the timer if profiling; it's expensive */
382 if (state->priv->enable_profile)
383 g_timer_start (state->priv->timer);
384
385 /* imply reset */
386 cd_state_reset (state);
387
388 /* set steps */
389 state->priv->steps = steps;
390
391 /* global share just got smaller */
392 state->priv->global_share /= steps;
393 return TRUE;
394 }
395
396 /**
397 * cd_state_set_steps_real:
398 **/
399 gboolean
cd_state_set_steps_real(CdState * state,GError ** error,const gchar * strloc,gint value,...)400 cd_state_set_steps_real (CdState *state, GError **error, const gchar *strloc, gint value, ...)
401 {
402 va_list args;
403 guint i;
404 gint value_temp;
405 guint total;
406
407 g_return_val_if_fail (state != NULL, FALSE);
408 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
409
410 /* we must set at least one thing */
411 total = value;
412
413 /* process the valist */
414 va_start (args, value);
415 for (i = 0;; i++) {
416 value_temp = va_arg (args, gint);
417 if (value_temp == -1)
418 break;
419 total += (guint) value_temp;
420 }
421 va_end (args);
422
423 /* does not sum to 100% */
424 if (total != 100) {
425 g_set_error (error,
426 CD_STATE_ERROR,
427 CD_STATE_ERROR_INVALID,
428 "percentage not 100: %u",
429 total);
430 return FALSE;
431 }
432
433 /* set step number */
434 if (!cd_state_set_number_steps_real (state, i+1, strloc)) {
435 g_set_error (error,
436 CD_STATE_ERROR,
437 CD_STATE_ERROR_INVALID,
438 "failed to set number steps: %u",
439 i+1);
440 return FALSE;
441 }
442
443 /* save this data */
444 total = value;
445 state->priv->step_data = g_new0 (guint, i+2);
446 state->priv->step_profile = g_new0 (gdouble, i+2);
447 state->priv->step_data[0] = total;
448 va_start (args, value);
449 for (i = 0;; i++) {
450 value_temp = va_arg (args, gint);
451 if (value_temp == -1)
452 break;
453
454 /* we pre-add the data to make access simpler */
455 total += (guint) value_temp;
456 state->priv->step_data[i+1] = total;
457 }
458 va_end (args);
459 return TRUE;
460 }
461
462 /**
463 * cd_state_show_profile:
464 **/
465 static void
cd_state_show_profile(CdState * state)466 cd_state_show_profile (CdState *state)
467 {
468 gdouble division;
469 gdouble total_time = 0.0f;
470 guint i;
471 guint uncumalitive = 0;
472 g_autoptr(GString) result = NULL;
473
474 /* get the total time so we can work out the divisor */
475 for (i = 0; i < state->priv->steps; i++)
476 total_time += state->priv->step_profile[i];
477 division = total_time / 100.0f;
478
479 /* what we set */
480 result = g_string_new ("steps were set as [ ");
481 for (i = 0; i < state->priv->steps; i++) {
482 g_string_append_printf (result, "%u, ",
483 state->priv->step_data[i] - uncumalitive);
484 uncumalitive = state->priv->step_data[i];
485 }
486
487 /* what we _should_ have set */
488 g_string_append_printf (result, "-1 ] but should have been: [ ");
489 for (i = 0; i < state->priv->steps; i++) {
490 g_string_append_printf (result, "%.0f, ",
491 state->priv->step_profile[i] / division);
492 }
493 g_printerr ("\n\n%s-1 ] at %s\n\n", result->str, state->priv->id);
494 }
495
496 /**
497 * cd_state_done_real:
498 **/
499 gboolean
cd_state_done_real(CdState * state,GError ** error,const gchar * strloc)500 cd_state_done_real (CdState *state, GError **error, const gchar *strloc)
501 {
502 gdouble elapsed;
503 gfloat percentage;
504
505 g_return_val_if_fail (state != NULL, FALSE);
506 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
507
508 /* did we call done on a state that did not have a size set? */
509 if (state->priv->steps == 0) {
510 g_set_error (error, CD_STATE_ERROR, CD_STATE_ERROR_INVALID,
511 "done on a state %p that did not have a size set! [%s]",
512 state, strloc);
513 cd_state_print_parent_chain (state, 0);
514 return FALSE;
515 }
516
517 /* save the step interval for profiling */
518 if (state->priv->enable_profile) {
519 /* save the duration in the array */
520 elapsed = g_timer_elapsed (state->priv->timer, NULL);
521 if (state->priv->step_profile != NULL)
522 state->priv->step_profile[state->priv->current] = elapsed;
523 g_timer_start (state->priv->timer);
524 }
525
526 /* is already at 100%? */
527 if (state->priv->current >= state->priv->steps) {
528 g_set_error (error, CD_STATE_ERROR, CD_STATE_ERROR_INVALID,
529 "already at 100%% state [%s]", strloc);
530 cd_state_print_parent_chain (state, 0);
531 return FALSE;
532 }
533
534 /* is child not at 100%? */
535 if (state->priv->child != NULL) {
536 CdStatePrivate *child_priv = state->priv->child->priv;
537 if (child_priv->current != child_priv->steps) {
538 g_print ("child is at %u/%u steps and parent done [%s]\n",
539 child_priv->current, child_priv->steps, strloc);
540 cd_state_print_parent_chain (state->priv->child, 0);
541 /* do not abort, as we want to clean this up */
542 return TRUE;
543 }
544 }
545
546 /* another */
547 state->priv->current++;
548
549 /* find new percentage */
550 if (state->priv->step_data == NULL) {
551 percentage = cd_state_discrete_to_percent (state->priv->current,
552 state->priv->steps);
553 } else {
554 /* this is cumalative */
555 percentage = state->priv->step_data[state->priv->current - 1];
556 }
557 cd_state_set_percentage (state, (guint) percentage);
558
559 /* show any profiling stats */
560 if (state->priv->enable_profile &&
561 state->priv->current == state->priv->steps &&
562 state->priv->step_profile != NULL) {
563 cd_state_show_profile (state);
564 }
565
566 /* reset child if it exists */
567 if (state->priv->child != NULL)
568 cd_state_reset (state->priv->child);
569 return TRUE;
570 }
571
572 /**
573 * cd_state_finished_real:
574 **/
575 gboolean
cd_state_finished_real(CdState * state,GError ** error,const gchar * strloc)576 cd_state_finished_real (CdState *state, GError **error, const gchar *strloc)
577 {
578 g_return_val_if_fail (state != NULL, FALSE);
579 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
580
581 /* is already at 100%? */
582 if (state->priv->current == state->priv->steps)
583 return TRUE;
584
585 /* all done */
586 state->priv->current = state->priv->steps;
587
588 /* set new percentage */
589 cd_state_set_percentage (state, 100);
590 return TRUE;
591 }
592
593 /**
594 * cd_state_finalize:
595 **/
596 static void
cd_state_finalize(GObject * object)597 cd_state_finalize (GObject *object)
598 {
599 CdState *state;
600
601 g_return_if_fail (object != NULL);
602 g_return_if_fail (CD_IS_STATE (object));
603 state = CD_STATE (object);
604
605 cd_state_reset (state);
606 g_free (state->priv->id);
607 g_free (state->priv->step_data);
608 g_free (state->priv->step_profile);
609 g_timer_destroy (state->priv->timer);
610
611 G_OBJECT_CLASS (cd_state_parent_class)->finalize (object);
612 }
613
614 /**
615 * cd_state_class_init:
616 **/
617 static void
cd_state_class_init(CdStateClass * klass)618 cd_state_class_init (CdStateClass *klass)
619 {
620 GObjectClass *object_class = G_OBJECT_CLASS (klass);
621 object_class->finalize = cd_state_finalize;
622
623 signals [SIGNAL_PERCENTAGE_CHANGED] =
624 g_signal_new ("percentage-changed",
625 G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
626 G_STRUCT_OFFSET (CdStateClass, percentage_changed),
627 NULL, NULL, g_cclosure_marshal_VOID__UINT,
628 G_TYPE_NONE, 1, G_TYPE_UINT);
629
630 signals [SIGNAL_SUBPERCENTAGE_CHANGED] =
631 g_signal_new ("subpercentage-changed",
632 G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
633 G_STRUCT_OFFSET (CdStateClass, subpercentage_changed),
634 NULL, NULL, g_cclosure_marshal_VOID__UINT,
635 G_TYPE_NONE, 1, G_TYPE_UINT);
636
637 g_type_class_add_private (klass, sizeof (CdStatePrivate));
638 }
639
640 /**
641 * cd_state_init:
642 **/
643 static void
cd_state_init(CdState * state)644 cd_state_init (CdState *state)
645 {
646 state->priv = CD_STATE_GET_PRIVATE (state);
647 state->priv->global_share = 1.0f;
648 state->priv->timer = g_timer_new ();
649 }
650
651 /**
652 * cd_state_new:
653 **/
654 CdState *
cd_state_new(void)655 cd_state_new (void)
656 {
657 CdState *state;
658 state = g_object_new (CD_TYPE_STATE, NULL);
659 return CD_STATE (state);
660 }
661