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