1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /*-
3 * Copyright (c) 2006 Benedikt Meurer <benny@xfce.org>
4 * Copyright (c) 2011 Jannis Pohlmann <jannis@xfce.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library 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
14 * GNU Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library 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
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif
29
30 #include <libxfce4util/libxfce4util.h>
31
32 #include <thunar-archive-plugin/tap-backend.h>
33 #include <thunar-archive-plugin/tap-provider.h>
34
35 /* use g_access() on win32 */
36 #if defined(G_OS_WIN32)
37 #include <glib/gstdio.h>
38 #else
39 #define g_access(filename, mode) access((filename), (mode))
40 #endif
41
42
43
44 static void tap_provider_menu_provider_init (ThunarxMenuProviderIface *iface);
45 static void tap_provider_finalize (GObject *object);
46 static GList *tap_provider_get_file_menu_items (ThunarxMenuProvider *menu_provider,
47 GtkWidget *window,
48 GList *files);
49 static GList *tap_provider_get_dnd_menu_items (ThunarxMenuProvider *menu_provider,
50 GtkWidget *window,
51 ThunarxFileInfo *folder,
52 GList *files);
53 static void tap_provider_execute (TapProvider *tap_provider,
54 GPid (*action) (const gchar *folder,
55 GList *files,
56 GtkWidget *window,
57 GError **error),
58 GtkWidget *window,
59 const gchar *folder,
60 GList *files,
61 const gchar *error_message);
62 static void tap_provider_child_watch (GPid pid,
63 gint status,
64 gpointer user_data);
65 static void tap_provider_child_watch_destroy (gpointer user_data);
66
67
68
69 struct _TapProviderClass
70 {
71 GObjectClass __parent__;
72 };
73
74 struct _TapProvider
75 {
76 GObject __parent__;
77
78 #if !GTK_CHECK_VERSION(2,9,0)
79 /* GTK+ 2.9.0 and above provide an icon-name property
80 * for GtkActions, so we don't need the icon factory.
81 */
82 GtkIconFactory *icon_factory;
83 #endif
84
85 /* child watch support for the last spawn command, which allowed us to refresh
86 * the folder contents after the command had terminated with ThunarVFS (i.e.
87 * when the archive had been created). This no longer works with GIO but
88 * we still use the watch to close the PID properly.
89 */
90 gint child_watch_id;
91 };
92
93
94
95 static const gchar TAP_MIME_TYPES[][34] = {
96 "application/x-7z-compressed",
97 "application/x-7z-compressed-tar",
98 "application/x-ar",
99 "application/x-arj",
100 "application/x-bzip",
101 "application/x-bzip-compressed-tar",
102 "application/x-compress",
103 "application/x-compressed-tar",
104 "application/x-deb",
105 "application/x-gtar",
106 "application/x-gzip",
107 "application/x-lha",
108 "application/x-lhz",
109 "application/x-lzma",
110 "application/x-lzma-compressed-tar",
111 "application/x-rar",
112 "application/x-rar-compressed",
113 "application/x-tar",
114 "application/x-xz",
115 "application/x-xz-compressed-tar",
116 "application/x-zip",
117 "application/x-zip-compressed",
118 "application/zip",
119 "multipart/x-zip",
120 "application/x-rpm",
121 "application/x-jar",
122 "application/x-java-archive",
123 "application/x-lzop",
124 "application/x-zoo",
125 "application/x-cd-image",
126 "application/x-7z-compressed",
127 };
128
129 static GQuark tap_item_files_quark;
130 static GQuark tap_item_folder_quark;
131 static GQuark tap_item_provider_quark;
132
133
134
135 THUNARX_DEFINE_TYPE_WITH_CODE (TapProvider,
136 tap_provider,
137 G_TYPE_OBJECT,
138 THUNARX_IMPLEMENT_INTERFACE (THUNARX_TYPE_MENU_PROVIDER,
139 tap_provider_menu_provider_init));
140
141
142 static void
tap_provider_class_init(TapProviderClass * klass)143 tap_provider_class_init (TapProviderClass *klass)
144 {
145 GObjectClass *gobject_class;
146
147 /* determine the "tap-item-files", "tap-item-folder" and "tap-item-provider" quarks */
148 tap_item_files_quark = g_quark_from_string ("tap-item-files");
149 tap_item_folder_quark = g_quark_from_string ("tap-item-folder");
150 tap_item_provider_quark = g_quark_from_string ("tap-item-provider");
151
152 gobject_class = G_OBJECT_CLASS (klass);
153 gobject_class->finalize = tap_provider_finalize;
154 }
155
156
157
158 static void
tap_provider_menu_provider_init(ThunarxMenuProviderIface * iface)159 tap_provider_menu_provider_init (ThunarxMenuProviderIface *iface)
160 {
161 iface->get_file_menu_items = tap_provider_get_file_menu_items;
162 iface->get_dnd_menu_items = tap_provider_get_dnd_menu_items;
163 }
164
165
166
167 static void
tap_provider_init(TapProvider * tap_provider)168 tap_provider_init (TapProvider *tap_provider)
169 {
170 }
171
172
173
174 static void
tap_provider_finalize(GObject * object)175 tap_provider_finalize (GObject *object)
176 {
177 TapProvider *tap_provider = TAP_PROVIDER (object);
178 GSource *source;
179
180 /* give up maintaince of any pending child watch */
181 if (G_UNLIKELY (tap_provider->child_watch_id != 0))
182 {
183 /* reset the callback function to g_spawn_close_pid() so the plugin can be
184 * safely unloaded and the child will still not become a zombie afterwards.
185 */
186 source = g_main_context_find_source_by_id (NULL, tap_provider->child_watch_id);
187 g_source_set_callback (source, (GSourceFunc) g_spawn_close_pid, NULL, NULL);
188 }
189
190 (*G_OBJECT_CLASS (tap_provider_parent_class)->finalize) (object);
191 }
192
193
194
195 static gboolean
tap_is_archive(ThunarxFileInfo * file_info)196 tap_is_archive (ThunarxFileInfo *file_info)
197 {
198 guint n;
199
200 for (n = 0; n < G_N_ELEMENTS (TAP_MIME_TYPES); ++n)
201 if (thunarx_file_info_has_mime_type (file_info, TAP_MIME_TYPES[n]))
202 return TRUE;
203
204 return FALSE;
205 }
206
207
208
209 static gboolean
tap_is_parent_writable(ThunarxFileInfo * file_info)210 tap_is_parent_writable (ThunarxFileInfo *file_info)
211 {
212 gboolean result = FALSE;
213 gchar *filename;
214 gchar *uri;
215
216 /* determine the parent URI for the file info */
217 uri = thunarx_file_info_get_parent_uri (file_info);
218 if (G_LIKELY (uri != NULL))
219 {
220 /* determine the local filename for the URI */
221 filename = g_filename_from_uri (uri, NULL, NULL);
222 if (G_LIKELY (filename != NULL))
223 {
224 /* check if we can write to that folder */
225 result = (g_access (filename, W_OK) == 0);
226
227 /* release the filename */
228 g_free (filename);
229 }
230
231 /* release the URI */
232 g_free (uri);
233 }
234
235 return result;
236 }
237
238
239
240 static void
tap_extract_here(ThunarxMenuItem * item,GtkWidget * window)241 tap_extract_here (ThunarxMenuItem *item,
242 GtkWidget *window)
243 {
244 ThunarxFileInfo *folder;
245 TapProvider *tap_provider;
246 GList *files;
247 gchar *dirname;
248 gchar *uri;
249
250 /* determine the files associated with the item */
251 files = g_object_get_qdata (G_OBJECT (item), tap_item_files_quark);
252 if (G_UNLIKELY (files == NULL))
253 return;
254
255 /* determine the provider associated with the item */
256 tap_provider = g_object_get_qdata (G_OBJECT (item), tap_item_provider_quark);
257 if (G_UNLIKELY (tap_provider == NULL))
258 return;
259
260 /* check if a folder was supplied (for the Drag'n'Drop item) */
261 folder = g_object_get_qdata (G_OBJECT (item), tap_item_folder_quark);
262 if (G_UNLIKELY (folder != NULL))
263 {
264 /* determine the URI of the supplied folder */
265 uri = thunarx_file_info_get_uri (folder);
266 }
267 else
268 {
269 /* determine the parent URI of the first selected file */
270 uri = thunarx_file_info_get_parent_uri (files->data);
271 }
272
273 /* verify that we have an URI */
274 if (G_LIKELY (uri != NULL))
275 {
276 /* determine the directory of the first selected file */
277 dirname = g_filename_from_uri (uri, NULL, NULL);
278
279 /* verify that we were able to determine a local path */
280 if (G_LIKELY (dirname != NULL))
281 {
282 /* execute the action associated with the menu item */
283 tap_provider_execute (tap_provider, tap_backend_extract_here, window, dirname, files, _("Failed to extract files"));
284
285 /* release the dirname */
286 g_free (dirname);
287 }
288
289 /* release the URI */
290 g_free (uri);
291 }
292 }
293
294
295
296 static void
tap_extract_to(ThunarxMenuItem * item,GtkWidget * window)297 tap_extract_to (ThunarxMenuItem *item,
298 GtkWidget *window)
299 {
300 TapProvider *tap_provider;
301 const gchar *default_dir;
302 GList *files;
303
304 /* determine the files associated with the item */
305 files = g_object_get_qdata (G_OBJECT (item), tap_item_files_quark);
306 if (G_UNLIKELY (files == NULL))
307 return;
308
309 /* determine the provider associated with the item */
310 tap_provider = g_object_get_qdata (G_OBJECT (item), tap_item_provider_quark);
311 if (G_UNLIKELY (tap_provider == NULL))
312 return;
313
314 /* if $GTK_DEFAULT_FILECHOOSER_DIR is set, we use that as default
315 * folder (i.e. Ubuntu), otherwise we just use $HOME.
316 */
317 default_dir = g_getenv ("GTK_DEFAULT_FILECHOOSER_DIR");
318 if (G_LIKELY (default_dir == NULL))
319 default_dir = g_get_home_dir ();
320
321 /* execute the action associated with the menu item */
322 tap_provider_execute (tap_provider, tap_backend_extract_to, window, default_dir, files, _("Failed to extract files"));
323 }
324
325
326
327 static void
tap_create_archive(ThunarxMenuItem * item,GtkWidget * window)328 tap_create_archive (ThunarxMenuItem *item,
329 GtkWidget *window)
330 {
331 TapProvider *tap_provider;
332 GList *files;
333 gchar *dirname;
334 gchar *uri;
335
336 /* determine the files associated with the item */
337 files = g_object_get_qdata (G_OBJECT (item), tap_item_files_quark);
338 if (G_UNLIKELY (files == NULL))
339 return;
340
341 /* determine the provider associated with the item */
342 tap_provider = g_object_get_qdata (G_OBJECT (item), tap_item_provider_quark);
343 if (G_UNLIKELY (tap_provider == NULL))
344 return;
345
346 /* determine the parent URI of the first selected file */
347 uri = thunarx_file_info_get_parent_uri (files->data);
348 if (G_UNLIKELY (uri == NULL))
349 return;
350
351 /* determine the directory of the first selected file */
352 dirname = g_filename_from_uri (uri, NULL, NULL);
353 g_free (uri);
354
355 /* verify that we were able to determine a local path */
356 if (G_UNLIKELY (dirname == NULL))
357 return;
358
359 /* execute the action associated with the menu item */
360 tap_provider_execute (tap_provider, tap_backend_create_archive, window, dirname, files, _("Failed to create archive"));
361
362 /* cleanup */
363 g_free (dirname);
364 }
365
366
367
368 static GList*
tap_provider_get_file_menu_items(ThunarxMenuProvider * menu_provider,GtkWidget * window,GList * files)369 tap_provider_get_file_menu_items (ThunarxMenuProvider *menu_provider,
370 GtkWidget *window,
371 GList *files)
372 {
373 gchar *scheme;
374 TapProvider *tap_provider = TAP_PROVIDER (menu_provider);
375 ThunarxMenuItem *item;
376 GClosure *closure;
377 gboolean all_archives = TRUE;
378 gboolean can_write = TRUE;
379 GList *items = NULL;
380 GList *lp;
381 gint n_files = 0;
382
383 /* check all supplied files */
384 for (lp = files; lp != NULL; lp = lp->next, ++n_files)
385 {
386 /* check if the file is a local file */
387 scheme = thunarx_file_info_get_uri_scheme (lp->data);
388
389 /* unable to handle non-local files */
390 if (G_UNLIKELY (strcmp (scheme, "file")))
391 {
392 g_free (scheme);
393 return NULL;
394 }
395 g_free (scheme);
396
397 /* check if this file is a supported archive */
398 if (all_archives && !tap_is_archive (lp->data))
399 all_archives = FALSE;
400
401 /* check if we can write to the parent folder */
402 if (can_write && !tap_is_parent_writable (lp->data))
403 can_write = FALSE;
404 }
405
406 /* check if all files are supported archives */
407 if (all_archives)
408 {
409 /* check if we can write to the parent folders */
410 if (G_LIKELY (can_write))
411 {
412 /* append the "Extract Here" menu item */
413 item = thunarx_menu_item_new ("Tap::extract-here",
414 _("Extract _Here"),
415 dngettext (GETTEXT_PACKAGE,
416 "Extract the selected archive in the current folder",
417 "Extract the selected archives in the current folder",
418 n_files),
419 "tap-extract");
420
421 g_object_set_qdata_full (G_OBJECT (item), tap_item_files_quark,
422 thunarx_file_info_list_copy (files),
423 (GDestroyNotify) thunarx_file_info_list_free);
424 g_object_set_qdata_full (G_OBJECT (item), tap_item_provider_quark,
425 g_object_ref (G_OBJECT (tap_provider)),
426 (GDestroyNotify) g_object_unref);
427 closure = g_cclosure_new_object (G_CALLBACK (tap_extract_here), G_OBJECT (window));
428 g_signal_connect_closure (G_OBJECT (item), "activate", closure, TRUE);
429 items = g_list_append (items, item);
430 }
431
432 /* append the "Extract To..." menu item */
433 item = thunarx_menu_item_new ("Tap::extract-to",
434 _("_Extract To..."),
435 dngettext (GETTEXT_PACKAGE,
436 "Extract the selected archive",
437 "Extract the selected archives",
438 n_files),
439 "tap-extract-to");
440
441 g_object_set_qdata_full (G_OBJECT (item), tap_item_files_quark,
442 thunarx_file_info_list_copy (files),
443 (GDestroyNotify) thunarx_file_info_list_free);
444 g_object_set_qdata_full (G_OBJECT (item), tap_item_provider_quark,
445 g_object_ref (G_OBJECT (tap_provider)),
446 (GDestroyNotify) g_object_unref);
447 closure = g_cclosure_new_object (G_CALLBACK (tap_extract_to), G_OBJECT (window));
448 g_signal_connect_closure (G_OBJECT (item), "activate", closure, TRUE);
449 items = g_list_append (items, item);
450 }
451
452 /* check if more than one files was given or the file is not an archive */
453 if (G_LIKELY (n_files > 1 || !all_archives))
454 {
455 /* append the "Create Archive..." menu item */
456 item = thunarx_menu_item_new ("Tap::create-archive",
457 _("Cr_eate Archive..."),
458 dngettext (GETTEXT_PACKAGE,
459 "Create an archive with the selected object",
460 "Create an archive with the selected objects",
461 n_files),
462 "tap-create");
463
464 g_object_set_qdata_full (G_OBJECT (item), tap_item_files_quark,
465 thunarx_file_info_list_copy (files),
466 (GDestroyNotify) thunarx_file_info_list_free);
467 g_object_set_qdata_full (G_OBJECT (item), tap_item_provider_quark,
468 g_object_ref (G_OBJECT (tap_provider)),
469 (GDestroyNotify) g_object_unref);
470 closure = g_cclosure_new_object (G_CALLBACK (tap_create_archive), G_OBJECT (window));
471 g_signal_connect_closure (G_OBJECT (item), "activate", closure, TRUE);
472 items = g_list_append (items, item);
473 }
474
475 return items;
476 }
477
478
479
480 static GList*
tap_provider_get_dnd_menu_items(ThunarxMenuProvider * menu_provider,GtkWidget * window,ThunarxFileInfo * folder,GList * files)481 tap_provider_get_dnd_menu_items (ThunarxMenuProvider *menu_provider,
482 GtkWidget *window,
483 ThunarxFileInfo *folder,
484 GList *files)
485 {
486 gchar *scheme;
487 TapProvider *tap_provider = TAP_PROVIDER (menu_provider);
488 ThunarxMenuItem *item;
489 GClosure *closure;
490 GList *lp;
491 gint n_files = 0;
492
493 /* check if the folder is a local folder */
494 scheme = thunarx_file_info_get_uri_scheme (folder);
495
496 /* unable to extract to non-local folders */
497 if (G_UNLIKELY (strcmp (scheme, "file")))
498 {
499 g_free (scheme);
500 return NULL;
501 }
502 g_free (scheme);
503
504 /* check all supplied files */
505 for (lp = files; lp != NULL; lp = lp->next, ++n_files)
506 {
507 /* check if the file is a local file */
508 scheme = thunarx_file_info_get_uri_scheme (lp->data);
509
510 /* unable to handle non-local files */
511 if (G_UNLIKELY (strcmp (scheme, "file")))
512 {
513 g_free (scheme);
514 return NULL;
515 }
516 g_free (scheme);
517
518 /* check if this file is a supported archive */
519 if (G_LIKELY (!tap_is_archive (lp->data)))
520 return NULL;
521 }
522
523 /* setup the "Extract here" menu item */
524 item = thunarx_menu_item_new ("Tap::extract-here-dnd",
525 /* TRANSLATORS: This is the label of the Drag'n'Drop "Extract here" menu item */
526 _("_Extract here"),
527 dngettext (GETTEXT_PACKAGE,
528 "Extract the selected archive here",
529 "Extract the selected archives here",
530 n_files),
531 "tap-extract");
532
533 g_object_set_qdata_full (G_OBJECT (item), tap_item_files_quark,
534 thunarx_file_info_list_copy (files),
535 (GDestroyNotify) thunarx_file_info_list_free);
536 g_object_set_qdata_full (G_OBJECT (item), tap_item_provider_quark,
537 g_object_ref (G_OBJECT (tap_provider)),
538 (GDestroyNotify) g_object_unref);
539 g_object_set_qdata_full (G_OBJECT (item), tap_item_folder_quark,
540 g_object_ref (G_OBJECT (folder)),
541 (GDestroyNotify) g_object_unref);
542 closure = g_cclosure_new_object (G_CALLBACK (tap_extract_here), G_OBJECT (window));
543 g_signal_connect_closure (G_OBJECT (item), "activate", closure, TRUE);
544
545 /* return a list with only the "Extract here" item */
546 return g_list_prepend (NULL, item);
547 }
548
549
550
551 static void
tap_provider_execute(TapProvider * tap_provider,GPid (* action)(const gchar * folder,GList * files,GtkWidget * window,GError ** error),GtkWidget * window,const gchar * folder,GList * files,const gchar * error_message)552 tap_provider_execute (TapProvider *tap_provider,
553 GPid (*action) (const gchar *folder,
554 GList *files,
555 GtkWidget *window,
556 GError **error),
557 GtkWidget *window,
558 const gchar *folder,
559 GList *files,
560 const gchar *error_message)
561 {
562 GtkWidget *dialog;
563 GSource *source;
564 GError *error = NULL;
565 GPid pid;
566
567 /* try to execute the action */
568 pid = (*action) (folder, files, window, &error);
569 if (G_LIKELY (pid >= 0))
570 {
571 /* check if we already have a child watch */
572 if (G_UNLIKELY (tap_provider->child_watch_id != 0))
573 {
574 /* reset the callback function to g_spawn_close_pid() so the plugin can be
575 * safely unloaded and the child will still not become a zombie afterwards.
576 */
577 source = g_main_context_find_source_by_id (NULL, tap_provider->child_watch_id);
578 g_source_set_callback (source, (GSourceFunc) g_spawn_close_pid, NULL, NULL);
579 }
580
581 /* schedule the new child watch */
582 tap_provider->child_watch_id = g_child_watch_add_full (G_PRIORITY_LOW, pid, tap_provider_child_watch,
583 tap_provider, tap_provider_child_watch_destroy);
584 }
585 else if (error != NULL)
586 {
587 /* display an error dialog */
588 dialog = gtk_message_dialog_new (GTK_WINDOW (window),
589 GTK_DIALOG_DESTROY_WITH_PARENT
590 | GTK_DIALOG_MODAL,
591 GTK_MESSAGE_ERROR,
592 GTK_BUTTONS_CLOSE,
593 "%s.", error_message);
594 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s.", error->message);
595 gtk_dialog_run (GTK_DIALOG (dialog));
596 gtk_widget_destroy (dialog);
597 g_error_free (error);
598 }
599 }
600
601
602
603 static void
tap_provider_child_watch(GPid pid,gint status,gpointer user_data)604 tap_provider_child_watch (GPid pid,
605 gint status,
606 gpointer user_data)
607 {
608 /* need to cleanup */
609 g_spawn_close_pid (pid);
610 }
611
612
613
614 static void
tap_provider_child_watch_destroy(gpointer user_data)615 tap_provider_child_watch_destroy (gpointer user_data)
616 {
617 TapProvider *tap_provider = TAP_PROVIDER (user_data);
618
619 /* reset child watch id */
620 tap_provider->child_watch_id = 0;
621 }
622
623
624
625