1 /*************************************************************************/
2 /* Copyright (C) 2009-2014 matias <mati86dl@gmail.com>                   */
3 /*                                                                       */
4 /* This program is free software: you can redistribute it and/or modify  */
5 /* it under the terms of the GNU General Public License as published by  */
6 /* the Free Software Foundation, either version 3 of the License, or     */
7 /* (at your option) any later version.                                   */
8 /*                                                                       */
9 /* This program is distributed in the hope that it will be useful,       */
10 /* but WITHOUT ANY WARRANTY; without even the implied warranty of        */
11 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         */
12 /* GNU General Public License for more details.                          */
13 /*                                                                       */
14 /* You should have received a copy of the GNU General Public License     */
15 /* along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16 /*************************************************************************/
17 
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
21 
22 #if defined(GETTEXT_PACKAGE)
23 #include <glib/gi18n-lib.h>
24 #else
25 #include <glib/gi18n.h>
26 #endif
27 #include <glib.h>
28 #include <glib-object.h>
29 #include <gmodule.h>
30 #include <gtk/gtk.h>
31 
32 #include <gudev/gudev.h>
33 
34 #include <libpeas/peas.h>
35 #include <libpeas-gtk/peas-gtk.h>
36 
37 #include "plugins/pragha-plugin-macros.h"
38 
39 #include "plugins/devices/pragha-devices-plugin.h"
40 #include "plugins/devices/pragha-device-client.h"
41 
42 #include "src/pragha-playback.h"
43 #include "src/pragha-utils.h"
44 #include "src/pragha.h"
45 
46 #define PRAGHA_TYPE_REMOVABLE_PLUGIN         (pragha_removable_plugin_get_type ())
47 #define PRAGHA_REMOVABLE_PLUGIN(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), PRAGHA_TYPE_REMOVABLE_PLUGIN, PraghaRemovablePlugin))
48 #define PRAGHA_REMOVABLE_PLUGIN_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), PRAGHA_TYPE_REMOVABLE_PLUGIN, PraghaRemovablePlugin))
49 #define PRAGHA_IS_REMOVABLE_PLUGIN(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), PRAGHA_TYPE_REMOVABLE_PLUGIN))
50 #define PRAGHA_IS_REMOVABLE_PLUGIN_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), PRAGHA_TYPE_REMOVABLE_PLUGIN))
51 #define PRAGHA_REMOVABLE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), PRAGHA_TYPE_REMOVABLE_PLUGIN, PraghaRemovablePluginClass))
52 
53 typedef struct _PraghaRemovablePluginPrivate PraghaRemovablePluginPrivate;
54 
55 struct _PraghaRemovablePluginPrivate {
56 	PraghaApplication  *pragha;
57 
58 	/* Gudev devie */
59 	guint64             bus_hooked;
60 	guint64             device_hooked;
61 	GUdevDevice        *u_device;
62 
63 	/* Gio Volume */
64 	GVolume            *volume;
65 
66 	/* Mount point. */
67 	gchar              *mount_path;
68 };
69 
PRAGHA_PLUGIN_REGISTER(PRAGHA_TYPE_REMOVABLE_PLUGIN,PraghaRemovablePlugin,pragha_removable_plugin)70 PRAGHA_PLUGIN_REGISTER (PRAGHA_TYPE_REMOVABLE_PLUGIN,
71                         PraghaRemovablePlugin,
72                         pragha_removable_plugin)
73 
74 static void
75 pragha_removable_clear_hook_device (PraghaRemovablePlugin *plugin)
76 {
77 	PraghaRemovablePluginPrivate *priv = plugin->priv;
78 
79 	priv->bus_hooked = 0;
80 	priv->device_hooked = 0;
81 
82 	if (priv->u_device) {
83 		g_object_unref (priv->u_device);
84 		priv->u_device = NULL;
85 	}
86 	if (priv->volume) {
87 		g_object_unref (priv->volume);
88 		priv->volume = NULL;
89 	}
90 	if (priv->mount_path) {
91 		g_free(priv->mount_path);
92 		priv->mount_path = NULL;
93 	}
94 }
95 
96 static void
pragha_block_device_add_to_library(PraghaRemovablePlugin * plugin,GMount * mount)97 pragha_block_device_add_to_library (PraghaRemovablePlugin *plugin, GMount *mount)
98 {
99 	PraghaPreferences *preferences;
100 	PraghaScanner *scanner;
101 	GSList *library_dir = NULL;
102 	GFile       *mount_point;
103 	gchar       *mount_path;
104 
105 	PraghaRemovablePluginPrivate *priv = plugin->priv;
106 
107 	mount_point = g_mount_get_root (mount);
108 	mount_path = g_file_get_path (mount_point);
109 
110 	preferences = pragha_application_get_preferences (priv->pragha);
111 
112 	library_dir = pragha_preferences_get_library_list (preferences);
113 	if (!is_present_str_list (mount_path, library_dir)) {
114 		library_dir = g_slist_append (library_dir, g_strdup(mount_path));
115 
116 		pragha_preferences_set_filename_list (preferences,
117 		                                      GROUP_LIBRARY,
118 		                                      KEY_LIBRARY_DIR,
119 		                                      library_dir);
120 	}
121 	priv->mount_path = g_strdup(mount_path);
122 
123 	scanner = pragha_application_get_scanner (priv->pragha);
124 	pragha_scanner_update_library (scanner);
125 
126 	g_object_unref (mount_point);
127 	free_str_list(library_dir);
128 	g_free (mount_path);
129 }
130 
131 static void
pragha_removable_drop_device_from_library(PraghaRemovablePlugin * plugin)132 pragha_removable_drop_device_from_library (PraghaRemovablePlugin *plugin)
133 {
134 	PraghaPreferences *preferences;
135 	PraghaScanner *scanner;
136 	GSList *library_dir = NULL;
137 
138 	PraghaRemovablePluginPrivate *priv = plugin->priv;
139 
140 	preferences = pragha_application_get_preferences (priv->pragha);
141 
142 	library_dir = pragha_preferences_get_library_list (preferences);
143 	if (is_present_str_list (priv->mount_path, library_dir)) {
144 		library_dir = delete_from_str_list (priv->mount_path, library_dir);
145 
146 		pragha_preferences_set_filename_list (preferences,
147 		                                      GROUP_LIBRARY,
148 		                                      KEY_LIBRARY_DIR,
149 		                                      library_dir);
150 
151 		scanner = pragha_application_get_scanner (priv->pragha);
152 		pragha_scanner_update_library (scanner);
153 	}
154 	free_str_list(library_dir);
155 }
156 
157 /*
158  * Some functions to mount block removable.
159  */
160 
161 /* Decode the ID_FS_LABEL_ENC of block device.
162  * Extentions copy of Thunar-volman code.
163  * http://git.xfce.org/xfce/thunar-volman/tree/thunar-volman/tvm-gio-extensions.c */
164 
165 static GVolume *
tvm_g_volume_monitor_get_volume_for_kind(GVolumeMonitor * monitor,const gchar * kind,const gchar * identifier)166 tvm_g_volume_monitor_get_volume_for_kind (GVolumeMonitor *monitor,
167                                           const gchar    *kind,
168                                           const gchar    *identifier)
169 {
170 	GVolume *volume = NULL;
171 	GList   *volumes;
172 	GList   *lp;
173 	gchar   *value;
174 
175 	g_return_val_if_fail (G_IS_VOLUME_MONITOR (monitor), NULL);
176 	g_return_val_if_fail (kind != NULL && *kind != '\0', NULL);
177 	g_return_val_if_fail (identifier != NULL && *identifier != '\0', NULL);
178 
179 	volumes = g_volume_monitor_get_volumes (monitor);
180 
181 	for (lp = volumes; volume == NULL && lp != NULL; lp = lp->next) {
182 		value = g_volume_get_identifier (lp->data, kind);
183 		if (value == NULL)
184 			continue;
185 		if (g_strcmp0 (value, identifier) == 0)
186 			volume = g_object_ref (lp->data);
187 		g_free (value);
188 	}
189 	g_list_foreach (volumes, (GFunc)g_object_unref, NULL);
190 	g_list_free (volumes);
191 
192 	return volume;
193 }
194 
195 static void
pragha_block_device_mount_finish(GVolume * volume,GAsyncResult * result,PraghaRemovablePlugin * plugin)196 pragha_block_device_mount_finish (GVolume *volume, GAsyncResult *result, PraghaRemovablePlugin *plugin)
197 {
198 	GtkWidget *dialog;
199 	GMount    *mount;
200 	GError    *error = NULL;
201 	gchar     *name = NULL, *primary = NULL;
202 
203 	g_return_if_fail (G_IS_VOLUME (volume));
204 	g_return_if_fail (G_IS_ASYNC_RESULT (result));
205 
206 	/* finish mounting the volume */
207 	if (!g_volume_mount_finish (volume, result, &error)) {
208 		if (error->code != G_IO_ERROR_FAILED_HANDLED &&
209 		    error->code != G_IO_ERROR_ALREADY_MOUNTED) {
210 			name = g_volume_get_name (G_VOLUME (volume));
211 			primary = g_strdup_printf (_("Unable to access \"%s\""), name);
212 			g_free (name);
213 
214 			dialog = pragha_gudev_dialog_new (NULL, _("Removable Device"), "media-removable",
215 			                                  primary, error->message,
216 			                                  NULL, PRAGHA_DEVICE_RESPONSE_NONE);
217 			g_signal_connect (dialog, "response",
218 			                  G_CALLBACK (gtk_widget_destroy), NULL);
219 
220 			gtk_widget_show_all (dialog);
221 
222 			g_free (primary);
223 		}
224 		g_error_free (error);
225 	}
226 
227 	/* get the moint point of the volume */
228 	mount = g_volume_get_mount (volume);
229 	if (mount != NULL) {
230 		pragha_block_device_add_to_library (plugin, mount);
231 		g_object_unref (mount);
232 	}
233 	g_object_unref (volume);
234 }
235 
236 static void
pragha_block_device_mount_device(PraghaRemovablePlugin * plugin)237 pragha_block_device_mount_device (PraghaRemovablePlugin *plugin)
238 {
239 	GMountOperation *mount_operation;
240 
241 	PraghaRemovablePluginPrivate *priv = plugin->priv;
242 
243 	/* try to mount the volume asynchronously */
244 	mount_operation = gtk_mount_operation_new (NULL);
245 	g_volume_mount (priv->volume, G_MOUNT_MOUNT_NONE, mount_operation,
246 	                NULL, (GAsyncReadyCallback) pragha_block_device_mount_finish,
247 	                plugin);
248 	g_object_unref (mount_operation);
249 }
250 
251 static void
pragha_block_device_detected_response(GtkWidget * dialog,gint response,gpointer user_data)252 pragha_block_device_detected_response (GtkWidget *dialog,
253                                        gint       response,
254                                        gpointer   user_data)
255 {
256 	PraghaRemovablePlugin *plugin = user_data;
257 
258 	switch (response)
259 	{
260 		case PRAGHA_DEVICE_RESPONSE_BROWSE:
261 			pragha_block_device_mount_device (plugin);
262 			break;
263 		case PRAGHA_DEVICE_RESPONSE_NONE:
264 			pragha_removable_clear_hook_device (plugin);
265 		default:
266 			break;
267 	}
268 	gtk_widget_destroy (dialog);
269 }
270 
271 static gboolean
pragha_block_device_detected(gpointer data)272 pragha_block_device_detected (gpointer data)
273 {
274 	GtkWidget      *dialog;
275 	GVolumeMonitor *monitor;
276 	GVolume        *volume;
277 	gchar *name = NULL, *primary = NULL;
278 
279 	PraghaRemovablePlugin *plugin = data;
280 	PraghaRemovablePluginPrivate *priv = plugin->priv;
281 
282 	/* determine the GVolume corresponding to the udev removable */
283 	monitor = g_volume_monitor_get ();
284 	volume = tvm_g_volume_monitor_get_volume_for_kind (monitor,
285 	                                                   G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE,
286 	                                                   g_udev_device_get_device_file (priv->u_device));
287 	g_object_unref (monitor);
288 
289 	/* check if we have a volume */
290 	priv->volume = volume;
291 	if (volume == NULL || !g_volume_can_mount (volume)) {
292 		pragha_removable_clear_hook_device (plugin);
293 		return FALSE;
294 	}
295 
296 	name = g_volume_get_name (G_VOLUME (volume));
297 	primary = g_strdup_printf (_("Want to manage \"%s\" volume?"), name);
298 	g_free (name);
299 
300 	dialog = pragha_gudev_dialog_new (NULL, _("Removable Device"), "media-removable",
301 	                                  primary, NULL,
302 	                                  _("_Update library"), PRAGHA_DEVICE_RESPONSE_BROWSE);
303 
304 	g_signal_connect (G_OBJECT (dialog), "response",
305 	                  G_CALLBACK (pragha_block_device_detected_response), plugin);
306 
307 	gtk_widget_show_all (dialog);
308 
309 	g_free (primary);
310 
311 	return FALSE;
312 }
313 
314 static void
pragha_removable_plugin_device_added(PraghaDeviceClient * device_client,PraghaDeviceType device_type,GUdevDevice * u_device,gpointer user_data)315 pragha_removable_plugin_device_added (PraghaDeviceClient *device_client,
316                                       PraghaDeviceType    device_type,
317                                       GUdevDevice        *u_device,
318                                       gpointer            user_data)
319 {
320 	PraghaRemovablePlugin *plugin = user_data;
321 	PraghaRemovablePluginPrivate *priv = plugin->priv;
322 
323 	if (device_type != PRAGHA_DEVICE_MOUNTABLE)
324 		return;
325 
326 	priv->bus_hooked = g_udev_device_get_property_as_uint64 (u_device, "BUSNUM");
327 	priv->device_hooked = g_udev_device_get_property_as_uint64 (u_device, "DEVNUM");
328 	priv->u_device = g_object_ref (u_device);
329 	priv->volume = NULL;
330 
331 	/*
332 	 * HACK: We're listening udev. Then wait 2 seconds, to ensure that GVolume also detects the device.
333 	 */
334 	g_timeout_add_seconds(2, pragha_block_device_detected, plugin);
335 }
336 
337 void
pragha_removable_plugin_device_removed(PraghaDeviceClient * device_client,PraghaDeviceType device_type,GUdevDevice * u_device,gpointer user_data)338 pragha_removable_plugin_device_removed (PraghaDeviceClient *device_client,
339                                         PraghaDeviceType    device_type,
340                                         GUdevDevice        *u_device,
341                                         gpointer            user_data)
342 {
343 	guint64 busnum = 0;
344 	guint64 devnum = 0;
345 
346 	PraghaRemovablePlugin *plugin = user_data;
347 	PraghaRemovablePluginPrivate *priv = plugin->priv;
348 
349 	if (!priv->u_device || !priv->mount_path)
350 		return;
351 
352 	if (device_type != PRAGHA_DEVICE_MOUNTABLE)
353 		return;
354 
355 	busnum = g_udev_device_get_property_as_uint64(u_device, "BUSNUM");
356 	devnum = g_udev_device_get_property_as_uint64(u_device, "DEVNUM");
357 
358 	if (busnum == priv->bus_hooked && devnum == priv->device_hooked) {
359 		pragha_removable_drop_device_from_library (plugin);
360 		pragha_removable_clear_hook_device (plugin);
361 	}
362 }
363 
364 static void
pragha_plugin_activate(PeasActivatable * activatable)365 pragha_plugin_activate (PeasActivatable *activatable)
366 {
367 	PraghaDeviceClient *device_client;
368 	PraghaRemovablePlugin *plugin = PRAGHA_REMOVABLE_PLUGIN (activatable);
369 	PraghaRemovablePluginPrivate *priv = plugin->priv;
370 
371 	CDEBUG(DBG_PLUGIN, "Removable plugin %s", G_STRFUNC);
372 
373 	priv->pragha = g_object_get_data (G_OBJECT (plugin), "object");
374 
375 	device_client = pragha_device_client_get();
376 	g_signal_connect (G_OBJECT(device_client), "device-added",
377 	                  G_CALLBACK(pragha_removable_plugin_device_added), plugin);
378 	g_signal_connect (G_OBJECT(device_client), "device-removed",
379 	                  G_CALLBACK(pragha_removable_plugin_device_removed), plugin);
380 	g_object_unref (device_client);
381 }
382 
383 static void
pragha_plugin_deactivate(PeasActivatable * activatable)384 pragha_plugin_deactivate (PeasActivatable *activatable)
385 {
386 	PraghaDeviceClient *device_client;
387 	PraghaRemovablePlugin *plugin = PRAGHA_REMOVABLE_PLUGIN (activatable);
388 	PraghaRemovablePluginPrivate *priv = plugin->priv;
389 
390 	CDEBUG(DBG_PLUGIN, "Removable plugin %s", G_STRFUNC);
391 
392 	device_client = pragha_device_client_get();
393 	g_signal_handlers_disconnect_by_func (device_client,
394 	                                      pragha_removable_plugin_device_added,
395 	                                      plugin);
396 	g_signal_handlers_disconnect_by_func (device_client,
397 	                                      pragha_removable_plugin_device_removed,
398 	                                      plugin);
399 	g_object_unref (device_client);
400 
401 	priv->pragha = NULL;
402 }