1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 * vim: set et sw=8 ts=8:
3 *
4 * Copyright (c) 2008, Novell, Inc.
5 * Copyright (C) 2012-2021 MATE Developers
6 *
7 * Authors: Vincent Untz <vuntz@gnome.org>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program 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
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
22 *
23 */
24
25 /* gcc -DHAVE_LIBNOTIFY -DTEST -Wall `pkg-config --cflags --libs gobject-2.0 gio-unix-2.0 glib-2.0 gtk+-2.0 libnotify` -o msd-disk-space-test msd-disk-space.c */
26
27 #include "config.h"
28
29 #include <sys/statvfs.h>
30 #include <time.h>
31 #include <unistd.h>
32
33 #include <glib.h>
34 #include <glib/gi18n.h>
35 #include <glib-object.h>
36 #include <gio/gunixmounts.h>
37 #include <gio/gio.h>
38 #include <gtk/gtk.h>
39
40 #include "msd-disk-space.h"
41 #include "msd-ldsm-dialog.h"
42 #include "msd-ldsm-trash-empty.h"
43
44
45 #define GIGABYTE 1024 * 1024 * 1024
46
47 #define CHECK_EVERY_X_SECONDS 60
48
49 #define DISK_SPACE_ANALYZER "mate-disk-usage-analyzer"
50
51 #define SETTINGS_HOUSEKEEPING_SCHEMA "org.mate.SettingsDaemon.plugins.housekeeping"
52 #define SETTINGS_FREE_PC_NOTIFY_KEY "free-percent-notify"
53 #define SETTINGS_FREE_PC_NOTIFY_AGAIN_KEY "free-percent-notify-again"
54 #define SETTINGS_FREE_SIZE_NO_NOTIFY "free-size-gb-no-notify"
55 #define SETTINGS_MIN_NOTIFY_PERIOD "min-notify-period"
56 #define SETTINGS_IGNORE_PATHS "ignore-paths"
57
58 typedef struct
59 {
60 GUnixMountEntry *mount;
61 struct statvfs buf;
62 time_t notify_time;
63 } LdsmMountInfo;
64
65 static GHashTable *ldsm_notified_hash = NULL;
66 static unsigned int ldsm_timeout_id = 0;
67 static GUnixMountMonitor *ldsm_monitor = NULL;
68 static double free_percent_notify = 0.05;
69 static double free_percent_notify_again = 0.01;
70 static unsigned int free_size_gb_no_notify = 2;
71 static unsigned int min_notify_period = 10;
72 static GSList *ignore_paths = NULL;
73 static GSettings *settings = NULL;
74 static MsdLdsmDialog *dialog = NULL;
75 static guint64 *time_read;
76
77 static gchar*
ldsm_get_fs_id_for_path(const gchar * path)78 ldsm_get_fs_id_for_path (const gchar *path)
79 {
80 GFile *file;
81 GFileInfo *fileinfo;
82 gchar *attr_id_fs;
83
84 file = g_file_new_for_path (path);
85 fileinfo = g_file_query_info (file, G_FILE_ATTRIBUTE_ID_FILESYSTEM, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
86 if (fileinfo) {
87 attr_id_fs = g_strdup (g_file_info_get_attribute_string (fileinfo, G_FILE_ATTRIBUTE_ID_FILESYSTEM));
88 g_object_unref (fileinfo);
89 } else {
90 attr_id_fs = NULL;
91 }
92
93 g_object_unref (file);
94
95 return attr_id_fs;
96 }
97
98 static gboolean
ldsm_mount_has_trash(LdsmMountInfo * mount)99 ldsm_mount_has_trash (LdsmMountInfo *mount)
100 {
101 const gchar *user_data_dir;
102 gchar *user_data_attr_id_fs;
103 gchar *path_attr_id_fs;
104 gboolean mount_uses_user_trash = FALSE;
105 gchar *trash_files_dir;
106 gboolean has_trash = FALSE;
107 GDir *dir;
108 const gchar *path;
109
110 user_data_dir = g_get_user_data_dir ();
111 user_data_attr_id_fs = ldsm_get_fs_id_for_path (user_data_dir);
112
113 path = g_unix_mount_get_mount_path (mount->mount);
114 path_attr_id_fs = ldsm_get_fs_id_for_path (path);
115
116 if (g_strcmp0 (user_data_attr_id_fs, path_attr_id_fs) == 0) {
117 /* The volume that is low on space is on the same volume as our home
118 * directory. This means the trash is at $XDG_DATA_HOME/Trash,
119 * not at the root of the volume which is full.
120 */
121 mount_uses_user_trash = TRUE;
122 }
123
124 g_free (user_data_attr_id_fs);
125 g_free (path_attr_id_fs);
126
127 /* I can't think of a better way to find out if a volume has any trash. Any suggestions? */
128 if (mount_uses_user_trash) {
129 trash_files_dir = g_build_filename (g_get_user_data_dir (), "Trash", "files", NULL);
130 } else {
131 gchar *uid;
132
133 uid = g_strdup_printf ("%d", getuid ());
134 trash_files_dir = g_build_filename (path, ".Trash", uid, "files", NULL);
135 if (!g_file_test (trash_files_dir, G_FILE_TEST_IS_DIR)) {
136 gchar *trash_dir;
137
138 g_free (trash_files_dir);
139 trash_dir = g_strdup_printf (".Trash-%s", uid);
140 trash_files_dir = g_build_filename (path, trash_dir, "files", NULL);
141 g_free (trash_dir);
142 if (!g_file_test (trash_files_dir, G_FILE_TEST_IS_DIR)) {
143 g_free (trash_files_dir);
144 g_free (uid);
145 return has_trash;
146 }
147 }
148 g_free (uid);
149 }
150
151 dir = g_dir_open (trash_files_dir, 0, NULL);
152 if (dir) {
153 if (g_dir_read_name (dir))
154 has_trash = TRUE;
155 g_dir_close (dir);
156 }
157
158 g_free (trash_files_dir);
159
160 return has_trash;
161 }
162
163 static void
ldsm_analyze_path(const gchar * path)164 ldsm_analyze_path (const gchar *path)
165 {
166 const gchar *argv[] = { DISK_SPACE_ANALYZER, path, NULL };
167
168 g_spawn_async (NULL, (gchar **) argv, NULL, G_SPAWN_SEARCH_PATH,
169 NULL, NULL, NULL, NULL);
170 }
171
172 static gboolean
ldsm_notify_for_mount(LdsmMountInfo * mount,gboolean multiple_volumes,gboolean other_usable_volumes)173 ldsm_notify_for_mount (LdsmMountInfo *mount,
174 gboolean multiple_volumes,
175 gboolean other_usable_volumes)
176 {
177 gchar *name, *program;
178 gint64 free_space;
179 gint response;
180 gboolean has_trash;
181 gboolean has_disk_analyzer;
182 gboolean retval = TRUE;
183 gchar *path;
184
185 /* Don't show a dialog if one is already displayed */
186 if (dialog)
187 return retval;
188
189 name = g_unix_mount_guess_name (mount->mount);
190 free_space = (gint64) mount->buf.f_frsize * (gint64) mount->buf.f_bavail;
191 has_trash = ldsm_mount_has_trash (mount);
192 path = g_strdup (g_unix_mount_get_mount_path (mount->mount));
193
194 program = g_find_program_in_path (DISK_SPACE_ANALYZER);
195 has_disk_analyzer = (program != NULL);
196 g_free (program);
197
198 dialog = msd_ldsm_dialog_new (other_usable_volumes,
199 multiple_volumes,
200 has_disk_analyzer,
201 has_trash,
202 free_space,
203 name,
204 path);
205
206 g_free (name);
207
208 g_object_ref (G_OBJECT (dialog));
209 response = gtk_dialog_run (GTK_DIALOG (dialog));
210
211 gtk_widget_destroy (GTK_WIDGET (dialog));
212 dialog = NULL;
213
214 switch (response) {
215 case GTK_RESPONSE_CANCEL:
216 retval = FALSE;
217 break;
218 case MSD_LDSM_DIALOG_RESPONSE_ANALYZE:
219 retval = FALSE;
220 ldsm_analyze_path (path);
221 break;
222 case MSD_LDSM_DIALOG_RESPONSE_EMPTY_TRASH:
223 retval = TRUE;
224 msd_ldsm_trash_empty ();
225 break;
226 case GTK_RESPONSE_NONE:
227 case GTK_RESPONSE_DELETE_EVENT:
228 retval = TRUE;
229 break;
230 default:
231 g_assert_not_reached ();
232 }
233
234 g_free (path);
235
236 return retval;
237 }
238
239 static gboolean
ldsm_mount_has_space(LdsmMountInfo * mount)240 ldsm_mount_has_space (LdsmMountInfo *mount)
241 {
242 gdouble free_space;
243
244 free_space = (double) mount->buf.f_bavail / (double) mount->buf.f_blocks;
245 /* enough free space, nothing to do */
246 if (free_space > free_percent_notify)
247 return TRUE;
248
249 if (((gint64) mount->buf.f_frsize * (gint64) mount->buf.f_bavail) > ((gint64) free_size_gb_no_notify * GIGABYTE))
250 return TRUE;
251
252 /* If we got here, then this volume is low on space */
253 return FALSE;
254 }
255
256 static gboolean
ldsm_mount_is_virtual(LdsmMountInfo * mount)257 ldsm_mount_is_virtual (LdsmMountInfo *mount)
258 {
259 if (mount->buf.f_blocks == 0) {
260 /* Filesystems with zero blocks are virtual */
261 return TRUE;
262 }
263
264 return FALSE;
265 }
266
267 static gint
ldsm_ignore_path_compare(gconstpointer a,gconstpointer b)268 ldsm_ignore_path_compare (gconstpointer a,
269 gconstpointer b)
270 {
271 return g_strcmp0 ((const gchar *)a, (const gchar *)b);
272 }
273
274 static gboolean
ldsm_mount_is_user_ignore(const gchar * path)275 ldsm_mount_is_user_ignore (const gchar *path)
276 {
277 if (g_slist_find_custom (ignore_paths, path, (GCompareFunc) ldsm_ignore_path_compare) != NULL)
278 return TRUE;
279 else
280 return FALSE;
281 }
282
283
284 static gboolean
is_in(const gchar * value,const gchar * set[])285 is_in (const gchar *value, const gchar *set[])
286 {
287 int i;
288 for (i = 0; set[i] != NULL; i++)
289 {
290 if (strcmp (set[i], value) == 0)
291 return TRUE;
292 }
293 return FALSE;
294 }
295
296 static gboolean
ldsm_mount_should_ignore(GUnixMountEntry * mount)297 ldsm_mount_should_ignore (GUnixMountEntry *mount)
298 {
299 const gchar *fs, *device, *path;
300
301 path = g_unix_mount_get_mount_path (mount);
302 if (ldsm_mount_is_user_ignore (path))
303 return TRUE;
304
305 /* This is borrowed from GLib and used as a way to determine
306 * which mounts we should ignore by default. GLib doesn't
307 * expose this in a way that allows it to be used for this
308 * purpose
309 */
310
311 /* We also ignore network filesystems */
312
313 const gchar *ignore_fs[] = {
314 "adfs",
315 "afs",
316 "auto",
317 "autofs",
318 "autofs4",
319 "cifs",
320 "cxfs",
321 "devfs",
322 "devpts",
323 "ecryptfs",
324 "fdescfs",
325 "gfs",
326 "gfs2",
327 "kernfs",
328 "linprocfs",
329 "linsysfs",
330 "lustre",
331 "lustre_lite",
332 "ncpfs",
333 "nfs",
334 "nfs4",
335 "nfsd",
336 "ocfs2",
337 "proc",
338 "procfs",
339 "ptyfs",
340 "rpc_pipefs",
341 "selinuxfs",
342 "smbfs",
343 "sysfs",
344 "tmpfs",
345 "usbfs",
346 "zfs",
347 NULL
348 };
349 const gchar *ignore_devices[] = {
350 "none",
351 "sunrpc",
352 "devpts",
353 "nfsd",
354 "/dev/loop",
355 "/dev/vn",
356 NULL
357 };
358
359 fs = g_unix_mount_get_fs_type (mount);
360 device = g_unix_mount_get_device_path (mount);
361
362 if (is_in (fs, ignore_fs))
363 return TRUE;
364
365 if (is_in (device, ignore_devices))
366 return TRUE;
367
368 return FALSE;
369 }
370
371 static void
ldsm_free_mount_info(gpointer data)372 ldsm_free_mount_info (gpointer data)
373 {
374 LdsmMountInfo *mount = data;
375
376 g_return_if_fail (mount != NULL);
377
378 g_unix_mount_free (mount->mount);
379 g_free (mount);
380 }
381
382 static void
ldsm_maybe_warn_mounts(GList * mounts,gboolean multiple_volumes,gboolean other_usable_volumes)383 ldsm_maybe_warn_mounts (GList *mounts,
384 gboolean multiple_volumes,
385 gboolean other_usable_volumes)
386 {
387 GList *l;
388 gboolean done = FALSE;
389
390 for (l = mounts; l != NULL; l = l->next) {
391 LdsmMountInfo *mount_info = l->data;
392 LdsmMountInfo *previous_mount_info;
393 gdouble free_space;
394 gdouble previous_free_space;
395 time_t curr_time;
396 const gchar *path;
397 gboolean show_notify;
398
399 if (done) {
400 /* Don't show any more dialogs if the user took action with the last one. The user action
401 * might free up space on multiple volumes, making the next dialog redundant.
402 */
403 ldsm_free_mount_info (mount_info);
404 continue;
405 }
406
407 path = g_unix_mount_get_mount_path (mount_info->mount);
408
409 previous_mount_info = g_hash_table_lookup (ldsm_notified_hash, path);
410 if (previous_mount_info != NULL)
411 previous_free_space = (gdouble) previous_mount_info->buf.f_bavail / (gdouble) previous_mount_info->buf.f_blocks;
412
413 free_space = (gdouble) mount_info->buf.f_bavail / (gdouble) mount_info->buf.f_blocks;
414
415 if (previous_mount_info == NULL) {
416 /* We haven't notified for this mount yet */
417 show_notify = TRUE;
418 mount_info->notify_time = time (NULL);
419 g_hash_table_replace (ldsm_notified_hash, g_strdup (path), mount_info);
420 } else if ((previous_free_space - free_space) > free_percent_notify_again) {
421 /* We've notified for this mount before and free space has decreased sufficiently since last time to notify again */
422 curr_time = time (NULL);
423 if (difftime (curr_time, previous_mount_info->notify_time) > (gdouble)(min_notify_period * 60)) {
424 show_notify = TRUE;
425 mount_info->notify_time = curr_time;
426 } else {
427 /* It's too soon to show the dialog again. However, we still replace the LdsmMountInfo
428 * struct in the hash table, but give it the notfiy time from the previous dialog.
429 * This will stop the notification from reappearing unnecessarily as soon as the timeout expires.
430 */
431 show_notify = FALSE;
432 mount_info->notify_time = previous_mount_info->notify_time;
433 }
434 g_hash_table_replace (ldsm_notified_hash, g_strdup (path), mount_info);
435 } else {
436 /* We've notified for this mount before, but the free space hasn't decreased sufficiently to notify again */
437 ldsm_free_mount_info (mount_info);
438 show_notify = FALSE;
439 }
440
441 if (show_notify) {
442 if (ldsm_notify_for_mount (mount_info, multiple_volumes, other_usable_volumes))
443 done = TRUE;
444 }
445 }
446 }
447
448 static gboolean
ldsm_check_all_mounts(gpointer data)449 ldsm_check_all_mounts (gpointer data)
450 {
451 GList *mounts;
452 GList *l;
453 GList *check_mounts = NULL;
454 GList *full_mounts = NULL;
455 guint number_of_mounts;
456 guint number_of_full_mounts;
457 gboolean multiple_volumes = FALSE;
458 gboolean other_usable_volumes = FALSE;
459
460 /* We iterate through the static mounts in /etc/fstab first, seeing if
461 * they're mounted by checking if the GUnixMountPoint has a corresponding GUnixMountEntry.
462 * Iterating through the static mounts means we automatically ignore dynamically mounted media.
463 */
464 mounts = g_unix_mount_points_get (time_read);
465
466 for (l = mounts; l != NULL; l = l->next) {
467 GUnixMountPoint *mount_point = l->data;
468 GUnixMountEntry *mount;
469 LdsmMountInfo *mount_info;
470 const gchar *path;
471
472 path = g_unix_mount_point_get_mount_path (mount_point);
473 mount = g_unix_mount_at (path, time_read);
474 g_unix_mount_point_free (mount_point);
475 if (mount == NULL) {
476 /* The GUnixMountPoint is not mounted */
477 continue;
478 }
479
480 mount_info = g_new0 (LdsmMountInfo, 1);
481 mount_info->mount = mount;
482
483 path = g_unix_mount_get_mount_path (mount);
484
485 if (g_unix_mount_is_readonly (mount)) {
486 ldsm_free_mount_info (mount_info);
487 continue;
488 }
489
490 if (ldsm_mount_should_ignore (mount)) {
491 ldsm_free_mount_info (mount_info);
492 continue;
493 }
494
495 if (statvfs (path, &mount_info->buf) != 0) {
496 ldsm_free_mount_info (mount_info);
497 continue;
498 }
499
500 if (ldsm_mount_is_virtual (mount_info)) {
501 ldsm_free_mount_info (mount_info);
502 continue;
503 }
504
505 check_mounts = g_list_prepend (check_mounts, mount_info);
506 }
507 g_list_free (mounts);
508
509 number_of_mounts = g_list_length (check_mounts);
510 if (number_of_mounts > 1)
511 multiple_volumes = TRUE;
512
513 for (l = check_mounts; l != NULL; l = l->next) {
514 LdsmMountInfo *mount_info = l->data;
515
516 if (!ldsm_mount_has_space (mount_info)) {
517 full_mounts = g_list_prepend (full_mounts, mount_info);
518 } else {
519 g_hash_table_remove (ldsm_notified_hash, g_unix_mount_get_mount_path (mount_info->mount));
520 ldsm_free_mount_info (mount_info);
521 }
522 }
523
524 number_of_full_mounts = g_list_length (full_mounts);
525 if (number_of_mounts > number_of_full_mounts)
526 other_usable_volumes = TRUE;
527
528 ldsm_maybe_warn_mounts (full_mounts, multiple_volumes,
529 other_usable_volumes);
530
531 g_list_free (check_mounts);
532 g_list_free (full_mounts);
533
534 return TRUE;
535 }
536
537 static gboolean
ldsm_is_hash_item_not_in_mounts(gpointer key,gpointer value,gpointer user_data)538 ldsm_is_hash_item_not_in_mounts (gpointer key,
539 gpointer value,
540 gpointer user_data)
541 {
542 GList *l;
543
544 for (l = (GList *) user_data; l != NULL; l = l->next) {
545 GUnixMountEntry *mount = l->data;
546 const char *path;
547
548 path = g_unix_mount_get_mount_path (mount);
549
550 if (strcmp (path, key) == 0)
551 return FALSE;
552 }
553
554 return TRUE;
555 }
556
557 static void
ldsm_mounts_changed(GObject * monitor G_GNUC_UNUSED,gpointer data G_GNUC_UNUSED)558 ldsm_mounts_changed (GObject *monitor G_GNUC_UNUSED,
559 gpointer data G_GNUC_UNUSED)
560 {
561 GList *mounts;
562
563 /* remove the saved data for mounts that got removed */
564 mounts = g_unix_mounts_get (time_read);
565 g_hash_table_foreach_remove (ldsm_notified_hash,
566 ldsm_is_hash_item_not_in_mounts, mounts);
567 g_list_free_full (mounts, (GDestroyNotify) g_unix_mount_free);
568
569 /* check the status now, for the new mounts */
570 ldsm_check_all_mounts (NULL);
571
572 /* and reset the timeout */
573 if (ldsm_timeout_id)
574 g_source_remove (ldsm_timeout_id);
575 ldsm_timeout_id = g_timeout_add_seconds (CHECK_EVERY_X_SECONDS,
576 ldsm_check_all_mounts, NULL);
577 }
578
579 static gboolean
ldsm_is_hash_item_in_ignore_paths(gpointer key,gpointer value,gpointer user_data)580 ldsm_is_hash_item_in_ignore_paths (gpointer key,
581 gpointer value,
582 gpointer user_data)
583 {
584 return ldsm_mount_is_user_ignore (key);
585 }
586
587 static void
msd_ldsm_get_config(void)588 msd_ldsm_get_config (void)
589 {
590 gchar **settings_list;
591
592 free_percent_notify = g_settings_get_double (settings,
593 SETTINGS_FREE_PC_NOTIFY_KEY);
594 if (free_percent_notify >= 1 || free_percent_notify < 0) {
595 /* FIXME define min and max in gschema! */
596 g_warning ("Invalid configuration of free_percent_notify: %f\n" \
597 "Using sensible default", free_percent_notify);
598 free_percent_notify = 0.05;
599 }
600
601 free_percent_notify_again = g_settings_get_double (settings,
602 SETTINGS_FREE_PC_NOTIFY_AGAIN_KEY);
603 if (free_percent_notify_again >= 1 || free_percent_notify_again < 0) {
604 /* FIXME define min and max in gschema! */
605 g_warning ("Invalid configuration of free_percent_notify_again: %f\n" \
606 "Using sensible default\n", free_percent_notify_again);
607 free_percent_notify_again = 0.01;
608 }
609
610 free_size_gb_no_notify = g_settings_get_int (settings,
611 SETTINGS_FREE_SIZE_NO_NOTIFY);
612 min_notify_period = g_settings_get_int (settings,
613 SETTINGS_MIN_NOTIFY_PERIOD);
614
615 if (ignore_paths != NULL) {
616 g_slist_free_full (ignore_paths, g_free);
617 ignore_paths = NULL;
618 }
619
620 settings_list = g_settings_get_strv (settings, SETTINGS_IGNORE_PATHS);
621 if (settings_list != NULL) {
622 guint i;
623
624 for (i = 0; settings_list[i] != NULL; i++) {
625 if (settings_list[i] != NULL)
626 ignore_paths = g_slist_prepend (ignore_paths, g_strdup (settings_list[i]));
627 }
628
629 /* Make sure we dont leave stale entries in ldsm_notified_hash */
630 g_hash_table_foreach_remove (ldsm_notified_hash,
631 ldsm_is_hash_item_in_ignore_paths, NULL);
632
633 g_strfreev (settings_list);
634 }
635
636 }
637
638 static void
msd_ldsm_update_config(GSettings * gsettings G_GNUC_UNUSED,gchar * key G_GNUC_UNUSED,gpointer user_data G_GNUC_UNUSED)639 msd_ldsm_update_config (GSettings *gsettings G_GNUC_UNUSED,
640 gchar *key G_GNUC_UNUSED,
641 gpointer user_data G_GNUC_UNUSED)
642 {
643 msd_ldsm_get_config ();
644 }
645
646 void
msd_ldsm_setup(gboolean check_now)647 msd_ldsm_setup (gboolean check_now)
648 {
649 if (ldsm_notified_hash || ldsm_timeout_id || ldsm_monitor) {
650 g_warning ("Low disk space monitor already initialized.");
651 return;
652 }
653
654 ldsm_notified_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
655 g_free,
656 ldsm_free_mount_info);
657
658 settings = g_settings_new (SETTINGS_HOUSEKEEPING_SCHEMA);
659 msd_ldsm_get_config ();
660 g_signal_connect (settings, "changed", G_CALLBACK (msd_ldsm_update_config), NULL);
661
662 ldsm_monitor = g_unix_mount_monitor_get ();
663 g_signal_connect (ldsm_monitor, "mounts-changed",
664 G_CALLBACK (ldsm_mounts_changed), NULL);
665
666 if (check_now)
667 ldsm_check_all_mounts (NULL);
668
669 ldsm_timeout_id = g_timeout_add_seconds (CHECK_EVERY_X_SECONDS,
670 ldsm_check_all_mounts, NULL);
671
672 }
673
674 void
msd_ldsm_clean(void)675 msd_ldsm_clean (void)
676 {
677 if (ldsm_timeout_id)
678 g_source_remove (ldsm_timeout_id);
679 ldsm_timeout_id = 0;
680
681 if (ldsm_notified_hash)
682 g_hash_table_destroy (ldsm_notified_hash);
683 ldsm_notified_hash = NULL;
684
685 if (ldsm_monitor)
686 g_object_unref (ldsm_monitor);
687 ldsm_monitor = NULL;
688
689 if (settings) {
690 g_object_unref (settings);
691 }
692
693 if (dialog) {
694 gtk_widget_destroy (GTK_WIDGET (dialog));
695 dialog = NULL;
696 }
697
698 if (ignore_paths) {
699 g_slist_free_full (ignore_paths, g_free);
700 }
701 }
702
703 #ifdef TEST
704 int
main(int argc,char ** argv)705 main (int argc,
706 char **argv)
707 {
708 GMainLoop *loop;
709
710 gtk_init (&argc, &argv);
711
712 loop = g_main_loop_new (NULL, FALSE);
713
714 msd_ldsm_setup (TRUE);
715
716 g_main_loop_run (loop);
717
718 msd_ldsm_clean ();
719 g_main_loop_unref (loop);
720
721 return 0;
722 }
723 #endif /* TEST */
724