1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* gvfs - extensions for gio
3  *
4  * Copyright (C) 2012, 2013 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General
17  * Public License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  * Author: Debarshi Ray <debarshir@gnome.org>
22  */
23 
24 #include <config.h>
25 
26 #include <stdio.h>
27 
28 #include <glib.h>
29 #include <gio/gio.h>
30 
31 #define GOA_API_IS_SUBJECT_TO_CHANGE
32 #include <goa/goa.h>
33 
34 #include <gvfsproxyvolumemonitordaemon.h>
35 #include "goavolume.h"
36 #include "goavolumemonitor.h"
37 
38 struct _GVfsGoaVolumeMonitor
39 {
40   GNativeVolumeMonitor parent;
41   GList *accounts;
42   GList *volumes;
43   GoaClient *client;
44 };
45 
46 struct _GVfsGoaVolumeMonitorClass
47 {
48   GVolumeMonitorClass parent_class;
49 };
50 
G_DEFINE_TYPE(GVfsGoaVolumeMonitor,g_vfs_goa_volume_monitor,G_TYPE_VOLUME_MONITOR)51 G_DEFINE_TYPE(GVfsGoaVolumeMonitor, g_vfs_goa_volume_monitor, G_TYPE_VOLUME_MONITOR)
52 
53 /* ---------------------------------------------------------------------------------------------------- */
54 
55 static void
56 diff_sorted_lists (GList         *list1,
57                    GList         *list2,
58                    GCompareFunc   compare,
59                    GList        **added,
60                    GList        **removed,
61                    GList        **unchanged)
62 {
63   int order;
64 
65   *added = *removed = NULL;
66   if (unchanged != NULL)
67     *unchanged = NULL;
68 
69   while (list1 != NULL &&
70          list2 != NULL)
71     {
72       order = (*compare) (list1->data, list2->data);
73       if (order < 0)
74         {
75           *removed = g_list_prepend (*removed, list1->data);
76           list1 = list1->next;
77         }
78       else if (order > 0)
79         {
80           *added = g_list_prepend (*added, list2->data);
81           list2 = list2->next;
82         }
83       else
84         { /* same item */
85           if (unchanged != NULL)
86             *unchanged = g_list_prepend (*unchanged, list1->data);
87           list1 = list1->next;
88           list2 = list2->next;
89         }
90     }
91 
92   while (list1 != NULL)
93     {
94       *removed = g_list_prepend (*removed, list1->data);
95       list1 = list1->next;
96     }
97   while (list2 != NULL)
98     {
99       *added = g_list_prepend (*added, list2->data);
100       list2 = list2->next;
101     }
102 }
103 
104 /* ---------------------------------------------------------------------------------------------------- */
105 
106 static gint
account_compare(GoaObject * a,GoaObject * b)107 account_compare (GoaObject *a, GoaObject *b)
108 {
109   GoaAccount *account_a;
110   GoaAccount *account_b;
111 
112   account_a = goa_object_peek_account (a);
113   account_b = goa_object_peek_account (b);
114 
115   return g_strcmp0 (goa_account_get_id (account_a), goa_account_get_id (account_b));
116 }
117 
118 /* ---------------------------------------------------------------------------------------------------- */
119 
120 static gint
uuid_compare(GVolume * volume_a,const gchar * uuid_b)121 uuid_compare (GVolume *volume_a, const gchar *uuid_b)
122 {
123   gchar *uuid_a;
124   gint ret_val;
125 
126   uuid_a = g_volume_get_uuid (volume_a);
127   ret_val = g_strcmp0 (uuid_a, uuid_b);
128   g_free (uuid_a);
129 
130   return ret_val;
131 }
132 
133 /* ---------------------------------------------------------------------------------------------------- */
134 
135 static gint
volume_compare(GVolume * a,GVolume * b)136 volume_compare (GVolume *a, GVolume *b)
137 {
138   gchar *uuid_a;
139   gchar *uuid_b;
140   gint ret_val;
141 
142   uuid_a = g_volume_get_uuid (a);
143   uuid_b = g_volume_get_uuid (b);
144 
145   ret_val = g_strcmp0 (uuid_a, uuid_b);
146 
147   g_free (uuid_a);
148   g_free (uuid_b);
149 
150   return ret_val;
151 }
152 
153 /* ---------------------------------------------------------------------------------------------------- */
154 
155 static void
object_list_emit(GVfsGoaVolumeMonitor * monitor,const gchar * monitor_signal,const gchar * object_signal,GList * objects)156 object_list_emit (GVfsGoaVolumeMonitor *monitor,
157                   const gchar          *monitor_signal,
158                   const gchar          *object_signal,
159                   GList                *objects)
160 {
161   GList *l;
162 
163   for (l = objects; l != NULL; l = l->next)
164     {
165       g_signal_emit_by_name (monitor, monitor_signal, l->data);
166       if (object_signal)
167         g_signal_emit_by_name (l->data, object_signal);
168     }
169 }
170 
171 /* ---------------------------------------------------------------------------------------------------- */
172 
173 static void
update_accounts(GVfsGoaVolumeMonitor * self,GList ** added_accounts,GList ** removed_accounts)174 update_accounts (GVfsGoaVolumeMonitor *self, GList **added_accounts, GList **removed_accounts)
175 {
176   GList *added;
177   GList *l;
178   GList *new_accounts;
179   GList *new_files_accounts = NULL;
180   GList *removed;
181 
182   if (added_accounts != NULL)
183     *added_accounts = NULL;
184 
185   if (removed_accounts != NULL)
186     *removed_accounts = NULL;
187 
188   new_accounts = goa_client_get_accounts (self->client);
189   for (l = new_accounts; l != NULL; l = l->next)
190     {
191       GoaObject *object = GOA_OBJECT (l->data);
192 
193       if (goa_object_peek_files (object) != NULL)
194         new_files_accounts = g_list_prepend (new_files_accounts, object);
195     }
196 
197   new_files_accounts = g_list_sort (new_files_accounts, (GCompareFunc) account_compare);
198   diff_sorted_lists (self->accounts,
199                      new_files_accounts,
200                      (GCompareFunc) account_compare,
201                      &added,
202                      &removed,
203                      NULL);
204 
205   for (l = removed; l != NULL; l = l->next)
206     {
207       GList *llink;
208       GoaObject *object = GOA_OBJECT (l->data);
209 
210       if (removed_accounts != NULL)
211         *removed_accounts = g_list_prepend (*removed_accounts, g_object_ref (object));
212 
213       llink = g_list_find_custom (self->accounts, object, (GCompareFunc) account_compare);
214       self->accounts = g_list_remove_link (self->accounts, llink);
215       g_list_free_full (llink, g_object_unref);
216     }
217 
218   for (l = added; l != NULL; l = l->next)
219     {
220       GoaObject *object = GOA_OBJECT (l->data);
221 
222       self->accounts = g_list_prepend (self->accounts, g_object_ref (object));
223 
224       if (added_accounts != NULL)
225         *added_accounts = g_list_prepend (*added_accounts, g_object_ref (object));
226     }
227 
228   self->accounts = g_list_sort (self->accounts, (GCompareFunc) account_compare);
229 
230   g_list_free (added);
231   g_list_free (removed);
232   g_list_free (new_files_accounts);
233   g_list_free_full (new_accounts, g_object_unref);
234 }
235 
236 /* ---------------------------------------------------------------------------------------------------- */
237 
238 static void
update_volumes(GVfsGoaVolumeMonitor * self,GList ** added_volumes,GList ** removed_volumes)239 update_volumes (GVfsGoaVolumeMonitor *self, GList **added_volumes, GList **removed_volumes)
240 {
241   GList *added;
242   GList *l;
243   GList *new_volumes = NULL;
244   GList *removed;
245 
246   if (added_volumes != NULL)
247     *added_volumes = NULL;
248 
249   if (removed_volumes != NULL)
250     *removed_volumes = NULL;
251 
252   for (l = self->accounts; l != NULL; l = l->next)
253     {
254       GVolume *volume;
255       GoaFiles *files;
256       GoaObject *object = GOA_OBJECT (l->data);
257       const gchar *uri;
258 
259       files = goa_object_peek_files (object);
260       uri = goa_files_get_uri (files);
261 
262       volume = g_vfs_goa_volume_new (object, uri);
263       new_volumes = g_list_prepend (new_volumes, volume);
264     }
265 
266   new_volumes = g_list_sort (new_volumes, (GCompareFunc) volume_compare);
267   diff_sorted_lists (self->volumes,
268                      new_volumes,
269                      (GCompareFunc) volume_compare,
270                      &added,
271                      &removed,
272                      NULL);
273 
274   for (l = removed; l != NULL; l = l->next)
275     {
276       GList *llink;
277       GVolume *volume = G_VOLUME (l->data);
278 
279       if (removed_volumes != NULL)
280         *removed_volumes = g_list_prepend (*removed_volumes, g_object_ref (volume));
281 
282       llink = g_list_find_custom (self->volumes, volume, (GCompareFunc) volume_compare);
283       self->volumes = g_list_remove_link (self->volumes, llink);
284       g_list_free_full (llink, g_object_unref);
285     }
286 
287   for (l = added; l != NULL; l = l->next)
288     {
289       GVolume *volume = G_VOLUME (l->data);
290 
291       self->volumes = g_list_prepend (self->volumes, g_object_ref (volume));
292 
293       if (added_volumes != NULL)
294         *added_volumes = g_list_prepend (*added_volumes, g_object_ref (volume));
295     }
296 
297   self->volumes = g_list_sort (self->volumes, (GCompareFunc) volume_compare);
298 
299   g_list_free (added);
300   g_list_free (removed);
301   g_list_free_full (new_volumes, g_object_unref);
302 }
303 
304 /* ---------------------------------------------------------------------------------------------------- */
305 
306 static void
update_all(GVfsGoaVolumeMonitor * self)307 update_all (GVfsGoaVolumeMonitor *self)
308 {
309   GList *added_volumes;
310   GList *removed_volumes;
311 
312   update_accounts (self, NULL, NULL);
313   update_volumes (self, &added_volumes, &removed_volumes);
314 
315   object_list_emit (self, "volume-removed", "removed", removed_volumes);
316   object_list_emit (self, "volume-added", NULL, added_volumes);
317 
318   g_list_free_full (added_volumes, g_object_unref);
319   g_list_free_full (removed_volumes, g_object_unref);
320 }
321 
322 /* ---------------------------------------------------------------------------------------------------- */
323 
324 static GoaClient *
get_goa_client_sync(GError ** error)325 get_goa_client_sync (GError **error)
326 {
327   static GoaClient *client = NULL;
328   static GError *_error = NULL;
329   static volatile gsize initialized = 0;
330 
331   if (g_once_init_enter (&initialized))
332     {
333       client = goa_client_new_sync (NULL, &_error);
334       g_once_init_leave (&initialized, 1);
335     }
336 
337   if (_error != NULL && error != NULL)
338     *error = g_error_copy (_error);
339 
340   return client;
341 }
342 
343 /* ---------------------------------------------------------------------------------------------------- */
344 
345 static GList *
g_vfs_goa_volume_monitor_get_connected_drives(GVolumeMonitor * _self)346 g_vfs_goa_volume_monitor_get_connected_drives (GVolumeMonitor *_self)
347 {
348   return NULL;
349 }
350 
351 static GMount *
g_vfs_goa_volume_monitor_get_mount_for_uuid(GVolumeMonitor * _self,const gchar * uuid)352 g_vfs_goa_volume_monitor_get_mount_for_uuid (GVolumeMonitor *_self, const gchar *uuid)
353 {
354   GMount *mount;
355   GVolume *volume;
356 
357   mount = NULL;
358 
359   volume = g_volume_monitor_get_volume_for_uuid (_self, uuid);
360   if (volume != NULL)
361     mount = g_volume_get_mount (G_VOLUME (volume));
362 
363   return mount;
364 }
365 
366 static GList *
g_vfs_goa_volume_monitor_get_mounts(GVolumeMonitor * _self)367 g_vfs_goa_volume_monitor_get_mounts (GVolumeMonitor *_self)
368 {
369   GVfsGoaVolumeMonitor *self = G_VFS_GOA_VOLUME_MONITOR (_self);
370   GList *l;
371   GList *mounts = NULL;
372 
373   for (l = self->volumes; l != NULL; l = l->next)
374     {
375       GMount *mount;
376       GVolume *volume = G_VOLUME (l->data);
377 
378       mount = g_volume_get_mount (volume);
379       if (mount != NULL)
380         mounts = g_list_prepend (mounts, mount);
381     }
382 
383   return mounts;
384 }
385 
386 static GVolume *
g_vfs_goa_volume_monitor_get_volume_for_uuid(GVolumeMonitor * _self,const gchar * uuid)387 g_vfs_goa_volume_monitor_get_volume_for_uuid (GVolumeMonitor *_self, const gchar *uuid)
388 {
389   GVfsGoaVolumeMonitor *self = G_VFS_GOA_VOLUME_MONITOR (_self);
390   GList *llink;
391   GVolume *volume = NULL;
392 
393   llink = g_list_find_custom (self->volumes, uuid, (GCompareFunc) uuid_compare);
394   if (llink != NULL)
395     volume = G_VOLUME (g_object_ref (llink->data));
396 
397   return volume;
398 }
399 
400 static GList *
g_vfs_goa_volume_monitor_get_volumes(GVolumeMonitor * _self)401 g_vfs_goa_volume_monitor_get_volumes (GVolumeMonitor *_self)
402 {
403   GVfsGoaVolumeMonitor *self = G_VFS_GOA_VOLUME_MONITOR (_self);
404 
405   return g_list_copy_deep (self->volumes, (GCopyFunc) g_object_ref, NULL);
406 }
407 
408 static gboolean
g_vfs_goa_volume_monitor_is_supported(void)409 g_vfs_goa_volume_monitor_is_supported (void)
410 {
411   if (get_goa_client_sync (NULL) != NULL)
412     return TRUE;
413   return FALSE;
414 }
415 
416 static void
g_vfs_goa_volume_monitor_dispose(GObject * _self)417 g_vfs_goa_volume_monitor_dispose (GObject *_self)
418 {
419   GVfsGoaVolumeMonitor *self = G_VFS_GOA_VOLUME_MONITOR (_self);
420 
421   g_list_free_full (self->accounts, g_object_unref);
422   self->accounts = NULL;
423 
424   g_list_free_full (self->volumes, g_object_unref);
425   self->volumes = NULL;
426 
427   g_clear_object (&self->client);
428 
429   G_OBJECT_CLASS (g_vfs_goa_volume_monitor_parent_class)->dispose (_self);
430 }
431 
432 static void
g_vfs_goa_volume_monitor_class_init(GVfsGoaVolumeMonitorClass * klass)433 g_vfs_goa_volume_monitor_class_init (GVfsGoaVolumeMonitorClass * klass)
434 {
435   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
436   GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass);
437 
438   gobject_class->dispose = g_vfs_goa_volume_monitor_dispose;
439 
440   monitor_class->get_connected_drives = g_vfs_goa_volume_monitor_get_connected_drives;
441   monitor_class->get_mount_for_uuid = g_vfs_goa_volume_monitor_get_mount_for_uuid;
442   monitor_class->get_mounts = g_vfs_goa_volume_monitor_get_mounts;
443   monitor_class->get_volume_for_uuid = g_vfs_goa_volume_monitor_get_volume_for_uuid;
444   monitor_class->get_volumes = g_vfs_goa_volume_monitor_get_volumes;
445   monitor_class->is_supported = g_vfs_goa_volume_monitor_is_supported;
446 }
447 
448 static void
g_vfs_goa_volume_monitor_init(GVfsGoaVolumeMonitor * self)449 g_vfs_goa_volume_monitor_init (GVfsGoaVolumeMonitor *self)
450 {
451   GError *error;
452 
453   error = NULL;
454   self->client = get_goa_client_sync (&error);
455   if (self->client == NULL)
456     {
457       g_warning ("Failed to connect to GOA: %s", error->message);
458       g_error_free (error);
459       return;
460     }
461 
462   update_all (self);
463 
464   g_signal_connect_swapped (self->client, "account-added", G_CALLBACK (update_all), self);
465   g_signal_connect_swapped (self->client, "account-changed", G_CALLBACK (update_all), self);
466   g_signal_connect_swapped (self->client, "account-removed", G_CALLBACK (update_all), self);
467 
468   g_vfs_proxy_volume_monitor_daemon_set_always_call_mount (TRUE);
469 }
470 
471 GVolumeMonitor *
g_vfs_goa_volume_monitor_new(void)472 g_vfs_goa_volume_monitor_new (void)
473 {
474   return g_object_new (G_VFS_TYPE_GOA_VOLUME_MONITOR, NULL);
475 }
476