1 /*  xfce4-places-plugin
2  *
3  *  Model: volumes bookmarks (i.e., removable media)
4  *
5  *  Copyright (c) 2007 Diego Ongaro <ongardie@gmail.com>
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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25 
26 #include "model.h"
27 #include "model_volumes.h"
28 #include "support.h"
29 
30 #include <gio/gio.h>
31 #ifdef HAVE_GIO_UNIX
32 #include <gio/gunixmounts.h>
33 #endif
34 #include <gtk/gtk.h>
35 
36 #ifdef HAVE_LIBNOTIFY
37 #include "model_volumes_notify.h"
38 #endif
39 
40 #include <libxfce4util/libxfce4util.h>
41 
42 #include <string.h>
43 
44 #define pbg_priv(pbg) ((PBVolData*) pbg->priv)
45 
46 typedef struct
47 {
48     GVolumeMonitor *volume_monitor;
49     gboolean   changed;
50     gboolean   mount_and_open_by_default;
51 } PBVolData;
52 
53 
54 /********** Actions Callbacks **********/
55 static void
pbvol_eject_finish(GObject * object,GAsyncResult * result,gpointer user_data)56 pbvol_eject_finish(GObject *object,
57                 GAsyncResult *result,
58                 gpointer user_data)
59 {
60     GVolume *volume = G_VOLUME(object);
61     GError *error = NULL;
62 
63     g_return_if_fail(G_IS_VOLUME(object));
64     g_return_if_fail(G_IS_ASYNC_RESULT(result));
65 
66     if (!g_volume_eject_with_operation_finish(volume, result, &error)) {
67          /* ignore GIO errors handled internally */
68          if(error->domain != G_IO_ERROR || error->code != G_IO_ERROR_FAILED_HANDLED) {
69              gchar *volume_name = g_volume_get_name(volume);
70              places_show_error_dialog(error,
71                                  _("Failed to eject \"%s\""),
72                                  volume_name);
73              g_free(volume_name);
74          }
75          g_error_free(error);
76     }
77 
78 #ifdef HAVE_LIBNOTIFY
79     pbvol_notify_eject_finish(volume);
80 #endif
81 }
82 
83 static void
pbvol_eject(PlacesBookmarkAction * action)84 pbvol_eject(PlacesBookmarkAction *action)
85 {
86     GVolume *volume;
87 
88     DBG("Eject");
89 
90     g_return_if_fail(G_IS_VOLUME(action->priv));
91     volume = G_VOLUME(action->priv);
92 
93     if (g_volume_can_eject(volume)) {
94 #ifdef HAVE_LIBNOTIFY
95         pbvol_notify_eject(volume);
96 #endif
97         g_volume_eject_with_operation(volume, G_MOUNT_UNMOUNT_NONE, NULL,
98                            NULL,
99                            pbvol_eject_finish,
100                            g_object_ref(volume));
101     }
102 }
103 
104 static void
pbvol_unmount_finish(GObject * object,GAsyncResult * result,gpointer user_data)105 pbvol_unmount_finish(GObject *object,
106                 GAsyncResult *result,
107                 gpointer user_data)
108 {
109     GMount *mount = G_MOUNT(object);
110     GError *error = NULL;
111 
112     g_return_if_fail(G_IS_MOUNT(object));
113     g_return_if_fail(G_IS_ASYNC_RESULT(result));
114 
115     if (!g_mount_unmount_with_operation_finish(mount, result, &error)) {
116          /* ignore GIO errors handled internally */
117          if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_FAILED_HANDLED) {
118              gchar *mount_name = g_mount_get_name(mount);
119              places_show_error_dialog(error,
120                                      _("Failed to unmount \"%s\""),
121                                      mount_name);
122              g_free(mount_name);
123          }
124          g_error_free (error);
125     }
126 
127 #ifdef HAVE_LIBNOTIFY
128     pbvol_notify_unmount_finish(mount);
129 #endif
130 }
131 
132 static void
pbvol_unmount(PlacesBookmarkAction * action)133 pbvol_unmount(PlacesBookmarkAction *action)
134 {
135     GVolume *volume;
136     GMount *mount;
137 
138     DBG("Unmount");
139 
140     g_return_if_fail(G_IS_VOLUME(action->priv));
141     volume = G_VOLUME(action->priv);
142     mount = g_volume_get_mount(volume);
143 
144     if (mount) {
145 #ifdef HAVE_LIBNOTIFY
146         pbvol_notify_unmount(mount);
147 #endif
148         g_mount_unmount_with_operation(mount, G_MOUNT_UNMOUNT_NONE, NULL,
149                             NULL,
150                             pbvol_unmount_finish,
151                             g_object_ref(volume));
152     }
153 }
154 
155 static void
pbvol_mount_finish(GObject * object,GAsyncResult * result,gpointer user_data)156 pbvol_mount_finish(GObject *object,
157                 GAsyncResult *result,
158                 gpointer user_data)
159 {
160     GVolume *volume = G_VOLUME(object);
161     GError *error = NULL;
162 
163     DBG("Mount finish");
164 
165     if (!g_volume_mount_finish(volume, result, &error)) {
166          /* ignore GIO errors handled internally */
167          if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_FAILED_HANDLED) {
168              gchar *volume_name = g_volume_get_name(volume);
169              places_show_error_dialog(error,
170                                      _("Failed to mount \"%s\""),
171                                      volume_name);
172              g_free(volume_name);
173          }
174          g_error_free (error);
175     }
176 }
177 
178 static void
pbvol_mount_finish_and_open(GObject * object,GAsyncResult * result,gpointer user_data)179 pbvol_mount_finish_and_open(GObject *object,
180                             GAsyncResult *result,
181                             gpointer user_data)
182 {
183     GVolume *volume = G_VOLUME(object);
184     GError *error = NULL;
185 
186     DBG("Mount finish and open");
187 
188     if (!g_volume_mount_finish(volume, result, &error)) {
189          /* ignore GIO errors handled internally */
190          if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_FAILED_HANDLED) {
191              gchar *volume_name = g_volume_get_name(volume);
192              places_show_error_dialog(error,
193                                      _("Failed to mount \"%s\""),
194                                      volume_name);
195              g_free(volume_name);
196          }
197          g_error_free (error);
198     } else {
199         GMount *mount;
200         gchar *uri;
201         mount = g_volume_get_mount(volume);
202 
203         if (mount) {
204             GFile *file = g_mount_get_root(mount);
205             uri = g_file_get_uri(file);
206             places_load_file_browser(uri);
207             g_free(uri);
208             g_object_unref(file);
209             g_object_unref(mount);
210         }
211     }
212 }
213 
214 static void
pbvol_mount(PlacesBookmarkAction * action)215 pbvol_mount(PlacesBookmarkAction *action)
216 {
217     GVolume *volume;
218     GMount *mount;
219 
220     DBG("Mount");
221 
222     g_return_if_fail(G_IS_VOLUME(action->priv));
223     volume = G_VOLUME(action->priv);
224     mount = g_volume_get_mount(volume);
225 
226     if (!mount) {
227         GMountOperation *operation = gtk_mount_operation_new(NULL);
228 
229         g_volume_mount(volume, G_MOUNT_MOUNT_NONE, operation, NULL,
230                        pbvol_mount_finish,
231                        g_object_ref(volume));
232 
233         g_object_unref(operation);
234     }
235 }
236 
237 static void
pbvol_mount_and_open(PlacesBookmarkAction * action)238 pbvol_mount_and_open(PlacesBookmarkAction *action)
239 {
240     GVolume *volume;
241     GMount *mount;
242 
243     DBG("Mount and open");
244 
245     g_return_if_fail(G_IS_VOLUME(action->priv));
246     volume = G_VOLUME(action->priv);
247     mount = g_volume_get_mount(volume);
248 
249     if (!mount) {
250         GMountOperation *operation = gtk_mount_operation_new(NULL);
251 
252         g_volume_mount(volume, G_MOUNT_MOUNT_NONE, operation, NULL,
253                        pbvol_mount_finish_and_open,
254                        g_object_ref(volume));
255 
256         g_object_unref(operation);
257     }
258 }
259 
260 #ifdef HAVE_GIO_UNIX
261 static gboolean
pbvol_mount_is_internal(GMount * mount)262 pbvol_mount_is_internal (GMount *mount)
263 {
264     const gchar *point_mount_path;
265     gboolean is_internal = FALSE;
266     GFile *root;
267     GList *lp;
268     GList *mount_points;
269     gchar *mount_path;
270 
271     g_return_val_if_fail(G_IS_MOUNT(mount), FALSE);
272 
273     /* determine the mount path */
274     root = g_mount_get_root(mount);
275     mount_path = g_file_get_path(root);
276     g_object_unref(root);
277 
278     /* assume non-internal if we cannot determine the path */
279     if (!mount_path)
280         return FALSE;
281 
282     if (g_unix_is_mount_path_system_internal(mount_path)) {
283         /* mark as internal */
284         is_internal = TRUE;
285     } else {
286         /* get a list of all mount points */
287         mount_points = g_unix_mount_points_get(NULL);
288 
289         /* search for the mount point associated with the mount entry */
290         for (lp = mount_points; !is_internal && lp != NULL; lp = lp->next) {
291             point_mount_path = g_unix_mount_point_get_mount_path(lp->data);
292 
293             /* check if this is the mount point we are looking for */
294             if (g_strcmp0(mount_path, point_mount_path) == 0) {
295                 /* mark as internal if the user cannot mount this device */
296                 if (!g_unix_mount_point_is_user_mountable(lp->data))
297                     is_internal = TRUE;
298             }
299 
300             /* free the mount point, we no longer need it */
301             g_unix_mount_point_free(lp->data);
302         }
303 
304         /* free the mount point list */
305         g_list_free(mount_points);
306     }
307 
308     g_free(mount_path);
309 
310 
311     return is_internal;
312 }
313 #endif
314 
315 
316 static gboolean
pbvol_is_removable(GVolume * volume)317 pbvol_is_removable(GVolume *volume)
318 {
319     gboolean can_eject = FALSE;
320     gboolean can_mount = FALSE;
321     gboolean can_unmount = FALSE;
322     gboolean is_removable = FALSE;
323     gboolean is_internal = FALSE;
324     GDrive *drive;
325     GMount *mount;
326 
327     g_return_val_if_fail(G_IS_VOLUME(volume), FALSE);
328 
329     /* check if the volume can be ejected */
330     can_eject = g_volume_can_eject(volume);
331 
332     /* determine the drive for the volume */
333     drive = g_volume_get_drive(volume);
334     if (drive) {
335         /*check if the drive media can be removed */
336         is_removable = g_drive_is_media_removable(drive);
337 
338         /* release the drive */
339         g_object_unref(drive);
340     }
341     /* determine the mount for the volume (if it is mounted at all) */
342     mount = g_volume_get_mount(volume);
343     if (mount) {
344 #ifdef HAVE_GIO_UNIX
345         is_internal = pbvol_mount_is_internal (mount);
346 #endif
347 
348         /* check if the volume can be unmounted */
349         can_unmount = g_mount_can_unmount(mount);
350 
351         /* release the mount */
352         g_object_unref(mount);
353     }
354 
355     /* determine whether the device can be mounted */
356     can_mount = g_volume_can_mount(volume);
357 
358     return (!is_internal) && (can_eject || can_unmount || is_removable || can_mount);
359 }
360 
361 static gboolean
pbvol_is_present(GVolume * volume)362 pbvol_is_present(GVolume *volume)
363 {
364     gboolean has_media = FALSE;
365     gboolean is_shadowed = FALSE;
366     GDrive *drive;
367     GMount *mount;
368 
369     g_return_val_if_fail(G_IS_VOLUME(volume), FALSE);
370 
371     drive = g_volume_get_drive (volume);
372     if(drive) {
373         has_media = g_drive_has_media(drive);
374         g_object_unref(drive);
375     }
376 
377     mount = g_volume_get_mount(volume);
378     if(mount) {
379         is_shadowed = g_mount_is_shadowed(mount);
380         g_object_unref(mount);
381     }
382 
383     return has_media && !is_shadowed;
384 }
385 
386 static gboolean
pbvol_show_volume(GVolume * volume)387 pbvol_show_volume(GVolume *volume){
388     GMount *mount = g_volume_get_mount(volume);
389     DBG("Volume: %s [mounted=%lu removable=%x present=%x]", g_volume_get_name(volume),
390                                                            (gulong) mount,
391                                                            pbvol_is_removable(volume),
392                                                            pbvol_is_present(volume));
393     if (mount)
394        g_object_unref(mount);
395 
396     return pbvol_is_removable(volume) &&
397            pbvol_is_present(volume);
398 }
399 
400 static void
pbvol_set_changed(PlacesBookmarkGroup * bookmark_group)401 pbvol_set_changed(PlacesBookmarkGroup *bookmark_group)
402 {
403     DBG("-");
404     pbg_priv(bookmark_group)->changed = TRUE;
405 }
406 
407 
408 static void
pbvol_volume_added(GVolumeMonitor * monitor,GVolume * volume,PlacesBookmarkGroup * bookmark_group)409 pbvol_volume_added(GVolumeMonitor *monitor, GVolume *volume, PlacesBookmarkGroup *bookmark_group)
410 {
411     DBG("-");
412 
413     pbg_priv(bookmark_group)->changed = TRUE;
414     g_signal_connect_swapped(G_VOLUME(volume), "changed",
415                              G_CALLBACK(pbvol_set_changed), bookmark_group);
416 }
417 
418 static void
pbvol_volume_removed(GVolumeMonitor * monitor,GVolume * volume,PlacesBookmarkGroup * bookmark_group)419 pbvol_volume_removed(GVolumeMonitor *monitor, GVolume *volume, PlacesBookmarkGroup *bookmark_group)
420 {
421     DBG("-");
422 
423     pbg_priv(bookmark_group)->changed = TRUE;
424     g_signal_handlers_disconnect_by_func(G_VOLUME(volume),
425                                          G_CALLBACK(pbvol_set_changed), bookmark_group);
426 }
427 
428 static void
pbvol_bookmark_finalize(PlacesBookmark * bookmark)429 pbvol_bookmark_finalize(PlacesBookmark *bookmark)
430 {
431     if(bookmark->uri != NULL){
432         g_free(bookmark->uri);
433         bookmark->uri = NULL;
434     }
435 }
436 
437 static void
pbvol_bookmark_action_finalize(PlacesBookmarkAction * action)438 pbvol_bookmark_action_finalize(PlacesBookmarkAction *action){
439     GVolume *volume;
440 
441     g_assert(action != NULL && action->priv != NULL);
442 
443     volume = G_VOLUME(action->priv);
444     g_object_unref(volume);
445     action->priv = NULL;
446 }
447 
448 static GList*
pbvol_get_bookmarks(PlacesBookmarkGroup * bookmark_group)449 pbvol_get_bookmarks(PlacesBookmarkGroup *bookmark_group)
450 {
451     GList *bookmarks = NULL;
452     PlacesBookmark *bookmark;
453     PlacesBookmarkAction *action, *terminal, *open;
454     const GList *volumes;
455     GVolume *volume;
456     GMount *mount;
457 
458     volumes = g_volume_monitor_get_volumes(pbg_priv(bookmark_group)->volume_monitor);
459     while (volumes != NULL) {
460         volume = volumes->data;
461         mount = g_volume_get_mount(volume);
462 
463         if(pbvol_show_volume(volume)){
464 
465             bookmark            = places_bookmark_create((gchar*) g_volume_get_name(volume));
466             if (mount) {
467                 GFile *file = g_mount_get_root(mount);
468                 bookmark->uri   = g_file_get_uri(file);
469                 g_object_unref(file);
470             } else
471                 bookmark->uri   = NULL;
472             bookmark->icon      = g_volume_get_icon(volume);
473             bookmark->finalize  = pbvol_bookmark_finalize;
474 
475             if (!mount) {
476 
477                 g_object_ref(volume);
478                 action              = places_bookmark_action_create(_("Mount and Open"));
479                 action->may_block   = TRUE;
480                 action->priv        = volume;
481                 action->action      = pbvol_mount_and_open;
482                 action->finalize    = pbvol_bookmark_action_finalize;
483                 bookmark->actions   = g_list_append(bookmark->actions, action);
484 
485                 if(pbg_priv(bookmark_group)->mount_and_open_by_default){
486                     bookmark->primary_action = action;
487                     bookmark->force_gray = TRUE;
488                 }
489 
490                 g_object_ref(volume);
491                 action              = places_bookmark_action_create(_("Mount"));
492                 action->may_block   = TRUE;
493                 action->priv        = volume;
494                 action->action      = pbvol_mount;
495                 action->finalize    = pbvol_bookmark_action_finalize;
496                 bookmark->actions   = g_list_append(bookmark->actions, action);
497 
498             }else{
499 
500                 terminal                 = places_create_open_terminal_action(bookmark);
501                 bookmark->actions        = g_list_prepend(bookmark->actions, terminal);
502                 open                     = places_create_open_action(bookmark);
503                 bookmark->actions        = g_list_prepend(bookmark->actions, open);
504                 bookmark->primary_action = open;
505 
506             }
507 
508             if (g_volume_can_eject(volume)) {
509 
510                 g_object_ref(volume);
511                 action              = places_bookmark_action_create(_("Eject"));
512                 action->may_block   = TRUE;
513                 action->priv        = volume;
514                 action->action      = pbvol_eject;
515                 action->finalize    = pbvol_bookmark_action_finalize;
516                 bookmark->actions   = g_list_append(bookmark->actions, action);
517 
518             }
519             if (mount) {
520                 g_object_ref(volume);
521                 action              = places_bookmark_action_create(_("Unmount"));
522                 action->may_block   = TRUE;
523                 action->priv        = volume;
524                 action->action      = pbvol_unmount;
525                 action->finalize    = pbvol_bookmark_action_finalize;
526                 bookmark->actions   = g_list_append(bookmark->actions, action);
527             }
528 
529             bookmarks = g_list_prepend(bookmarks, bookmark);
530         }
531 
532         volumes = volumes->next;
533     }
534 
535     pbg_priv(bookmark_group)->changed = FALSE;
536 
537     return g_list_reverse(bookmarks);
538 
539 }
540 
541 static gboolean
pbvol_changed(PlacesBookmarkGroup * bookmark_group)542 pbvol_changed(PlacesBookmarkGroup *bookmark_group)
543 {
544     return pbg_priv(bookmark_group)->changed;
545 }
546 
547 static void
pbvol_finalize(PlacesBookmarkGroup * bookmark_group)548 pbvol_finalize(PlacesBookmarkGroup *bookmark_group)
549 {
550     const GList *volumes;
551 
552     volumes = g_volume_monitor_get_volumes(pbg_priv(bookmark_group)->volume_monitor);
553     while(volumes != NULL){
554         g_signal_handlers_disconnect_by_func(G_VOLUME(volumes->data),
555                                              G_CALLBACK(pbvol_set_changed), bookmark_group);
556         volumes = volumes->next;
557     }
558 
559     g_signal_handlers_disconnect_by_func(pbg_priv(bookmark_group)->volume_monitor,
560                                          G_CALLBACK(pbvol_volume_added), bookmark_group);
561     g_signal_handlers_disconnect_by_func(pbg_priv(bookmark_group)->volume_monitor,
562                                          G_CALLBACK(pbvol_volume_removed), bookmark_group);
563 
564     g_object_unref(pbg_priv(bookmark_group)->volume_monitor);
565     pbg_priv(bookmark_group)->volume_monitor = NULL;
566 
567     g_free(pbg_priv(bookmark_group));
568     bookmark_group->priv = NULL;
569 }
570 
571 PlacesBookmarkGroup*
places_bookmarks_volumes_create(gboolean mount_and_open_by_default)572 places_bookmarks_volumes_create(gboolean mount_and_open_by_default)
573 {
574     GList *volumes;
575     PlacesBookmarkGroup *bookmark_group;
576 
577     bookmark_group                      = places_bookmark_group_create();
578     bookmark_group->get_bookmarks       = pbvol_get_bookmarks;
579     bookmark_group->changed             = pbvol_changed;
580     bookmark_group->finalize            = pbvol_finalize;
581     bookmark_group->priv                = g_new0(PBVolData, 1);
582 
583     pbg_priv(bookmark_group)->volume_monitor = g_volume_monitor_get();
584     pbg_priv(bookmark_group)->changed        = TRUE;
585     pbg_priv(bookmark_group)->mount_and_open_by_default = mount_and_open_by_default;
586 
587     volumes = g_volume_monitor_get_volumes(pbg_priv(bookmark_group)->volume_monitor);
588     while(volumes != NULL) {
589         g_signal_connect_swapped(G_OBJECT(volumes->data), "changed",
590                                  G_CALLBACK(pbvol_set_changed), bookmark_group);
591         g_object_unref(volumes->data);
592         volumes = volumes->next;
593     }
594     g_list_free(volumes);
595 
596     g_signal_connect(pbg_priv(bookmark_group)->volume_monitor, "volume-added",
597                      G_CALLBACK(pbvol_volume_added), bookmark_group);
598 
599     g_signal_connect(pbg_priv(bookmark_group)->volume_monitor, "volume-removed",
600                      G_CALLBACK(pbvol_volume_removed), bookmark_group);
601 
602     return bookmark_group;
603 }
604 
605 /* vim: set ai et tabstop=4: */
606