No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2011 Jonathan Matthew <jonathan@d14n.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 *
27 */
28
29 #include <config.h>
30
31 #include <gio/gio.h>
32 #include <glib/gi18n.h>
33
34 #include "rb-device-source.h"
35 #include "rb-source.h"
36 #include "rb-debug.h"
37 #include "rb-dialog.h"
38
39 G_DEFINE_INTERFACE (RBDeviceSource, rb_device_source, 0)
40
41 /**
42 * SECTION:rb-device-source
43 * @short_description: interface for sources based on physical devices
44 * @include: rb-device-source.h
45 *
46 * Sources that represent physical devices should implement this interface.
47 * It exposes the ability to eject the device, and also can be used to
48 * implement some #RBSource methods by using details from a #GVolume or
49 * #GMount accessed via 'volume' and 'mount' properties on the source object.
50 * Devices that are not based on a #GVolume or #GMount can still use the
51 * interface, but they must provide implementations of the @can_eject and
52 * @eject methods.
53 */
54
55 static gboolean
56 default_can_eject (RBDeviceSource *source)
57 {
58 gboolean result = FALSE;
59 GVolume *volume = NULL;
60 GMount *mount = NULL;
61
62 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "volume")) {
63 g_object_get (source, "volume", &volume, NULL);
64 }
65 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "mount")) {
66 g_object_get (source, "mount", &mount, NULL);
67 }
68
69 if (volume != NULL) {
70 result = g_volume_can_eject (volume);
71
72 g_object_unref (volume);
73 if (mount != NULL) {
74 g_object_unref (mount);
75 }
76 } else if (mount != NULL) {
77 result = g_mount_can_eject (mount) || g_mount_can_unmount (mount);
78
79 if (mount != NULL) {
80 g_object_unref (mount);
81 }
82 }
83
84 return result;
85 }
86
87 static void
88 eject_cb (GObject *object, GAsyncResult *result, gpointer nothing)
89 {
90 GError *error = NULL;
91
92 if (G_IS_VOLUME (object)) {
93 GVolume *volume = G_VOLUME (object);
94
95 rb_debug ("finishing ejection of volume");
96 g_volume_eject_with_operation_finish (volume, result, &error);
97 } else if (G_IS_MOUNT (object)) {
98 GMount *mount = G_MOUNT (object);
99
100 rb_debug ("finishing ejection of mount");
101 g_mount_eject_with_operation_finish (mount, result, &error);
102 }
103
104 if (error != NULL) {
105 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED)) {
106 rb_error_dialog (NULL, _("Unable to eject"), "%s", error->message);
107 } else {
108 rb_debug ("eject failure has already been handled");
109 }
110 g_error_free (error);
111 }
112 }
113
114 static void
115 unmount_cb (GObject *object, GAsyncResult *result, gpointer nothing)
116 {
117 GMount *mount = G_MOUNT (object);
118 GError *error = NULL;
119
120 rb_debug ("finishing unmount of mount");
121 g_mount_unmount_with_operation_finish (mount, result, &error);
122 if (error != NULL) {
123 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED)) {
124 rb_error_dialog (NULL, _("Unable to unmount"), "%s", error->message);
125 } else {
126 rb_debug ("unmount failure has already been handled");
127 }
128 g_error_free (error);
129 }
130 }
131
132 static void
133 default_eject (RBDeviceSource *source)
134 {
135 GVolume *volume = NULL;
136 GMount *mount = NULL;
137
138 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "volume")) {
139 g_object_get (source, "volume", &volume, NULL);
140 }
141 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "mount")) {
142 g_object_get (source, "mount", &mount, NULL);
143 }
144
145 /* try ejecting based on volume first, then based on the mount,
146 * and finally try unmounting.
147 */
148 if (volume != NULL) {
149 if (g_volume_can_eject (volume)) {
150 rb_debug ("ejecting volume");
151 g_volume_eject_with_operation (volume,
152 G_MOUNT_UNMOUNT_NONE,
153 NULL,
154 NULL,
155 (GAsyncReadyCallback) eject_cb,
156 NULL);
157 } else {
158 /* this should never happen; the eject command will be
159 * insensitive if the selected source cannot be ejected.
160 */
161 rb_debug ("don't know what to do with this volume");
162 }
163 } else if (mount != NULL) {
164 if (g_mount_can_eject (mount)) {
165 rb_debug ("ejecting mount");
166 g_mount_eject_with_operation (mount,
167 G_MOUNT_UNMOUNT_NONE,
168 NULL,
169 NULL,
170 (GAsyncReadyCallback) eject_cb,
171 NULL);
172 } else if (g_mount_can_unmount (mount)) {
173 rb_debug ("unmounting mount");
174 g_mount_unmount_with_operation (mount,
175 G_MOUNT_UNMOUNT_NONE,
176 NULL,
177 NULL,
178 (GAsyncReadyCallback) unmount_cb,
179 NULL);
180 } else {
181 /* this should never happen; the eject command will be
182 * insensitive if the selected source cannot be ejected.
183 */
184 rb_debug ("don't know what to do with this mount");
185 }
186 }
187
188 if (volume != NULL) {
189 g_object_unref (volume);
190 }
191 if (mount != NULL) {
192 g_object_unref (mount);
193 }
194 }
195
196 /**
197 * rb_device_can_eject:
198 * @source: a #RBDeviceSource
199 *
200 * Checks if the device that the source represents can be ejected.
201 *
202 * Return value: %TRUE if the device can be ejected
203 */
204 gboolean
205 rb_device_source_can_eject (RBDeviceSource *source)
206 {
207 RBDeviceSourceInterface *iface = RB_DEVICE_SOURCE_GET_IFACE (source);
208 return iface->can_eject (source);
209 }
210
211 /**
212 * rb_device_source_eject:
213 * @source: a #RBDeviceSource
214 *
215 * Ejects the device that the source represents.
216 */
217 void
218 rb_device_source_eject (RBDeviceSource *source)
219 {
220 RBDeviceSourceInterface *iface = RB_DEVICE_SOURCE_GET_IFACE (source);
221 iface->eject (source);
222 }
223
224 /**
225 * rb_device_source_want_uri:
226 * @source: a #RBDeviceSource
227 * @uri: a URI to consider
228 *
229 * Checks whether @uri identifies a path underneath the
230 * device's mount point. Should be used to implement
231 * the #RBSource impl_want_uri method.
232 *
233 * Return value: URI match strength
234 */
235 guint
236 rb_device_source_want_uri (RBSource *source, const char *uri)
237 {
238 GMount *mount = NULL;
239 GVolume *volume = NULL;
240 GFile *file;
241 char *device_path, *uri_path;
242 int retval;
243 int len;
244
245 retval = 0;
246
247 file = g_file_new_for_uri (uri);
248
249 /* Deal with the mount root being passed, eg. file:///media/IPODNAME */
250 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "mount")) {
251 g_object_get (source, "mount", &mount, NULL);
252 }
253 if (mount != NULL) {
254 GFile *root;
255
256 root = g_mount_get_root (mount);
257 retval = g_file_equal (root, file) ? 100 : 0;
258 g_object_unref (root);
259 if (retval) {
260 g_object_unref (file);
261 g_object_unref (mount);
262 return retval;
263 }
264 volume = g_mount_get_volume (mount);
265 g_object_unref (mount);
266 } else {
267 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "volume")) {
268 g_object_get (source, "volume", &volume, NULL);
269 }
270 }
271
272 /* ignore anything that isn't a local file or doesn't have a volume */
273 if (g_file_has_uri_scheme (file, "file") == FALSE || volume == NULL) {
274 g_object_unref (file);
275 return 0;
276 }
277
278 /* Deal with the path to the device node being passed */
279 device_path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
280 g_object_unref (volume);
281 if (device_path == NULL) {
282 g_object_unref (file);
283 return 0;
284 }
285
286 uri_path = g_file_get_path (file);
287 g_object_unref (file);
288 if (uri_path == NULL)
289 return 0;
290 len = strlen (uri_path);
291 if (uri_path[len - 1] == '/') {
292 if (strncmp (uri_path, device_path, len - 1) == 0) {
293 retval = 100;
294 }
295 } else if (strcmp (uri_path, device_path) == 0) {
296 retval = 100;
297 }
298
299 g_free (device_path);
300 g_free (uri_path);
301 return retval;
302 }
303
304 /**
305 * rb_device_source_uri_is_source:
306 * @source: a #RBDeviceSource
307 * @uri: a URI to check
308 *
309 * Returns %TRUE if @uri matches @source. This should be
310 * used to implement the impl_uri_is_source #RBSource method.
311 *
312 * Return value: %TRUE if @uri matches @source
313 */
314 gboolean
315 rb_device_source_uri_is_source (RBSource *source, const char *uri)
316 {
317 return (rb_device_source_want_uri (source, uri) == 100);
318 }
319
320 /**
321 * rb_device_source_set_display_details:
322 * @source: a #RBDeviceSource
323 *
324 * Sets the icon and display name for a device-based source.
325 * The details come from the mount and/or volume. This should
326 * be called in the source's constructed method.
327 */
328 void
329 rb_device_source_set_display_details (RBDeviceSource *source)
330 {
331 GMount *mount = NULL;
332 GVolume *volume = NULL;
333 GIcon *icon = NULL;
334 char *display_name;
335 GdkPixbuf *pixbuf = NULL;
336
337 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "volume")) {
338 g_object_get (source, "volume", &volume, NULL);
339 }
340 if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "mount")) {
341 g_object_get (source, "mount", &mount, NULL);
342 }
343
344 /* prefer mount details to volume details, as the nautilus sidebar does */
345 if (mount != NULL) {
346 mount = g_object_ref (mount);
347 } else if (volume != NULL) {
348 mount = g_volume_get_mount (volume);
349 } else {
350 mount = NULL;
351 }
352
353 if (mount != NULL) {
354 display_name = g_mount_get_name (mount);
355 icon = g_mount_get_icon (mount);
356 rb_debug ("details from mount: display name = %s, icon = %p", display_name, icon);
357 } else if (volume != NULL) {
358 display_name = g_volume_get_name (volume);
359 icon = g_volume_get_icon (volume);
360 rb_debug ("details from volume: display name = %s, icon = %p", display_name, icon);
361 } else {
362 display_name = g_strdup ("Unknown Device");
363 icon = g_themed_icon_new ("multimedia-player");
364 }
365
366 g_object_set (source, "name", display_name, NULL);
367 g_free (display_name);
368
369 if (icon == NULL) {
370 rb_debug ("no icon set");
371 pixbuf = NULL;
372 } else if (G_IS_THEMED_ICON (icon)) {
373 GtkIconTheme *theme;
374 const char * const *names;
375 gint size;
376 int i;
377
378 theme = gtk_icon_theme_get_default ();
379 gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
380
381 i = 0;
382 names = g_themed_icon_get_names (G_THEMED_ICON (icon));
383 while (names[i] != NULL && pixbuf == NULL) {
384 rb_debug ("looking up themed icon: %s", names[i]);
385 pixbuf = gtk_icon_theme_load_icon (theme, names[i], size, 0, NULL);
386 i++;
387 }
388
389 } else if (G_IS_LOADABLE_ICON (icon)) {
390 rb_debug ("loading of GLoadableIcons is not implemented yet");
391 pixbuf = NULL;
392 }
393
394 if (pixbuf != NULL) {
395 g_object_set (source, "pixbuf", pixbuf, NULL);
396 g_object_unref (pixbuf);
397 }
398 if (mount != NULL) {
399 g_object_unref (mount);
400 }
401 if (volume != NULL) {
402 g_object_unref (volume);
403 }
404 if (icon != NULL) {
405 g_object_unref (icon);
406 }
407 }
408
409 static void
410 rb_device_source_default_init (RBDeviceSourceInterface *interface)
411 {
412 interface->can_eject = default_can_eject;
413 interface->eject = default_eject;
414 }