Mercurial > projects > package-update-indicator
comparison pui-backend.c @ 0:6884bb8130ca
Initial revision
author | Guido Berhoerster <guido+pui@berhoerster.name> |
---|---|
date | Sun, 20 May 2018 11:32:57 +0200 |
parents | |
children | 3d72ca76538d |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:6884bb8130ca |
---|---|
1 /* | |
2 * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name> | |
3 * | |
4 * Permission is hereby granted, free of charge, to any person obtaining | |
5 * a copy of this software and associated documentation files (the | |
6 * "Software"), to deal in the Software without restriction, including | |
7 * without limitation the rights to use, copy, modify, merge, publish, | |
8 * distribute, sublicense, and/or sell copies of the Software, and to | |
9 * permit persons to whom the Software is furnished to do so, subject to | |
10 * the following conditions: | |
11 * | |
12 * The above copyright notice and this permission notice shall be included | |
13 * in all copies or substantial portions of the Software. | |
14 * | |
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
22 */ | |
23 | |
24 #include <errno.h> | |
25 #include <fcntl.h> | |
26 #include <packagekit-glib2/packagekit.h> | |
27 #include <string.h> | |
28 #include <sys/stat.h> | |
29 #include <sys/types.h> | |
30 #include <utime.h> | |
31 | |
32 #include "pui-common.h" | |
33 #include "pui-backend.h" | |
34 #include "pui-get-updates.h" | |
35 | |
36 struct _PuiBackend { | |
37 GObject parent_instance; | |
38 PkControl *pk_control; | |
39 GCancellable *cancellable; | |
40 gint64 last_check; | |
41 PkNetworkEnum network_state; | |
42 guint periodic_check_id; | |
43 guint refresh_interval; | |
44 guint important_updates; | |
45 guint normal_updates; | |
46 }; | |
47 | |
48 static void pui_backend_async_initable_iface_init(gpointer, gpointer); | |
49 | |
50 G_DEFINE_TYPE_WITH_CODE(PuiBackend, pui_backend, G_TYPE_OBJECT, | |
51 G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE, | |
52 pui_backend_async_initable_iface_init)) | |
53 | |
54 enum { | |
55 STATE_CHANGED, | |
56 RESTART_REQUIRED, | |
57 SIGNAL_LAST | |
58 }; | |
59 | |
60 enum { | |
61 PROP_0, | |
62 PROP_IMPORTANT_UPDATES, | |
63 PROP_NORMAL_UPDATES, | |
64 PROP_REFRESH_INTERVAL, | |
65 PROP_LAST | |
66 }; | |
67 | |
68 static guint signals[SIGNAL_LAST] = { 0 }; | |
69 static GParamSpec *properties[PROP_LAST] = { NULL }; | |
70 | |
71 static gboolean periodic_check(gpointer); | |
72 | |
73 GQuark | |
74 pui_backend_error_quark(void) | |
75 { | |
76 return (g_quark_from_static_string("pui-backend-error-quark")); | |
77 } | |
78 | |
79 static void | |
80 process_pk_package(gpointer data, gpointer user_data) | |
81 { | |
82 PkPackage *package = data; | |
83 PuiBackend *self = user_data; | |
84 PkInfoEnum type_info = pk_package_get_info(package); | |
85 | |
86 switch (type_info) { | |
87 case PK_INFO_ENUM_LOW: /* FALLTHROUGH */ | |
88 case PK_INFO_ENUM_ENHANCEMENT: /* FALLTHROUGH */ | |
89 case PK_INFO_ENUM_NORMAL: | |
90 self->normal_updates++; | |
91 break; | |
92 case PK_INFO_ENUM_BUGFIX: /* FALLTHROUGH */ | |
93 case PK_INFO_ENUM_IMPORTANT: /* FALLTHROUGH */ | |
94 case PK_INFO_ENUM_SECURITY: | |
95 self->important_updates++; | |
96 break; | |
97 default: | |
98 break; | |
99 } | |
100 } | |
101 | |
102 static void | |
103 on_get_updates_finished(GObject *source_object, GAsyncResult *async_result, | |
104 gpointer user_data) | |
105 { | |
106 PuiBackend *self = user_data; | |
107 GPtrArray *package_list = NULL; | |
108 GError *error = NULL; | |
109 guint prev_normal_updates = self->normal_updates; | |
110 guint prev_important_updates = self->important_updates; | |
111 | |
112 package_list = pui_get_updates_finish(async_result, &error); | |
113 if (package_list == NULL) { | |
114 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || | |
115 g_error_matches(error, PUI_GET_UPDATES_ERROR, | |
116 PUI_GET_UPDATES_ERROR_CANCELLED)) { | |
117 /* cancelled */ | |
118 g_debug("cancelled checking for updates"); | |
119 } else { | |
120 g_warning("failed to check for updates: %s", | |
121 error->message); | |
122 } | |
123 g_error_free(error); | |
124 goto out; | |
125 } | |
126 | |
127 self->normal_updates = 0; | |
128 self->important_updates = 0; | |
129 g_ptr_array_foreach(package_list, process_pk_package, self); | |
130 g_debug("normal updates: %u, important updates: %u", | |
131 self->normal_updates, self->important_updates); | |
132 if (self->normal_updates != prev_normal_updates) { | |
133 g_object_notify_by_pspec(G_OBJECT(self), | |
134 properties[PROP_NORMAL_UPDATES]); | |
135 } | |
136 if (self->important_updates != prev_important_updates) { | |
137 g_object_notify_by_pspec(G_OBJECT(self), | |
138 properties[PROP_IMPORTANT_UPDATES]); | |
139 } | |
140 if ((self->normal_updates != prev_normal_updates) || | |
141 (self->important_updates != prev_important_updates)) { | |
142 g_debug("emitting signal state-changed"); | |
143 g_signal_emit(self, signals[STATE_CHANGED], 0); | |
144 } | |
145 | |
146 /* last successful check */ | |
147 self->last_check = g_get_monotonic_time(); | |
148 | |
149 out: | |
150 /* reschedule periodic check */ | |
151 if (self->network_state != PK_NETWORK_ENUM_OFFLINE) { | |
152 self->periodic_check_id = | |
153 g_timeout_add_seconds(PUI_CHECK_UPDATES_INTERVAL, | |
154 periodic_check, self); | |
155 } | |
156 | |
157 if (package_list != NULL) { | |
158 g_ptr_array_unref(package_list); | |
159 } | |
160 } | |
161 | |
162 static gboolean | |
163 periodic_check(gpointer user_data) | |
164 { | |
165 PuiBackend *self = user_data; | |
166 | |
167 g_debug("running periodic check"); | |
168 | |
169 pui_get_updates_async(self->pk_control, self->refresh_interval, | |
170 self->cancellable, on_get_updates_finished, self); | |
171 | |
172 /* next periodic check will be scheduled after completion */ | |
173 self->periodic_check_id = 0; | |
174 | |
175 return (G_SOURCE_REMOVE); | |
176 } | |
177 | |
178 static void | |
179 pui_backend_set_property(GObject *object, guint property_id, | |
180 const GValue *value, GParamSpec *pspec) | |
181 { | |
182 PuiBackend *self = PUI_BACKEND(object); | |
183 | |
184 switch (property_id) { | |
185 case PROP_REFRESH_INTERVAL: | |
186 self->refresh_interval = g_value_get_uint(value); | |
187 g_debug("property \"refresh-interval\" set to %u", | |
188 self->refresh_interval); | |
189 break; | |
190 default: | |
191 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); | |
192 break; | |
193 } | |
194 } | |
195 | |
196 static void | |
197 pui_backend_get_property(GObject *object, guint property_id, GValue *value, | |
198 GParamSpec *pspec) | |
199 { | |
200 PuiBackend *self = PUI_BACKEND(object); | |
201 | |
202 switch (property_id) { | |
203 case PROP_IMPORTANT_UPDATES: | |
204 g_value_set_uint(value, self->important_updates); | |
205 break; | |
206 case PROP_NORMAL_UPDATES: | |
207 g_value_set_uint(value, self->normal_updates); | |
208 break; | |
209 case PROP_REFRESH_INTERVAL: | |
210 g_value_set_uint(value, self->refresh_interval); | |
211 break; | |
212 default: | |
213 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); | |
214 break; | |
215 } | |
216 } | |
217 | |
218 static void | |
219 pui_backend_dispose(GObject *object) | |
220 { | |
221 PuiBackend *self = PUI_BACKEND(object); | |
222 | |
223 if (self->periodic_check_id != 0) { | |
224 g_source_remove(self->periodic_check_id); | |
225 self->periodic_check_id = 0; | |
226 } | |
227 | |
228 if (self->cancellable != NULL) { | |
229 g_cancellable_cancel(self->cancellable); | |
230 g_clear_object(&self->cancellable); | |
231 } | |
232 | |
233 if (self->pk_control != NULL) { | |
234 g_clear_object(&self->pk_control); | |
235 } | |
236 | |
237 G_OBJECT_CLASS(pui_backend_parent_class)->dispose(object); | |
238 } | |
239 | |
240 static void | |
241 pui_backend_class_init(PuiBackendClass *klass) | |
242 { | |
243 GObjectClass *object_class = G_OBJECT_CLASS(klass); | |
244 | |
245 object_class->set_property = pui_backend_set_property; | |
246 object_class->get_property = pui_backend_get_property; | |
247 object_class->dispose = pui_backend_dispose; | |
248 | |
249 properties[PROP_IMPORTANT_UPDATES] = | |
250 g_param_spec_uint("important-updates", "Important updates", | |
251 "Number of available important updates", 0, G_MAXUINT, 0, | |
252 G_PARAM_READABLE); | |
253 | |
254 properties[PROP_NORMAL_UPDATES] = | |
255 g_param_spec_uint("normal-updates", "Normal updates", | |
256 "Number of available normal updates", 0, G_MAXUINT, 0, | |
257 G_PARAM_READABLE); | |
258 | |
259 properties[PROP_REFRESH_INTERVAL] = | |
260 g_param_spec_uint("refresh-interval", "Refresh interval", | |
261 "Interval in seconds for refreshing the package cache", 0, | |
262 G_MAXUINT, PUI_DEFAULT_REFRESH_INTERVAL, G_PARAM_READWRITE); | |
263 | |
264 g_object_class_install_properties(object_class, PROP_LAST, properties); | |
265 | |
266 signals[STATE_CHANGED] = g_signal_new("state-changed", | |
267 G_TYPE_FROM_CLASS(object_class), | |
268 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, | |
269 NULL, NULL, NULL, G_TYPE_NONE, 0); | |
270 | |
271 signals[RESTART_REQUIRED] = g_signal_new("restart-required", | |
272 G_TYPE_FROM_CLASS(object_class), | |
273 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, | |
274 NULL, NULL, NULL, G_TYPE_NONE, 0); | |
275 } | |
276 | |
277 static void | |
278 pui_backend_init(PuiBackend *self) | |
279 { | |
280 self->cancellable = g_cancellable_new(); | |
281 self->pk_control = pk_control_new(); | |
282 } | |
283 | |
284 static void | |
285 on_get_properties_finished(GObject *object, GAsyncResult *result, | |
286 gpointer user_data) | |
287 { | |
288 PkControl *control = PK_CONTROL(object); | |
289 PuiBackend *self; | |
290 GTask *task = user_data; | |
291 GError *error = NULL; | |
292 gchar *backend_name = NULL; | |
293 PkBitfield roles = 0; | |
294 gchar *roles_str = NULL; | |
295 | |
296 self = g_task_get_task_data(task); | |
297 | |
298 if (!pk_control_get_properties_finish(control, result, &error)) { | |
299 g_task_return_error(task, error); | |
300 goto out; | |
301 } | |
302 | |
303 /* check whether the backend supports GetUpdates */ | |
304 g_object_get(control, "backend-name", &backend_name, "roles", &roles, | |
305 "network-state", &self->network_state, NULL); | |
306 g_debug("backend: %s", backend_name); | |
307 roles_str = pk_role_bitfield_to_string(roles); | |
308 g_debug("roles: %s", roles_str); | |
309 g_debug("network-state: %s", | |
310 pk_network_enum_to_string(self->network_state)); | |
311 if (!pk_bitfield_contain(roles, PK_ROLE_ENUM_GET_UPDATES)) { | |
312 error = g_error_new(PUI_BACKEND_ERROR, | |
313 PUI_BACKEND_ERROR_GET_UPDATES_NOT_IMPLEMENTED, | |
314 "Getting updates is not implemented in the %s PackageKit " | |
315 "backend", backend_name); | |
316 g_task_return_error(task, error); | |
317 goto out; | |
318 } | |
319 | |
320 g_task_return_boolean(task, TRUE); | |
321 out: | |
322 g_free(roles_str); | |
323 g_free(backend_name); | |
324 g_object_unref(task); | |
325 } | |
326 | |
327 static void | |
328 on_notify_network_state(PkControl *pk_control, GParamSpec *pspec, | |
329 gpointer user_data) | |
330 { | |
331 PuiBackend *self = user_data; | |
332 PkNetworkEnum network_state; | |
333 guint elapsed_time; | |
334 guint remaining_time; | |
335 | |
336 g_object_get(pk_control, "network-state", &network_state, NULL); | |
337 g_debug("network state changed: %s", | |
338 pk_network_enum_to_string(network_state)); | |
339 if ((self->network_state == PK_NETWORK_ENUM_OFFLINE) && | |
340 (network_state != PK_NETWORK_ENUM_OFFLINE)) { | |
341 /* schedule periodic checks when coming back online */ | |
342 elapsed_time = (g_get_monotonic_time() - self->last_check) / | |
343 G_USEC_PER_SEC; | |
344 /* | |
345 * if more time that the check interval has passed since the | |
346 * last check, schedule a check after a short delay, otherwise | |
347 * wait until the interval has passed | |
348 */ | |
349 remaining_time = (elapsed_time < PUI_CHECK_UPDATES_INTERVAL) ? | |
350 PUI_CHECK_UPDATES_INTERVAL - elapsed_time : | |
351 PUI_STARTUP_DELAY; | |
352 self->periodic_check_id = g_timeout_add_seconds(remaining_time, | |
353 periodic_check, self); | |
354 } else if ((self->network_state != PK_NETWORK_ENUM_OFFLINE) && | |
355 (network_state == PK_NETWORK_ENUM_OFFLINE)) { | |
356 /* cancel periodic checks while offline */ | |
357 if (self->periodic_check_id != 0) { | |
358 g_source_remove(self->periodic_check_id); | |
359 } | |
360 } | |
361 self->network_state = network_state; | |
362 } | |
363 | |
364 static void | |
365 on_updates_changed(PkControl *control, gpointer user_data) | |
366 { | |
367 PuiBackend *self = user_data; | |
368 | |
369 /* | |
370 * schedule a check after a short delay so that a rapid succession of | |
371 * signals is coalesced | |
372 */ | |
373 if (self->network_state != PK_NETWORK_ENUM_OFFLINE) { | |
374 if (self->periodic_check_id != 0) { | |
375 g_source_remove(self->periodic_check_id); | |
376 } | |
377 self->periodic_check_id = | |
378 g_timeout_add_seconds(PUI_STARTUP_DELAY, periodic_check, | |
379 self); | |
380 } | |
381 } | |
382 | |
383 static void | |
384 on_restart_schedule(PkControl *control, gpointer user_data) | |
385 { | |
386 PuiBackend *self = user_data; | |
387 | |
388 g_debug("emitting signal restart-required"); | |
389 g_signal_emit(self, signals[RESTART_REQUIRED], 0); | |
390 } | |
391 | |
392 static void | |
393 pui_backend_init_async(GAsyncInitable *initable, int io_priority, | |
394 GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) | |
395 { | |
396 PuiBackend *self = PUI_BACKEND(initable); | |
397 GTask *task; | |
398 | |
399 task = g_task_new(G_OBJECT(initable), cancellable, callback, user_data); | |
400 g_task_set_priority(task, io_priority); | |
401 g_task_set_task_data(task, g_object_ref(self), | |
402 (GDestroyNotify)g_object_unref); | |
403 | |
404 pk_control_get_properties_async(self->pk_control, cancellable, | |
405 on_get_properties_finished, task); | |
406 } | |
407 | |
408 static gboolean | |
409 pui_backend_init_finish(GAsyncInitable *initable, GAsyncResult *result, | |
410 GError **errorp) | |
411 { | |
412 PuiBackend *self = PUI_BACKEND(initable); | |
413 GTask *task = G_TASK(result); | |
414 | |
415 if (!g_task_propagate_boolean(task, errorp)) { | |
416 return (FALSE); | |
417 } | |
418 | |
419 /* get notification when the network state changes */ | |
420 g_signal_connect(self->pk_control, "notify::network-state", | |
421 G_CALLBACK(on_notify_network_state), self); | |
422 /* get notifications when the available package updates change */ | |
423 g_signal_connect(self->pk_control, "updates-changed", | |
424 G_CALLBACK(on_updates_changed), self); | |
425 /* get notifications when an application restart is required */ | |
426 g_signal_connect(self->pk_control, "restart-schedule", | |
427 G_CALLBACK(on_restart_schedule), self); | |
428 /* schedule first check after a small delay */ | |
429 self->periodic_check_id = g_timeout_add_seconds(PUI_STARTUP_DELAY, | |
430 periodic_check, self); | |
431 | |
432 return (TRUE); | |
433 } | |
434 | |
435 static void | |
436 pui_backend_async_initable_iface_init(gpointer g_iface, gpointer iface_data) | |
437 { | |
438 GAsyncInitableIface *iface = g_iface; | |
439 | |
440 iface->init_async = pui_backend_init_async; | |
441 iface->init_finish = pui_backend_init_finish; | |
442 } | |
443 | |
444 void | |
445 pui_backend_new_async(GCancellable *cancellable, GAsyncReadyCallback callback, | |
446 gpointer user_data) | |
447 { | |
448 g_async_initable_new_async(PUI_TYPE_BACKEND, G_PRIORITY_DEFAULT, | |
449 cancellable, callback, user_data, NULL); | |
450 } | |
451 | |
452 PuiBackend * | |
453 pui_backend_new_finish(GAsyncResult *result, GError **errorp) | |
454 { | |
455 GObject *object; | |
456 GObject *source_object; | |
457 | |
458 source_object = g_async_result_get_source_object(result); | |
459 object = g_async_initable_new_finish(G_ASYNC_INITABLE(source_object), | |
460 result, errorp); | |
461 g_object_unref(source_object); | |
462 | |
463 return ((object != NULL) ? PUI_BACKEND(object) : NULL); | |
464 } |