1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /*-
3  * Copyright (c) 2005-2007 Benedikt Meurer <benny@xfce.org>
4  * Copyright (c) 2009-2011 Jannis Pohlmann <jannis@xfce.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program 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 General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the Free
18  * 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_SYS_TYPES_H
27 #include <sys/types.h>
28 #endif
29 
30 #ifdef HAVE_SYS_STAT_H
31 #include <sys/stat.h>
32 #endif
33 
34 #ifdef HAVE_ERRNO_H
35 #include <errno.h>
36 #endif
37 #ifdef HAVE_MEMORY_H
38 #include <memory.h>
39 #endif
40 #ifdef HAVE_STDLIB_H
41 #include <stdlib.h>
42 #endif
43 #ifdef HAVE_STRING_H
44 #include <string.h>
45 #endif
46 #ifdef HAVE_TIME_H
47 #include <time.h>
48 #endif
49 #ifdef HAVE_UNISTD_H
50 #include <unistd.h>
51 #endif
52 
53 #include <gio/gio.h>
54 #include <libxfce4ui/libxfce4ui.h>
55 
56 #include <thunarx/thunarx.h>
57 
58 #include <thunar/thunar-application.h>
59 #include <thunar/thunar-chooser-dialog.h>
60 #include <thunar/thunar-file.h>
61 #include <thunar/thunar-file-monitor.h>
62 #include <thunar/thunar-gio-extensions.h>
63 #include <thunar/thunar-gobject-extensions.h>
64 #include <thunar/thunar-private.h>
65 #include <thunar/thunar-preferences.h>
66 #include <thunar/thunar-user.h>
67 #include <thunar/thunar-util.h>
68 #include <thunar/thunar-dialogs.h>
69 #include <thunar/thunar-icon-factory.h>
70 
71 
72 
73 /* Dump the file cache every X second, set to 0 to disable */
74 #define DUMP_FILE_CACHE 0
75 
76 
77 
78 /* Signal identifiers */
79 enum
80 {
81   DESTROY,
82   LAST_SIGNAL,
83 };
84 
85 
86 
87 static void               thunar_file_info_init                (ThunarxFileInfoIface   *iface);
88 static void               thunar_file_dispose                  (GObject                *object);
89 static void               thunar_file_finalize                 (GObject                *object);
90 static gchar             *thunar_file_info_get_name            (ThunarxFileInfo        *file_info);
91 static gchar             *thunar_file_info_get_uri             (ThunarxFileInfo        *file_info);
92 static gchar             *thunar_file_info_get_parent_uri      (ThunarxFileInfo        *file_info);
93 static gchar             *thunar_file_info_get_uri_scheme      (ThunarxFileInfo        *file_info);
94 static gchar             *thunar_file_info_get_mime_type       (ThunarxFileInfo        *file_info);
95 static gboolean           thunar_file_info_has_mime_type       (ThunarxFileInfo        *file_info,
96                                                                 const gchar            *mime_type);
97 static gboolean           thunar_file_info_is_directory        (ThunarxFileInfo        *file_info);
98 static GFileInfo         *thunar_file_info_get_file_info       (ThunarxFileInfo        *file_info);
99 static GFileInfo         *thunar_file_info_get_filesystem_info (ThunarxFileInfo        *file_info);
100 static GFile             *thunar_file_info_get_location        (ThunarxFileInfo        *file_info);
101 static void               thunar_file_info_changed             (ThunarxFileInfo        *file_info);
102 static gboolean           thunar_file_denies_access_permission (const ThunarFile       *file,
103                                                                 ThunarFileMode          usr_permissions,
104                                                                 ThunarFileMode          grp_permissions,
105                                                                 ThunarFileMode          oth_permissions);
106 static void               thunar_file_monitor                  (GFileMonitor           *monitor,
107                                                                 GFile                  *path,
108                                                                 GFile                  *other_path,
109                                                                 GFileMonitorEvent       event_type,
110                                                                 gpointer                user_data);
111 static void               thunar_file_watch_reconnect          (ThunarFile             *file);
112 static gboolean           thunar_file_load                     (ThunarFile             *file,
113                                                                 GCancellable           *cancellable,
114                                                                 GError                **error);
115 static gboolean           thunar_file_is_readable              (const ThunarFile       *file);
116 static gboolean           thunar_file_same_filesystem          (const ThunarFile       *file_a,
117                                                                 const ThunarFile       *file_b);
118 
119 
120 
121 G_LOCK_DEFINE_STATIC (file_cache_mutex);
122 G_LOCK_DEFINE_STATIC (file_content_type_mutex);
123 G_LOCK_DEFINE_STATIC (file_rename_mutex);
124 
125 
126 
127 static ThunarUserManager *user_manager;
128 static GHashTable        *file_cache;
129 static guint32            effective_user_id;
130 static GQuark             thunar_file_watch_quark;
131 static guint              file_signals[LAST_SIGNAL];
132 
133 
134 
135 #define FLAG_SET_THUMB_STATE(file,new_state) G_STMT_START{ (file)->flags = ((file)->flags & ~THUNAR_FILE_FLAG_THUMB_MASK) | (new_state); }G_STMT_END
136 #define FLAG_GET_THUMB_STATE(file)           ((file)->flags & THUNAR_FILE_FLAG_THUMB_MASK)
137 #define FLAG_SET(file,flag)                  G_STMT_START{ ((file)->flags |= (flag)); }G_STMT_END
138 #define FLAG_UNSET(file,flag)                G_STMT_START{ ((file)->flags &= ~(flag)); }G_STMT_END
139 #define FLAG_IS_SET(file,flag)               (((file)->flags & (flag)) != 0)
140 
141 #define DEFAULT_CONTENT_TYPE "application/octet-stream"
142 
143 
144 
145 typedef enum
146 {
147   THUNAR_FILE_FLAG_THUMB_MASK     = 0x03,   /* storage for ThunarFileThumbState */
148   THUNAR_FILE_FLAG_IN_DESTRUCTION = 1 << 2, /* for avoiding recursion during destroy */
149   THUNAR_FILE_FLAG_IS_MOUNTED     = 1 << 3, /* whether this file is mounted */
150 }
151 ThunarFileFlags;
152 
153 struct _ThunarFileClass
154 {
155   GObjectClass __parent__;
156 
157   /* signals */
158   void (*destroy) (ThunarFile *file);
159 };
160 
161 struct _ThunarFile
162 {
163   GObject __parent__;
164 
165   /* storage for the file information */
166   GFileInfo            *info;
167   GFileType             kind;
168   GFile                *gfile;
169   gchar                *content_type;
170   gchar                *icon_name;
171 
172   gchar                *custom_icon_name;
173   gchar                *display_name;
174   gchar                *basename;
175   gchar                *thumbnail_path;
176 
177   /* sorting */
178   gchar                *collate_key;
179   gchar                *collate_key_nocase;
180 
181   /* flags for thumbnail state etc */
182   ThunarFileFlags       flags;
183 
184   /* tells whether the file watch is not set */
185   gboolean              no_file_watch;
186 };
187 
188 typedef struct
189 {
190   GFileMonitor  *monitor;
191   guint          watch_count;
192 }
193 ThunarFileWatch;
194 
195 typedef struct
196 {
197   ThunarFileGetFunc  func;
198   gpointer           user_data;
199   GCancellable      *cancellable;
200 }
201 ThunarFileGetData;
202 
203 static struct
204 {
205   GUserDirectory  type;
206   const gchar    *icon_name;
207 }
208 thunar_file_dirs[] =
209 {
210   { G_USER_DIRECTORY_DESKTOP,      "user-desktop" },
211   { G_USER_DIRECTORY_DOCUMENTS,    "folder-documents" },
212   { G_USER_DIRECTORY_DOWNLOAD,     "folder-download" },
213   { G_USER_DIRECTORY_MUSIC,        "folder-music" },
214   { G_USER_DIRECTORY_PICTURES,     "folder-pictures" },
215   { G_USER_DIRECTORY_PUBLIC_SHARE, "folder-publicshare" },
216   { G_USER_DIRECTORY_TEMPLATES,    "folder-templates" },
217   { G_USER_DIRECTORY_VIDEOS,       "folder-videos" }
218 };
219 
220 
221 
G_DEFINE_TYPE_WITH_CODE(ThunarFile,thunar_file,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (THUNARX_TYPE_FILE_INFO,thunar_file_info_init))222 G_DEFINE_TYPE_WITH_CODE (ThunarFile, thunar_file, G_TYPE_OBJECT,
223     G_IMPLEMENT_INTERFACE (THUNARX_TYPE_FILE_INFO, thunar_file_info_init))
224 
225 
226 static GWeakRef*
227 weak_ref_new (GObject *obj)
228 {
229   GWeakRef *ref;
230   ref = g_slice_new (GWeakRef);
231   g_weak_ref_init (ref, obj);
232   return ref;
233 }
234 
235 static void
weak_ref_free(GWeakRef * ref)236 weak_ref_free (GWeakRef *ref)
237 {
238   g_weak_ref_clear (ref);
239   g_slice_free (GWeakRef, ref);
240 }
241 
242 
243 #ifdef G_ENABLE_DEBUG
244 #ifdef HAVE_ATEXIT
245 static gboolean thunar_file_atexit_registered = FALSE;
246 
247 
248 
249 static void
thunar_file_atexit_foreach(gpointer key,gpointer value,gpointer user_data)250 thunar_file_atexit_foreach (gpointer key,
251                             gpointer value,
252                             gpointer user_data)
253 {
254   gchar *uri;
255 
256   uri = g_file_get_uri (key);
257   g_print ("--> %s\n", uri);
258   if (G_OBJECT (key)->ref_count > 2)
259     g_print ("    GFile (%u)\n", G_OBJECT (key)->ref_count - 2);
260   g_free (uri);
261 }
262 
263 
264 
265 static void
thunar_file_atexit(void)266 thunar_file_atexit (void)
267 {
268   G_LOCK (file_cache_mutex);
269 
270   if (file_cache == NULL || g_hash_table_size (file_cache) == 0)
271     {
272       G_UNLOCK (file_cache_mutex);
273       return;
274     }
275 
276   g_print ("--- Leaked a total of %u ThunarFile objects:\n",
277            g_hash_table_size (file_cache));
278 
279   g_hash_table_foreach (file_cache, thunar_file_atexit_foreach, NULL);
280 
281   g_print ("\n");
282 
283   G_UNLOCK (file_cache_mutex);
284 }
285 #endif
286 #endif
287 
288 
289 
290 #if DUMP_FILE_CACHE
291 static void
thunar_file_cache_dump_foreach(gpointer gfile,gpointer value,gpointer user_data)292 thunar_file_cache_dump_foreach (gpointer gfile,
293                                 gpointer value,
294                                 gpointer user_data)
295 {
296   gchar *name;
297 
298   name = g_file_get_parse_name (G_FILE (gfile));
299   g_print ("    %s\n", name);
300   g_free (name);
301 }
302 
303 
304 
305 static gboolean
thunar_file_cache_dump(gpointer user_data)306 thunar_file_cache_dump (gpointer user_data)
307 {
308   G_LOCK (file_cache_mutex);
309 
310   if (file_cache != NULL)
311     {
312       g_print ("--- %d ThunarFile objects in cache:\n",
313                g_hash_table_size (file_cache));
314 
315       g_hash_table_foreach (file_cache, thunar_file_cache_dump_foreach, NULL);
316 
317       g_print ("\n");
318     }
319 
320   G_UNLOCK (file_cache_mutex);
321 
322   return TRUE;
323 }
324 #endif
325 
326 
327 
328 static void
thunar_file_class_init(ThunarFileClass * klass)329 thunar_file_class_init (ThunarFileClass *klass)
330 {
331   GObjectClass *gobject_class;
332 
333 #ifdef G_ENABLE_DEBUG
334 #ifdef HAVE_ATEXIT
335   if (G_UNLIKELY (!thunar_file_atexit_registered))
336     {
337       atexit ((void (*)(void)) thunar_file_atexit);
338       thunar_file_atexit_registered = TRUE;
339     }
340 #endif
341 #endif
342 
343 #if DUMP_FILE_CACHE
344   g_timeout_add_seconds (DUMP_FILE_CACHE, thunar_file_cache_dump, NULL);
345 #endif
346 
347   /* pre-allocate the required quarks */
348   thunar_file_watch_quark = g_quark_from_static_string ("thunar-file-watch");
349 
350   /* grab a reference on the user manager */
351   user_manager = thunar_user_manager_get_default ();
352 
353   /* determine the effective user id of the process */
354   effective_user_id = geteuid ();
355 
356   gobject_class = G_OBJECT_CLASS (klass);
357   gobject_class->dispose = thunar_file_dispose;
358   gobject_class->finalize = thunar_file_finalize;
359 
360   /**
361    * ThunarFile::destroy:
362    * @file : the #ThunarFile instance.
363    *
364    * Emitted when the system notices that the @file
365    * was destroyed.
366    **/
367   file_signals[DESTROY] =
368     g_signal_new (I_("destroy"),
369                   G_TYPE_FROM_CLASS (klass),
370                   G_SIGNAL_RUN_CLEANUP | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
371                   G_STRUCT_OFFSET (ThunarFileClass, destroy),
372                   NULL, NULL,
373                   g_cclosure_marshal_VOID__VOID,
374                   G_TYPE_NONE, 0);
375 }
376 
377 
378 
379 static void
thunar_file_init(ThunarFile * file)380 thunar_file_init (ThunarFile *file)
381 {
382 }
383 
384 
385 
386 static void
thunar_file_info_init(ThunarxFileInfoIface * iface)387 thunar_file_info_init (ThunarxFileInfoIface *iface)
388 {
389   iface->get_name = thunar_file_info_get_name;
390   iface->get_uri = thunar_file_info_get_uri;
391   iface->get_parent_uri = thunar_file_info_get_parent_uri;
392   iface->get_uri_scheme = thunar_file_info_get_uri_scheme;
393   iface->get_mime_type = thunar_file_info_get_mime_type;
394   iface->has_mime_type = thunar_file_info_has_mime_type;
395   iface->is_directory = thunar_file_info_is_directory;
396   iface->get_file_info = thunar_file_info_get_file_info;
397   iface->get_filesystem_info = thunar_file_info_get_filesystem_info;
398   iface->get_location = thunar_file_info_get_location;
399   iface->changed = thunar_file_info_changed;
400 }
401 
402 
403 
404 static void
thunar_file_dispose(GObject * object)405 thunar_file_dispose (GObject *object)
406 {
407   ThunarFile *file = THUNAR_FILE (object);
408 
409   /* check that we don't recurse here */
410   if (!FLAG_IS_SET (file, THUNAR_FILE_FLAG_IN_DESTRUCTION))
411     {
412       /* emit the "destroy" signal */
413       FLAG_SET (file, THUNAR_FILE_FLAG_IN_DESTRUCTION);
414       g_signal_emit (object, file_signals[DESTROY], 0);
415       FLAG_UNSET (file, THUNAR_FILE_FLAG_IN_DESTRUCTION);
416     }
417 
418   (*G_OBJECT_CLASS (thunar_file_parent_class)->dispose) (object);
419 }
420 
421 
422 
423 static void
thunar_file_finalize(GObject * object)424 thunar_file_finalize (GObject *object)
425 {
426   ThunarFile *file = THUNAR_FILE (object);
427 
428   /* verify that nobody's watching the file anymore */
429 #ifdef G_ENABLE_DEBUG
430   ThunarFileWatch *file_watch = g_object_get_qdata (G_OBJECT (file), thunar_file_watch_quark);
431   if (file_watch != NULL)
432     {
433       g_error ("Attempt to finalize a ThunarFile, which has an active "
434                "watch count of %d", file_watch->watch_count);
435     }
436 #endif
437 
438   /* drop the entry from the cache */
439   G_LOCK (file_cache_mutex);
440   g_hash_table_remove (file_cache, file->gfile);
441   G_UNLOCK (file_cache_mutex);
442 
443   /* release file info */
444   if (file->info != NULL)
445     g_object_unref (file->info);
446 
447   /* free the custom icon name */
448   g_free (file->custom_icon_name);
449 
450   /* content type info */
451   g_free (file->content_type);
452   g_free (file->icon_name);
453 
454   /* free display name and basename */
455   g_free (file->display_name);
456   g_free (file->basename);
457 
458   /* free collate keys */
459   if (file->collate_key_nocase != file->collate_key)
460     g_free (file->collate_key_nocase);
461   g_free (file->collate_key);
462 
463   /* free the thumbnail path */
464   g_free (file->thumbnail_path);
465 
466   /* release file */
467   g_object_unref (file->gfile);
468 
469   (*G_OBJECT_CLASS (thunar_file_parent_class)->finalize) (object);
470 }
471 
472 
473 
474 static gchar *
thunar_file_info_get_name(ThunarxFileInfo * file_info)475 thunar_file_info_get_name (ThunarxFileInfo *file_info)
476 {
477   return g_strdup (thunar_file_get_basename (THUNAR_FILE (file_info)));
478 }
479 
480 
481 
482 static gchar*
thunar_file_info_get_uri(ThunarxFileInfo * file_info)483 thunar_file_info_get_uri (ThunarxFileInfo *file_info)
484 {
485   return thunar_file_dup_uri (THUNAR_FILE (file_info));
486 }
487 
488 
489 
490 static gchar*
thunar_file_info_get_parent_uri(ThunarxFileInfo * file_info)491 thunar_file_info_get_parent_uri (ThunarxFileInfo *file_info)
492 {
493   GFile *parent;
494   gchar *uri = NULL;
495 
496   parent = g_file_get_parent (THUNAR_FILE (file_info)->gfile);
497   if (G_LIKELY (parent != NULL))
498     {
499       uri = g_file_get_uri (parent);
500       g_object_unref (parent);
501     }
502 
503   return uri;
504 }
505 
506 
507 
508 static gchar*
thunar_file_info_get_uri_scheme(ThunarxFileInfo * file_info)509 thunar_file_info_get_uri_scheme (ThunarxFileInfo *file_info)
510 {
511   return g_file_get_uri_scheme (THUNAR_FILE (file_info)->gfile);
512 }
513 
514 
515 
516 static gchar*
thunar_file_info_get_mime_type(ThunarxFileInfo * file_info)517 thunar_file_info_get_mime_type (ThunarxFileInfo *file_info)
518 {
519   return g_strdup (thunar_file_get_content_type (THUNAR_FILE (file_info)));
520 }
521 
522 
523 
524 static gboolean
thunar_file_info_has_mime_type(ThunarxFileInfo * file_info,const gchar * mime_type)525 thunar_file_info_has_mime_type (ThunarxFileInfo *file_info,
526                                 const gchar     *mime_type)
527 {
528   if (THUNAR_FILE (file_info)->info == NULL)
529     return FALSE;
530 
531   return g_content_type_is_a (thunar_file_get_content_type (THUNAR_FILE (file_info)), mime_type);
532 }
533 
534 
535 
536 static gboolean
thunar_file_info_is_directory(ThunarxFileInfo * file_info)537 thunar_file_info_is_directory (ThunarxFileInfo *file_info)
538 {
539   return thunar_file_is_directory (THUNAR_FILE (file_info));
540 }
541 
542 
543 
544 static GFileInfo *
thunar_file_info_get_file_info(ThunarxFileInfo * file_info)545 thunar_file_info_get_file_info (ThunarxFileInfo *file_info)
546 {
547   _thunar_return_val_if_fail (THUNAR_IS_FILE (file_info), NULL);
548 
549   if (THUNAR_FILE (file_info)->info != NULL)
550     return g_object_ref (THUNAR_FILE (file_info)->info);
551   else
552     return NULL;
553 }
554 
555 
556 
557 static GFileInfo *
thunar_file_info_get_filesystem_info(ThunarxFileInfo * file_info)558 thunar_file_info_get_filesystem_info (ThunarxFileInfo *file_info)
559 {
560   _thunar_return_val_if_fail (THUNAR_IS_FILE (file_info), NULL);
561 
562   return g_file_query_filesystem_info (THUNAR_FILE (file_info)->gfile,
563                                        THUNARX_FILESYSTEM_INFO_NAMESPACE,
564                                        NULL, NULL);
565 }
566 
567 
568 
569 static GFile *
thunar_file_info_get_location(ThunarxFileInfo * file_info)570 thunar_file_info_get_location (ThunarxFileInfo *file_info)
571 {
572   _thunar_return_val_if_fail (THUNAR_IS_FILE (file_info), NULL);
573   return g_object_ref (THUNAR_FILE (file_info)->gfile);
574 }
575 
576 
577 
578 static void
thunar_file_info_changed(ThunarxFileInfo * file_info)579 thunar_file_info_changed (ThunarxFileInfo *file_info)
580 {
581   ThunarFile *file = THUNAR_FILE (file_info);
582 
583   _thunar_return_if_fail (THUNAR_IS_FILE (file_info));
584 
585   /* set the new thumbnail state manually, so we only emit file
586    * changed once */
587   FLAG_SET_THUMB_STATE (file, THUNAR_FILE_THUMB_STATE_UNKNOWN);
588 
589   /* tell the file monitor that this file changed */
590   thunar_file_monitor_file_changed (file);
591 }
592 
593 
594 
595 static gboolean
thunar_file_denies_access_permission(const ThunarFile * file,ThunarFileMode usr_permissions,ThunarFileMode grp_permissions,ThunarFileMode oth_permissions)596 thunar_file_denies_access_permission (const ThunarFile *file,
597                                       ThunarFileMode    usr_permissions,
598                                       ThunarFileMode    grp_permissions,
599                                       ThunarFileMode    oth_permissions)
600 {
601   ThunarFileMode mode;
602   ThunarGroup   *group;
603   ThunarUser    *user;
604   gboolean       result;
605   GList         *groups;
606   GList         *lp;
607 
608   /* query the file mode */
609   mode = thunar_file_get_mode (file);
610 
611   /* query the owner of the file, if we cannot determine
612    * the owner, we can't tell if we're denied to access
613    * the file, so we simply return FALSE then.
614    */
615   user = thunar_file_get_user (file);
616   if (G_UNLIKELY (user == NULL))
617     return FALSE;
618 
619   /* root is allowed to do everything */
620   if (G_UNLIKELY (effective_user_id == 0))
621     return FALSE;
622 
623   if (thunar_user_is_me (user))
624     {
625       /* we're the owner, so the usr permissions must be granted */
626       result = ((mode & usr_permissions) == 0);
627 
628       /* release the user */
629       g_object_unref (G_OBJECT (user));
630     }
631   else
632     {
633       group = thunar_file_get_group (file);
634       if (G_LIKELY (group != NULL))
635         {
636           /* release the file owner */
637           g_object_unref (G_OBJECT (user));
638 
639           /* determine the effective user */
640           user = thunar_user_manager_get_user_by_id (user_manager, effective_user_id);
641           if (G_LIKELY (user != NULL))
642             {
643               /* check the group permissions */
644               groups = thunar_user_get_groups (user);
645               for (lp = groups; lp != NULL; lp = lp->next)
646                 if (THUNAR_GROUP (lp->data) == group)
647                   {
648                     g_object_unref (G_OBJECT (user));
649                     g_object_unref (G_OBJECT (group));
650                     return ((mode & grp_permissions) == 0);
651                   }
652 
653               /* release the effective user */
654               g_object_unref (G_OBJECT (user));
655             }
656 
657           /* release the file group */
658           g_object_unref (G_OBJECT (group));
659         }
660 
661       /* check other permissions */
662       result = ((mode & oth_permissions) == 0);
663     }
664 
665   return result;
666 }
667 
668 
669 
670 static void
thunar_file_monitor_update(GFile * path,GFileMonitorEvent event_type)671 thunar_file_monitor_update (GFile             *path,
672                             GFileMonitorEvent  event_type)
673 {
674   ThunarFile *file;
675 
676   _thunar_return_if_fail (G_IS_FILE (path));
677   file = thunar_file_cache_lookup (path);
678   if (G_LIKELY (file != NULL))
679     {
680       switch (event_type)
681         {
682         case G_FILE_MONITOR_EVENT_CREATED:
683         case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
684         case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
685         case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
686         case G_FILE_MONITOR_EVENT_DELETED:
687           thunar_file_reload (file);
688           break;
689 
690         default:
691           break;
692         }
693 
694       g_object_unref (file);
695     }
696 }
697 
698 
699 
700 static void
thunar_file_move_thumbnail_cache_file(GFile * old_file,GFile * new_file)701 thunar_file_move_thumbnail_cache_file (GFile *old_file,
702                                        GFile *new_file)
703 {
704   ThunarApplication    *application;
705   ThunarThumbnailCache *thumbnail_cache;
706 
707   _thunar_return_if_fail (G_IS_FILE (old_file));
708   _thunar_return_if_fail (G_IS_FILE (new_file));
709 
710   application = thunar_application_get ();
711   thumbnail_cache = thunar_application_get_thumbnail_cache (application);
712   thunar_thumbnail_cache_move_file (thumbnail_cache, old_file, new_file);
713 
714   g_object_unref (thumbnail_cache);
715   g_object_unref (application);
716 }
717 
718 
719 
720 static void
thunar_file_monitor_moved(ThunarFile * file,GFile * renamed_file)721 thunar_file_monitor_moved (ThunarFile *file,
722                            GFile      *renamed_file)
723 {
724   GFile *previous_file;
725 
726   /* ref the old location */
727   previous_file = G_FILE (g_object_ref (G_OBJECT (file->gfile)));
728 
729   /* notify the thumbnail cache that we can now also move the thumbnail */
730   thunar_file_move_thumbnail_cache_file (previous_file, renamed_file);
731 
732   /* set the new file */
733   file->gfile = G_FILE (g_object_ref (G_OBJECT (renamed_file)));
734 
735   /* reload file information */
736   thunar_file_load (file, NULL, NULL);
737 
738   /* need to re-register the monitor handle for the new uri */
739   thunar_file_watch_reconnect (file);
740 
741   G_LOCK (file_cache_mutex);
742 
743   /* drop the previous entry from the cache */
744   g_hash_table_remove (file_cache, previous_file);
745 
746   /* drop the reference on the previous file */
747   g_object_unref (previous_file);
748 
749   /* insert the new entry */
750   g_hash_table_insert (file_cache,
751                        g_object_ref (file->gfile),
752                        weak_ref_new (G_OBJECT (file)));
753 
754   G_UNLOCK (file_cache_mutex);
755 }
756 
757 
758 
759 void
thunar_file_reload_parent(ThunarFile * file)760 thunar_file_reload_parent (ThunarFile *file)
761 {
762   ThunarFile *parent = NULL;
763 
764   _thunar_return_if_fail (THUNAR_IS_FILE (file));
765 
766   if (thunar_file_has_parent (file))
767     {
768       GFile *parent_file;
769 
770       /* only reload file if it is in cache */
771       parent_file = g_file_get_parent (file->gfile);
772       parent = thunar_file_cache_lookup (parent_file);
773       g_object_unref (parent_file);
774     }
775 
776   if (parent)
777     {
778       thunar_file_reload (parent);
779       g_object_unref (parent);
780     }
781 }
782 
783 
784 
785 static void
thunar_file_monitor(GFileMonitor * monitor,GFile * event_path,GFile * other_path,GFileMonitorEvent event_type,gpointer user_data)786 thunar_file_monitor (GFileMonitor     *monitor,
787                      GFile            *event_path,
788                      GFile            *other_path,
789                      GFileMonitorEvent event_type,
790                      gpointer          user_data)
791 {
792   ThunarFile *file = THUNAR_FILE (user_data);
793   ThunarFile *other_file;
794 
795   _thunar_return_if_fail (G_IS_FILE_MONITOR (monitor));
796   _thunar_return_if_fail (G_IS_FILE (event_path));
797   _thunar_return_if_fail (THUNAR_IS_FILE (file));
798 
799   if (g_file_equal (event_path, file->gfile))
800     {
801       /* the event occurred for the monitored ThunarFile */
802       if (event_type == G_FILE_MONITOR_EVENT_RENAMED ||
803           event_type == G_FILE_MONITOR_EVENT_MOVED_IN ||
804           event_type == G_FILE_MONITOR_EVENT_MOVED_OUT)
805         {
806           G_LOCK (file_rename_mutex);
807           thunar_file_monitor_moved (file, other_path);
808           G_UNLOCK (file_rename_mutex);
809           return;
810         }
811 
812       if (G_LIKELY (event_path))
813           thunar_file_monitor_update (event_path, event_type);
814     }
815   else
816     {
817       /* The event did not occur for the monitored ThunarFile, but for
818          a file that is contained in ThunarFile which is actually a
819          directory. */
820       if (event_type == G_FILE_MONITOR_EVENT_RENAMED ||
821           event_type == G_FILE_MONITOR_EVENT_MOVED_IN ||
822           event_type == G_FILE_MONITOR_EVENT_MOVED_OUT)
823         {
824           /* reload the target file if cached */
825           if (other_path == NULL)
826             return;
827 
828           G_LOCK (file_rename_mutex);
829 
830           other_file = thunar_file_cache_lookup (other_path);
831           if (other_file)
832               thunar_file_reload (other_file);
833           else
834               other_file = thunar_file_get (other_path, NULL);
835 
836           if (other_file != NULL)
837             {
838               /* notify the thumbnail cache that we can now also move the thumbnail */
839               thunar_file_move_thumbnail_cache_file (event_path, other_path);
840 
841               /* reload the containing target folder */
842               thunar_file_reload_parent (other_file);
843 
844               g_object_unref (other_file);
845             }
846 
847           G_UNLOCK (file_rename_mutex);
848         }
849       return;
850     }
851 }
852 
853 
854 static void
thunar_file_watch_destroyed(gpointer data)855 thunar_file_watch_destroyed (gpointer data)
856 {
857   ThunarFileWatch *file_watch = data;
858 
859   if (G_LIKELY (file_watch->monitor != NULL))
860     {
861       g_file_monitor_cancel (file_watch->monitor);
862       g_object_unref (file_watch->monitor);
863     }
864 
865   g_slice_free (ThunarFileWatch, file_watch);
866 }
867 
868 
869 
870 static void
thunar_file_watch_reconnect(ThunarFile * file)871 thunar_file_watch_reconnect (ThunarFile *file)
872 {
873   ThunarFileWatch *file_watch;
874 
875   /* recreate the monitor without changing the watch_count for file renames */
876   file_watch = g_object_get_qdata (G_OBJECT (file), thunar_file_watch_quark);
877   if (file_watch != NULL)
878     {
879       /* reset the old monitor */
880       if (G_LIKELY (file_watch->monitor != NULL))
881         {
882           g_file_monitor_cancel (file_watch->monitor);
883           g_object_unref (file_watch->monitor);
884         }
885 
886       /* create a file or directory monitor */
887       file_watch->monitor = g_file_monitor (file->gfile, G_FILE_MONITOR_WATCH_MOUNTS | G_FILE_MONITOR_SEND_MOVED, NULL, NULL);
888       if (G_LIKELY (file_watch->monitor != NULL))
889         {
890           /* watch monitor for file changes */
891           g_signal_connect (file_watch->monitor, "changed", G_CALLBACK (thunar_file_monitor), file);
892         }
893     }
894 }
895 
896 
897 
898 static void
thunar_file_set_emblem_names_ready(GObject * source_object,GAsyncResult * result,gpointer user_data)899 thunar_file_set_emblem_names_ready (GObject      *source_object,
900                                     GAsyncResult *result,
901                                     gpointer      user_data)
902 {
903   ThunarFile *file = THUNAR_FILE (user_data);
904   GError     *error = NULL;
905 
906   if (!g_file_set_attributes_finish (G_FILE (source_object), result, NULL, &error))
907     {
908       g_warning ("Failed to set metadata: %s", error->message);
909       g_error_free (error);
910 
911       g_file_info_remove_attribute (file->info, "metadata::emblems");
912     }
913 
914   thunar_file_changed (file);
915 }
916 
917 
918 
919 static void
thunar_file_info_clear(ThunarFile * file)920 thunar_file_info_clear (ThunarFile *file)
921 {
922   _thunar_return_if_fail (THUNAR_IS_FILE (file));
923 
924   /* release the current file info */
925   if (file->info != NULL)
926     {
927       g_object_unref (file->info);
928       file->info = NULL;
929     }
930 
931   /* unset */
932   file->kind = G_FILE_TYPE_UNKNOWN;
933 
934   /* free the custom icon name */
935   g_free (file->custom_icon_name);
936   file->custom_icon_name = NULL;
937 
938   /* free display name and basename */
939   g_free (file->display_name);
940   file->display_name = NULL;
941 
942   g_free (file->basename);
943   file->basename = NULL;
944 
945   /* content type */
946   g_free (file->content_type);
947   file->content_type = NULL;
948   g_free (file->icon_name);
949   file->icon_name = NULL;
950 
951   /* free collate keys */
952   if (file->collate_key_nocase != file->collate_key)
953     g_free (file->collate_key_nocase);
954   file->collate_key_nocase = NULL;
955 
956   g_free (file->collate_key);
957   file->collate_key = NULL;
958 
959   /* free thumbnail path */
960   g_free (file->thumbnail_path);
961   file->thumbnail_path = NULL;
962 
963   /* assume the file is mounted by default */
964   FLAG_SET (file, THUNAR_FILE_FLAG_IS_MOUNTED);
965 
966   /* set thumb state to unknown */
967   FLAG_SET_THUMB_STATE (file, THUNAR_FILE_THUMB_STATE_UNKNOWN);
968 }
969 
970 
971 
972 static void
thunar_file_info_reload(ThunarFile * file,GCancellable * cancellable)973 thunar_file_info_reload (ThunarFile   *file,
974                          GCancellable *cancellable)
975 {
976   const gchar *target_uri;
977   GKeyFile    *key_file;
978   gchar       *p;
979   const gchar *display_name;
980   gboolean     is_secure = FALSE;
981   gchar       *casefold;
982   gchar       *path;
983 
984   _thunar_return_if_fail (THUNAR_IS_FILE (file));
985   _thunar_return_if_fail (file->info == NULL || G_IS_FILE_INFO (file->info));
986 
987   if (G_LIKELY (file->info != NULL))
988     {
989       /* this is requested so often, cache it */
990       file->kind = g_file_info_get_file_type (file->info);
991 
992       if (file->kind == G_FILE_TYPE_MOUNTABLE)
993         {
994           target_uri = g_file_info_get_attribute_string (file->info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
995           if (target_uri != NULL
996               && !g_file_info_get_attribute_boolean (file->info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT))
997             FLAG_SET (file, THUNAR_FILE_FLAG_IS_MOUNTED);
998           else
999              FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED);
1000         }
1001     }
1002 
1003   /* determine the basename */
1004   file->basename = g_file_get_basename (file->gfile);
1005   _thunar_assert (file->basename != NULL);
1006 
1007   /* problematic files with content type reading */
1008   if (strcmp (file->basename, "kmsg") == 0
1009       && g_file_is_native (file->gfile))
1010     {
1011       path = g_file_get_path (file->gfile);
1012       if (g_strcmp0 (path, "/proc/kmsg") == 0)
1013         file->content_type = g_strdup (DEFAULT_CONTENT_TYPE);
1014       g_free (path);
1015     }
1016 
1017   /* check if this file is a desktop entry */
1018   if (thunar_file_is_desktop_file (file, &is_secure) && is_secure)
1019     {
1020       /* determine the custom icon and display name for .desktop files */
1021 
1022       /* query a key file for the .desktop file */
1023       key_file = thunar_g_file_query_key_file (file->gfile, cancellable, NULL);
1024       if (key_file != NULL)
1025         {
1026           /* read the icon name from the .desktop file */
1027           file->custom_icon_name = g_key_file_get_string (key_file,
1028                                                           G_KEY_FILE_DESKTOP_GROUP,
1029                                                           G_KEY_FILE_DESKTOP_KEY_ICON,
1030                                                           NULL);
1031 
1032           if (G_UNLIKELY (exo_str_is_empty (file->custom_icon_name)))
1033             {
1034               /* make sure we set null if the string is empty else the assertion in
1035                * thunar_icon_factory_lookup_icon() will fail */
1036               g_free (file->custom_icon_name);
1037               file->custom_icon_name = NULL;
1038             }
1039           else
1040             {
1041               /* drop any suffix (e.g. '.png') from themed icons */
1042               if (!g_path_is_absolute (file->custom_icon_name))
1043                 {
1044                   p = strrchr (file->custom_icon_name, '.');
1045                   if (p != NULL)
1046                     *p = '\0';
1047                 }
1048             }
1049           /* free the key file */
1050           g_key_file_free (key_file);
1051         }
1052     }
1053 
1054   /* determine the display name */
1055   if (file->display_name == NULL)
1056     {
1057       if (G_UNLIKELY (thunar_g_file_is_trash (file->gfile)))
1058         file->display_name = g_strdup (_("Trash"));
1059       else if (G_LIKELY (file->info != NULL))
1060         {
1061           display_name = g_file_info_get_display_name (file->info);
1062           if (G_LIKELY (display_name != NULL))
1063             {
1064               if (strcmp (display_name, "/") == 0)
1065                 file->display_name = g_strdup (_("File System"));
1066               else
1067                 file->display_name = g_strdup (display_name);
1068             }
1069         }
1070 
1071       /* fall back to a name for the gfile */
1072       if (file->display_name == NULL)
1073         file->display_name = thunar_g_file_get_display_name (file->gfile);
1074     }
1075 
1076   /* create case sensitive collation key */
1077   file->collate_key = g_utf8_collate_key_for_filename (file->display_name, -1);
1078 
1079   /* lowercase the display name */
1080   casefold = g_utf8_casefold (file->display_name, -1);
1081 
1082   /* if the lowercase name is equal, only peek the already hash key */
1083   if (casefold != NULL && strcmp (casefold, file->display_name) != 0)
1084     file->collate_key_nocase = g_utf8_collate_key_for_filename (casefold, -1);
1085   else
1086     file->collate_key_nocase = file->collate_key;
1087 
1088   /* cleanup */
1089   g_free (casefold);
1090 }
1091 
1092 
1093 
1094 static void
thunar_file_get_async_finish(GObject * object,GAsyncResult * result,gpointer user_data)1095 thunar_file_get_async_finish (GObject      *object,
1096                               GAsyncResult *result,
1097                               gpointer      user_data)
1098 {
1099   ThunarFileGetData *data = user_data;
1100   ThunarFile        *file;
1101   GFileInfo         *file_info;
1102   GError            *error = NULL;
1103   GFile             *location = G_FILE (object);
1104 
1105   _thunar_return_if_fail (G_IS_FILE (location));
1106   _thunar_return_if_fail (G_IS_ASYNC_RESULT (result));
1107 
1108   /* finish querying the file information */
1109   file_info = g_file_query_info_finish (location, result, &error);
1110 
1111   /* allocate a new file object */
1112   file = g_object_new (THUNAR_TYPE_FILE, NULL);
1113   file->gfile = g_object_ref (location);
1114 
1115   /* reset the file */
1116   thunar_file_info_clear (file);
1117 
1118   /* set the file information */
1119   file->info = file_info;
1120 
1121   /* update the file from the information */
1122   thunar_file_info_reload (file, data->cancellable);
1123 
1124   /* update the mounted info */
1125   if (error != NULL
1126       && error->domain == G_IO_ERROR
1127       && error->code == G_IO_ERROR_NOT_MOUNTED)
1128    {
1129       FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED);
1130       g_clear_error (&error);
1131    }
1132 
1133   /* insert the file into the cache */
1134   G_LOCK (file_cache_mutex);
1135   g_hash_table_insert (file_cache,
1136                        g_object_ref (file->gfile),
1137                        weak_ref_new (G_OBJECT (file)));
1138   G_UNLOCK (file_cache_mutex);
1139 
1140   /* pass the loaded file and possible errors to the return function */
1141   (data->func) (location, file, error, data->user_data);
1142 
1143   /* release the file, see description in ThunarFileGetFunc */
1144   g_object_unref (file);
1145 
1146   /* free the error, if there is any */
1147   if (error != NULL)
1148     g_error_free (error);
1149 
1150   /* release the get data */
1151   if (data->cancellable != NULL)
1152     g_object_unref (data->cancellable);
1153   g_slice_free (ThunarFileGetData, data);
1154 }
1155 
1156 
1157 
1158 /**
1159  * thunar_file_load:
1160  * @file        : a #ThunarFile.
1161  * @cancellable : a #GCancellable.
1162  * @error       : return location for errors or %NULL.
1163  *
1164  * Loads all information about the file. As this is a possibly
1165  * blocking call, it can be cancelled using @cancellable.
1166  *
1167  * If loading the file fails or the operation is cancelled,
1168  * @error will be set.
1169  *
1170  * Return value: %TRUE on success, %FALSE on error or interruption.
1171  **/
1172 static gboolean
thunar_file_load(ThunarFile * file,GCancellable * cancellable,GError ** error)1173 thunar_file_load (ThunarFile   *file,
1174                   GCancellable *cancellable,
1175                   GError      **error)
1176 {
1177   GError *err = NULL;
1178 
1179   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
1180   _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1181   _thunar_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
1182   _thunar_return_val_if_fail (G_IS_FILE (file->gfile), FALSE);
1183 
1184   /* remove the file from cache */
1185   G_LOCK (file_cache_mutex);
1186   if (g_hash_table_lookup (file_cache, file->gfile) != NULL)
1187     g_hash_table_remove (file_cache, file->gfile);
1188   G_UNLOCK (file_cache_mutex);
1189 
1190   /* reset the file */
1191   thunar_file_info_clear (file);
1192 
1193   /* query a new file info */
1194   file->info = g_file_query_info (file->gfile,
1195                                   THUNARX_FILE_INFO_NAMESPACE,
1196                                   G_FILE_QUERY_INFO_NONE,
1197                                   cancellable, &err);
1198 
1199   /* update the file from the information */
1200   thunar_file_info_reload (file, cancellable);
1201 
1202   /* update the mounted info */
1203   if (err != NULL
1204       && err->domain == G_IO_ERROR
1205       && err->code == G_IO_ERROR_NOT_MOUNTED)
1206    {
1207       FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED);
1208       g_clear_error (&err);
1209    }
1210 
1211   if (err != NULL)
1212     {
1213       g_propagate_error (error, err);
1214       return FALSE;
1215     }
1216 
1217   /* (re)insert the file into the cache */
1218   if (file != NULL && file->kind != G_FILE_TYPE_UNKNOWN)
1219     {
1220       G_LOCK (file_cache_mutex);
1221       g_hash_table_insert (file_cache,
1222                            g_object_ref (file->gfile),
1223                            weak_ref_new (G_OBJECT (file)));
1224       G_UNLOCK (file_cache_mutex);
1225     }
1226   return TRUE;
1227 }
1228 
1229 
1230 
1231 /**
1232  * thunar_file_get:
1233  * @file  : a #GFile.
1234  * @error : return location for errors.
1235  *
1236  * Looks up the #ThunarFile referred to by @file. This function may return a
1237  * ThunarFile even though the file doesn't actually exist. This is the case
1238  * with remote URIs (like SFTP) for instance, if they are not mounted.
1239  *
1240  * The caller is responsible to call g_object_unref()
1241  * when done with the returned object.
1242  *
1243  * Return value: the #ThunarFile for @file or %NULL on errors.
1244  **/
1245 ThunarFile*
thunar_file_get(GFile * gfile,GError ** error)1246 thunar_file_get (GFile   *gfile,
1247                  GError **error)
1248 {
1249   ThunarFile *file;
1250 
1251   _thunar_return_val_if_fail (G_IS_FILE (gfile), NULL);
1252 
1253   /* check if we already have a cached version of that file */
1254   file = thunar_file_cache_lookup (gfile);
1255   if (G_UNLIKELY (file != NULL))
1256     {
1257       /* return the file, it already has an additional ref set
1258        * in thunar_file_cache_lookup */
1259     }
1260   else
1261     {
1262       /* allocate a new object */
1263       file = g_object_new (THUNAR_TYPE_FILE, NULL);
1264       file->gfile = g_object_ref (gfile);
1265 
1266       if (thunar_file_load (file, NULL, error))
1267         {
1268           /* setup lock until the file is inserted */
1269           G_LOCK (file_cache_mutex);
1270 
1271           /* insert the file into the cache */
1272           g_hash_table_insert (file_cache,
1273                                g_object_ref (file->gfile),
1274                                weak_ref_new (G_OBJECT (file)));
1275 
1276           /* done inserting in the cache */
1277           G_UNLOCK (file_cache_mutex);
1278         }
1279       else
1280         {
1281           /* failed loading, destroy the file */
1282           g_object_unref (file);
1283 
1284           /* make sure we return NULL */
1285           file = NULL;
1286         }
1287     }
1288 
1289   return file;
1290 }
1291 
1292 
1293 /**
1294  * thunar_file_get_with_info:
1295  * @uri         : an URI or an absolute filename.
1296  * @info        : #GFileInfo to use when loading the info.
1297  * @not_mounted : if the file is mounted.
1298  *
1299  * Looks up the #ThunarFile referred to by @file. This function may return a
1300  * ThunarFile even though the file doesn't actually exist. This is the case
1301  * with remote URIs (like SFTP) for instance, if they are not mounted.
1302  *
1303  * This function does not use g_file_query_info() to get the info,
1304  * but takes a reference on the @info,
1305  *
1306  * The caller is responsible to call g_object_unref()
1307  * when done with the returned object.
1308  *
1309  * Return value: the #ThunarFile for @file or %NULL on errors.
1310  **/
1311 ThunarFile *
thunar_file_get_with_info(GFile * gfile,GFileInfo * info,gboolean not_mounted)1312 thunar_file_get_with_info (GFile     *gfile,
1313                            GFileInfo *info,
1314                            gboolean   not_mounted)
1315 {
1316   ThunarFile *file;
1317 
1318   _thunar_return_val_if_fail (G_IS_FILE (gfile), NULL);
1319   _thunar_return_val_if_fail (G_IS_FILE_INFO (info), NULL);
1320 
1321   /* check if we already have a cached version of that file */
1322   file = thunar_file_cache_lookup (gfile);
1323   if (G_UNLIKELY (file != NULL))
1324     {
1325       /* return the file, it already has an additional ref set
1326        * in thunar_file_cache_lookup */
1327     }
1328   else
1329     {
1330       /* allocate a new object */
1331       file = g_object_new (THUNAR_TYPE_FILE, NULL);
1332       file->gfile = g_object_ref (gfile);
1333 
1334       /* reset the file */
1335       thunar_file_info_clear (file);
1336 
1337       /* set the passed info */
1338       file->info = g_object_ref (info);
1339 
1340       /* update the file from the information */
1341       thunar_file_info_reload (file, NULL);
1342 
1343       /* update the mounted info */
1344       if (not_mounted)
1345         FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED);
1346 
1347       /* setup lock until the file is inserted */
1348       G_LOCK (file_cache_mutex);
1349 
1350       /* insert the file into the cache */
1351       g_hash_table_insert (file_cache,
1352                            g_object_ref (file->gfile),
1353                            weak_ref_new (G_OBJECT (file)));
1354 
1355       /* done inserting in the cache */
1356       G_UNLOCK (file_cache_mutex);
1357     }
1358 
1359   return file;
1360 }
1361 
1362 
1363 
1364 
1365 
1366 /**
1367  * thunar_file_get_for_uri:
1368  * @uri   : an URI or an absolute filename.
1369  * @error : return location for errors or %NULL.
1370  *
1371  * Convenience wrapper function for thunar_file_get_for_path(), as its
1372  * often required to determine a #ThunarFile for a given @uri.
1373  *
1374  * The caller is responsible to free the returned object using
1375  * g_object_unref() when no longer needed.
1376  *
1377  * Return value: the #ThunarFile for the given @uri or %NULL if
1378  *               unable to determine.
1379  **/
1380 ThunarFile*
thunar_file_get_for_uri(const gchar * uri,GError ** error)1381 thunar_file_get_for_uri (const gchar *uri,
1382                          GError     **error)
1383 {
1384   ThunarFile *file;
1385   GFile      *path;
1386 
1387   _thunar_return_val_if_fail (uri != NULL, NULL);
1388   _thunar_return_val_if_fail (error == NULL || *error == NULL, NULL);
1389 
1390   path = g_file_new_for_commandline_arg (uri);
1391   file = thunar_file_get (path, error);
1392   g_object_unref (path);
1393 
1394   return file;
1395 }
1396 
1397 
1398 
1399 /**
1400  * thunar_file_get_async:
1401  **/
1402 void
thunar_file_get_async(GFile * location,GCancellable * cancellable,ThunarFileGetFunc func,gpointer user_data)1403 thunar_file_get_async (GFile            *location,
1404                        GCancellable     *cancellable,
1405                        ThunarFileGetFunc func,
1406                        gpointer          user_data)
1407 {
1408   ThunarFile        *file;
1409   ThunarFileGetData *data;
1410 
1411   _thunar_return_if_fail (G_IS_FILE (location));
1412   _thunar_return_if_fail (func != NULL);
1413 
1414   /* check if we already have a cached version of that file */
1415   file = thunar_file_cache_lookup (location);
1416   if (G_UNLIKELY (file != NULL))
1417     {
1418       /* call the return function with the file from the cache */
1419       (func) (location, file, NULL, user_data);
1420       g_object_unref (file);
1421     }
1422   else
1423     {
1424       /* allocate get data */
1425       data = g_slice_new0 (ThunarFileGetData);
1426       data->user_data = user_data;
1427       data->func = func;
1428       if (cancellable != NULL)
1429         data->cancellable = g_object_ref (cancellable);
1430 
1431       /* load the file information asynchronously */
1432       g_file_query_info_async (location,
1433                                THUNARX_FILE_INFO_NAMESPACE,
1434                                G_FILE_QUERY_INFO_NONE,
1435                                G_PRIORITY_DEFAULT,
1436                                cancellable,
1437                                thunar_file_get_async_finish,
1438                                data);
1439     }
1440 }
1441 
1442 
1443 
1444 /**
1445  * thunar_file_get_file:
1446  * @file : a #ThunarFile instance.
1447  *
1448  * Returns the #GFile that refers to the location of @file.
1449  *
1450  * The returned #GFile is owned by @file and must not be released
1451  * with g_object_unref().
1452  *
1453  * Return value: the #GFile corresponding to @file.
1454  **/
1455 GFile *
thunar_file_get_file(const ThunarFile * file)1456 thunar_file_get_file (const ThunarFile *file)
1457 {
1458   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
1459   _thunar_return_val_if_fail (G_IS_FILE (file->gfile), NULL);
1460   return file->gfile;
1461 }
1462 
1463 
1464 
1465 /**
1466  * thunar_file_get_info:
1467  * @file : a #ThunarFile instance.
1468  *
1469  * Returns the #GFileInfo for @file.
1470  *
1471  * Note, that there's no reference taken for the caller on the
1472  * returned #GFileInfo, so if you need the object for a longer
1473  * perioud, you'll need to take a reference yourself using the
1474  * g_object_ref() method.
1475  *
1476  * Return value: the #GFileInfo for @file or %NULL.
1477  **/
1478 GFileInfo *
thunar_file_get_info(const ThunarFile * file)1479 thunar_file_get_info (const ThunarFile *file)
1480 {
1481   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
1482   _thunar_return_val_if_fail (file->info == NULL || G_IS_FILE_INFO (file->info), NULL);
1483   return file->info;
1484 }
1485 
1486 
1487 
1488 /**
1489  * thunar_file_get_parent:
1490  * @file  : a #ThunarFile instance.
1491  * @error : return location for errors.
1492  *
1493  * Determines the parent #ThunarFile for @file. If @file has no parent or
1494  * the user is not allowed to open the parent folder of @file, %NULL will
1495  * be returned and @error will be set to point to a #GError that
1496  * describes the cause. Else, the #ThunarFile will be returned, and
1497  * the caller must call g_object_unref() on it.
1498  *
1499  * You may want to call thunar_file_has_parent() first to
1500  * determine whether @file has a parent.
1501  *
1502  * Return value: the parent #ThunarFile or %NULL.
1503  **/
1504 ThunarFile*
thunar_file_get_parent(const ThunarFile * file,GError ** error)1505 thunar_file_get_parent (const ThunarFile *file,
1506                         GError          **error)
1507 {
1508   ThunarFile *parent = NULL;
1509   GFile      *parent_file;
1510 
1511   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
1512   _thunar_return_val_if_fail (error == NULL || *error == NULL, NULL);
1513 
1514   parent_file = g_file_get_parent (file->gfile);
1515 
1516   if (parent_file == NULL)
1517     {
1518       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, _("The root folder has no parent"));
1519       return NULL;
1520     }
1521 
1522   parent = thunar_file_get (parent_file, error);
1523   g_object_unref (parent_file);
1524 
1525   return parent;
1526 }
1527 
1528 
1529 
1530 /**
1531  * thunar_file_check_loaded:
1532  * @file              : a #ThunarFile instance.
1533  *
1534  * Check if @file has its information loaded, if not, try this once else
1535  * return %FALSE.
1536  *
1537  * Return value: %TRUE on success, else %FALSE.
1538  **/
1539 gboolean
thunar_file_check_loaded(ThunarFile * file)1540 thunar_file_check_loaded (ThunarFile *file)
1541 {
1542   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
1543 
1544   if (G_UNLIKELY (file->info == NULL))
1545     thunar_file_load (file, NULL, NULL);
1546 
1547   return (file->info != NULL);
1548 }
1549 
1550 
1551 
1552 /**
1553  * thunar_file_execute:
1554  * @file              : a #ThunarFile instance.
1555  * @working_directory : the working directory used to resolve relative filenames
1556  *                      in @file_list.
1557  * @parent            : %NULL, a #GdkScreen or #GtkWidget.
1558  * @file_list         : the list of #GFile<!---->s to supply to @file on execution.
1559  * @startup_id        : startup id for the new window (send over for dbus) or %NULL.
1560  * @error             : return location for errors or %NULL.
1561  *
1562  * Tries to execute @file on the specified @screen. If @file is executable
1563  * and could have been spawned successfully, %TRUE is returned, else %FALSE
1564  * will be returned and @error will be set to point to the error location.
1565  *
1566  * Return value: %TRUE on success, else %FALSE.
1567  **/
1568 gboolean
thunar_file_execute(ThunarFile * file,GFile * working_directory,gpointer parent,GList * file_list,const gchar * startup_id,GError ** error)1569 thunar_file_execute (ThunarFile  *file,
1570                      GFile       *working_directory,
1571                      gpointer     parent,
1572                      GList       *file_list,
1573                      const gchar *startup_id,
1574                      GError     **error)
1575 {
1576   gboolean    snotify = FALSE;
1577   gboolean    terminal;
1578   gboolean    result = FALSE;
1579   GKeyFile   *key_file;
1580   GError     *err = NULL;
1581   GFile      *file_parent;
1582   GList      *li;
1583   GSList     *uri_list = NULL;
1584   gchar      *icon_name = NULL;
1585   gchar      *name;
1586   gchar      *type;
1587   gchar      *url;
1588   gchar      *location;
1589   gchar      *escaped_location;
1590   gchar     **argv = NULL;
1591   gchar      *exec;
1592   gchar      *command;
1593   gchar      *directory = NULL;
1594   gboolean    is_secure = FALSE;
1595   guint32     stimestamp = 0;
1596 
1597   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
1598   _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1599 
1600   location = thunar_g_file_get_location (file->gfile);
1601   for (li = file_list; li != NULL; li = li->next)
1602     uri_list = g_slist_prepend (uri_list, g_file_get_uri (li->data));
1603   uri_list = g_slist_reverse (uri_list);
1604 
1605   if (thunar_file_is_desktop_file (file, &is_secure))
1606     {
1607       /* parse file first, even if it is insecure */
1608       key_file = thunar_g_file_query_key_file (file->gfile, NULL, &err);
1609       if (key_file == NULL)
1610         {
1611           g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
1612                        _("Failed to parse the desktop file: %s"), err->message);
1613           g_error_free (err);
1614           return FALSE;
1615         }
1616 
1617       type = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, NULL);
1618       if (G_LIKELY (exo_str_is_equal (type, "Application")))
1619         {
1620           exec = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
1621           if (G_LIKELY (exec != NULL))
1622             {
1623               /* if the .desktop file is not secure, ask user what to do */
1624               if (is_secure || thunar_dialogs_show_insecure_program (parent, _("Untrusted application launcher"), file, exec))
1625                 {
1626                   /* parse other fields */
1627                   name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
1628                   icon_name = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
1629                   directory = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL);
1630                   terminal = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL);
1631                   snotify = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL);
1632 
1633                   /* expand the field codes and parse the execute command */
1634                   command = xfce_expand_desktop_entry_field_codes (exec, uri_list, icon_name,
1635                                                                    name, location, terminal);
1636                   g_free (name);
1637                   result = g_shell_parse_argv (command, NULL, &argv, error);
1638                   g_free (command);
1639                 }
1640               else
1641                 {
1642                   /* fall-through to free value and leave without execution */
1643                   result = TRUE;
1644                 }
1645 
1646               g_free (exec);
1647             }
1648           else
1649             {
1650               /* TRANSLATORS: `Exec' is a field name in a .desktop file. Don't translate it. */
1651               g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
1652                            _("No Exec field specified"));
1653             }
1654         }
1655       else if (exo_str_is_equal (type, "Link"))
1656         {
1657           url = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, NULL);
1658           if (G_LIKELY (url != NULL))
1659             {
1660               /* if the .desktop file is not secure, ask user what to do */
1661               if (is_secure || thunar_dialogs_show_insecure_program (parent, _("Untrusted link launcher"), file, url))
1662                 {
1663                   /* pass the URL to the webbrowser, this could be a bit strange,
1664                    * but then at least we are on the secure side */
1665                   argv = g_new (gchar *, 3);
1666                   argv[0] = g_strdup ("exo-open");
1667                   argv[1] = url;
1668                   argv[2] = NULL;
1669                 }
1670 
1671               result = TRUE;
1672             }
1673           else
1674             {
1675               /* TRANSLATORS: `URL' is a field name in a .desktop file. Don't translate it. */
1676               g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
1677                            _("No URL field specified"));
1678             }
1679         }
1680       else
1681         {
1682           g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Invalid desktop file"));
1683         }
1684 
1685       g_free (type);
1686       g_key_file_free (key_file);
1687     }
1688   else
1689     {
1690       /* fake the Exec line */
1691       escaped_location = g_shell_quote (location);
1692       exec = g_strconcat (escaped_location, " %F", NULL);
1693       command = xfce_expand_desktop_entry_field_codes (exec, uri_list, NULL, NULL, NULL, FALSE);
1694       result = g_shell_parse_argv (command, NULL, &argv, error);
1695       g_free (escaped_location);
1696       g_free (exec);
1697       g_free (command);
1698     }
1699 
1700   if (G_LIKELY (result && argv != NULL))
1701     {
1702       /* use other directory if the Path from the desktop file was not set */
1703       if (G_LIKELY (directory == NULL))
1704         {
1705           /* determine the working directory */
1706           if (G_LIKELY (working_directory != NULL))
1707             {
1708               /* copy the working directory provided to this method */
1709               directory = g_file_get_path (working_directory);
1710             }
1711           else if (file_list != NULL)
1712             {
1713               /* use the directory of the first list item */
1714               file_parent = g_file_get_parent (file_list->data);
1715               directory = (file_parent != NULL) ? thunar_g_file_get_location (file_parent) : NULL;
1716               g_object_unref (file_parent);
1717             }
1718           else
1719             {
1720               /* use the directory of the executable file */
1721               parent = g_file_get_parent (file->gfile);
1722               directory = (parent != NULL) ? thunar_g_file_get_location (parent) : NULL;
1723               g_object_unref (parent);
1724             }
1725         }
1726 
1727       /* check if a startup id was passed (launch request over dbus) */
1728       if (startup_id != NULL && *startup_id != '\0')
1729         {
1730           /* parse startup_id string and extract timestamp
1731            * format: <unique>_TIME<timestamp>) */
1732           gchar *time_str = g_strrstr (startup_id, "_TIME");
1733           if (time_str != NULL)
1734             {
1735               gchar *end;
1736 
1737               /* ignore the "_TIME" part */
1738               time_str += 5;
1739 
1740               stimestamp = strtoul (time_str, &end, 0);
1741               if (end == time_str)
1742                 stimestamp = 0;
1743             }
1744         }
1745       else
1746         {
1747           /* use current event time */
1748           stimestamp = gtk_get_current_event_time ();
1749         }
1750 
1751       /* execute the command */
1752       result = xfce_spawn_on_screen (thunar_util_parse_parent (parent, NULL),
1753                                      directory, argv, NULL, G_SPAWN_SEARCH_PATH,
1754                                      snotify, stimestamp, icon_name, error);
1755     }
1756 
1757   /* clean up */
1758   g_slist_free_full (uri_list, g_free);
1759   g_strfreev (argv);
1760   g_free (location);
1761   g_free (directory);
1762   g_free (icon_name);
1763 
1764   return result;
1765 }
1766 
1767 
1768 
1769 /**
1770  * thunar_file_launch:
1771  * @file       : a #ThunarFile instance.
1772  * @parent     : a #GtkWidget or a #GdkScreen on which to launch the @file.
1773  *               May also be %NULL in which case the default #GdkScreen will
1774  *               be used.
1775  * @startup_id : startup id for the new window (send over for dbus) or %NULL.
1776  * @error      : return location for errors or %NULL.
1777  *
1778  * If @file is an executable file, tries to execute it. Else if @file is
1779  * a directory, opens a new #ThunarWindow to display the directory. Else,
1780  * the default handler for @file is determined and run.
1781  *
1782  * The @parent can be either a #GtkWidget or a #GdkScreen, on which to
1783  * launch the @file. If @parent is a #GtkWidget, the chooser dialog (if
1784  * no default application is available for @file) will be transient for
1785  * @parent. Else if @parent is a #GdkScreen it specifies the screen on
1786  * which to launch @file.
1787  *
1788  * Return value: %TRUE on success, else %FALSE.
1789  **/
1790 gboolean
thunar_file_launch(ThunarFile * file,gpointer parent,const gchar * startup_id,GError ** error)1791 thunar_file_launch (ThunarFile  *file,
1792                     gpointer     parent,
1793                     const gchar *startup_id,
1794                     GError     **error)
1795 {
1796   GdkAppLaunchContext *context;
1797   ThunarApplication   *application;
1798   GAppInfo            *app_info;
1799   gboolean             succeed;
1800   GList                path_list;
1801   GdkScreen           *screen;
1802 
1803   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
1804   _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1805   _thunar_return_val_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent), FALSE);
1806 
1807   screen = thunar_util_parse_parent (parent, NULL);
1808 
1809   /* check if we have a folder here */
1810   if (thunar_file_is_directory (file))
1811     {
1812       application = thunar_application_get ();
1813       thunar_application_open_window (application, file, screen, startup_id, FALSE);
1814       g_object_unref (G_OBJECT (application));
1815       return TRUE;
1816     }
1817 
1818   /* check if we should execute the file */
1819   if (thunar_file_is_executable (file))
1820     return thunar_file_execute (file, NULL, parent, NULL, NULL, error);
1821 
1822   /* determine the default application to open the file */
1823   /* TODO We should probably add a cancellable argument to thunar_file_launch() */
1824   app_info = thunar_file_get_default_handler (THUNAR_FILE (file));
1825 
1826   /* display the application chooser if no application is defined for this file
1827    * type yet */
1828   if (G_UNLIKELY (app_info == NULL))
1829     {
1830       thunar_show_chooser_dialog (parent, file, TRUE);
1831       return TRUE;
1832     }
1833 
1834   /* HACK: check if we're not trying to launch another file manager again, possibly
1835    * ourselfs which will end in a loop */
1836   if (g_strcmp0 (g_app_info_get_id (app_info), "exo-file-manager.desktop") == 0
1837       || g_strcmp0 (g_app_info_get_id (app_info), "thunar.desktop") == 0
1838       || g_strcmp0 (g_app_info_get_name (app_info), "exo-file-manager") == 0)
1839     {
1840       g_object_unref (G_OBJECT (app_info));
1841       thunar_show_chooser_dialog (parent, file, TRUE);
1842       return TRUE;
1843     }
1844 
1845   /* fake a path list */
1846   path_list.data = file->gfile;
1847   path_list.next = path_list.prev = NULL;
1848 
1849   /* create a launch context */
1850   context = gdk_display_get_app_launch_context (gdk_screen_get_display (screen));
1851   gdk_app_launch_context_set_screen (context, screen);
1852   gdk_app_launch_context_set_timestamp (context, gtk_get_current_event_time ());
1853 
1854   /* otherwise try to execute the application */
1855   succeed = g_app_info_launch (app_info, &path_list, G_APP_LAUNCH_CONTEXT (context), error);
1856 
1857   /* destroy the launch context */
1858   g_object_unref (context);
1859 
1860   /* release the handler reference */
1861   g_object_unref (G_OBJECT (app_info));
1862 
1863   return succeed;
1864 }
1865 
1866 
1867 
1868 /**
1869  * thunar_file_rename:
1870  * @file  : a #ThunarFile instance.
1871  * @name  : the new file name in UTF-8 encoding.
1872  * @error : return location for errors or %NULL.
1873  *
1874  * Tries to rename @file to the new @name. If @file cannot be renamed,
1875  * %FALSE will be returned and @error will be set accordingly. Else, if
1876  * the operation succeeds, %TRUE will be returned, and @file will have
1877  * a new URI and a new display name.
1878  *
1879  * When offering a rename action in the user interface, the implementation
1880  * should first check whether the file is available, using the
1881  * thunar_file_is_renameable() method.
1882  *
1883  * Return value: %TRUE on success, else %FALSE.
1884  **/
1885 gboolean
thunar_file_rename(ThunarFile * file,const gchar * name,GCancellable * cancellable,gboolean called_from_job,GError ** error)1886 thunar_file_rename (ThunarFile   *file,
1887                     const gchar  *name,
1888                     GCancellable *cancellable,
1889                     gboolean      called_from_job,
1890                     GError      **error)
1891 {
1892   GFile                *renamed_file;
1893 
1894   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
1895   _thunar_return_val_if_fail (g_utf8_validate (name, -1, NULL), FALSE);
1896   _thunar_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
1897   _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1898 
1899   G_LOCK (file_rename_mutex);
1900   /* try to rename the file */
1901   renamed_file = g_file_set_display_name (file->gfile, name, cancellable, error);
1902 
1903   /* check if we succeeded */
1904   if (renamed_file != NULL)
1905     {
1906       /* notify the file is renamed */
1907       thunar_file_monitor_moved (file, renamed_file);
1908 
1909       g_object_unref (G_OBJECT (renamed_file));
1910 
1911       if (!called_from_job)
1912         {
1913           /* emit the file changed signal */
1914           thunar_file_changed (file);
1915         }
1916       G_UNLOCK (file_rename_mutex);
1917       return TRUE;
1918     }
1919   else
1920     {
1921       G_UNLOCK (file_rename_mutex);
1922       return FALSE;
1923     }
1924 }
1925 
1926 
1927 
1928 /**
1929  * thunar_file_accepts_drop:
1930  * @file                    : a #ThunarFile instance.
1931  * @file_list               : the list of #GFile<!---->s that will be dropped.
1932  * @context                 : the current #GdkDragContext, which is used for the drop.
1933  * @suggested_action_return : return location for the suggested #GdkDragAction or %NULL.
1934  *
1935  * Checks whether @file can accept @file_list for the given @context and
1936  * returns the #GdkDragAction<!---->s that can be used or 0 if no actions
1937  * apply.
1938  *
1939  * If any #GdkDragAction<!---->s apply and @suggested_action_return is not
1940  * %NULL, the suggested #GdkDragAction for this drop will be stored to the
1941  * location pointed to by @suggested_action_return.
1942  *
1943  * Return value: the #GdkDragAction<!---->s supported for the drop or
1944  *               0 if no drop is possible.
1945  **/
1946 GdkDragAction
thunar_file_accepts_drop(ThunarFile * file,GList * file_list,GdkDragContext * context,GdkDragAction * suggested_action_return)1947 thunar_file_accepts_drop (ThunarFile     *file,
1948                           GList          *file_list,
1949                           GdkDragContext *context,
1950                           GdkDragAction  *suggested_action_return)
1951 {
1952   GdkDragAction suggested_action;
1953   GdkDragAction actions;
1954   ThunarFile   *ofile;
1955   GFile        *parent_file;
1956   ThunarFile   *parent_thunar_file;
1957   GList        *lp;
1958 
1959   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0);
1960   _thunar_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), 0);
1961 
1962   /* we can never drop an empty list */
1963   if (G_UNLIKELY (file_list == NULL))
1964     return 0;
1965 
1966   /* default to whatever GTK+ thinks for the suggested action */
1967   suggested_action = gdk_drag_context_get_suggested_action (context);
1968 
1969   /* get the possible actions */
1970   actions = gdk_drag_context_get_actions (context);
1971 
1972   /* when the option to ask the user is set, make it the preferred action */
1973   if (G_UNLIKELY ((actions & GDK_ACTION_ASK) != 0))
1974     suggested_action = GDK_ACTION_ASK;
1975 
1976   /* check if we have a writable directory here or an executable file */
1977   if (thunar_file_is_directory (file) && thunar_file_is_writable (file))
1978     {
1979       /* determine the possible actions */
1980       actions &= (GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK);
1981 
1982       /* cannot create symbolic links in the trash or copy to the trash */
1983       if (thunar_file_is_trashed (file))
1984         actions &= ~(GDK_ACTION_COPY | GDK_ACTION_LINK);
1985 
1986       for (lp = file_list; lp != NULL; lp = lp->next)
1987         {
1988           /* we cannot drop a file on itself */
1989           if (G_UNLIKELY (g_file_equal (file->gfile, lp->data)))
1990             return 0;
1991 
1992           /* check whether source and destination are the same */
1993           parent_file = g_file_get_parent (lp->data);
1994           if (G_LIKELY (parent_file != NULL))
1995             {
1996               if (g_file_equal (file->gfile, parent_file))
1997                 {
1998                   g_object_unref (parent_file);
1999                   return 0;
2000                 }
2001               else
2002                 g_object_unref (parent_file);
2003             }
2004 
2005           /* copy/move/link within the trash not possible */
2006           if (G_UNLIKELY (thunar_g_file_is_trashed (lp->data) && thunar_file_is_trashed (file)))
2007             return 0;
2008         }
2009 
2010       /* if the source offers both copy and move and the GTK+ suggested action is copy, try to be smart telling whether
2011        * we should copy or move by default by checking whether the source and target are on the same disk.
2012        */
2013       if ((actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE)) != 0
2014           && (suggested_action == GDK_ACTION_COPY))
2015         {
2016           /* default to move as suggested action */
2017           suggested_action = GDK_ACTION_MOVE;
2018 
2019           for (lp = file_list; lp != NULL; lp = lp->next)
2020             {
2021               /* dropping from the trash always suggests move */
2022               if (G_UNLIKELY (thunar_g_file_is_trashed (lp->data)))
2023                 break;
2024 
2025               /* determine the cached version of the source file */
2026               ofile = thunar_file_cache_lookup (lp->data);
2027 
2028               /* fallback to non-cached version */
2029               if (ofile == NULL)
2030                 ofile = thunar_file_get (lp->data, NULL);
2031 
2032               /* we only can 'move' if we know the source folder is writable (i.e. the file can be deleted)
2033                * and both the source and the target are on the same disk.
2034                */
2035               if (ofile == NULL)
2036                 {
2037                   suggested_action = GDK_ACTION_COPY;
2038                   break;
2039                 }
2040               parent_thunar_file = thunar_file_get_parent (ofile, NULL);
2041               if (parent_thunar_file == NULL || !thunar_file_is_writable (parent_thunar_file) || !thunar_file_same_filesystem (file, ofile))
2042                 {
2043                   /* default to copy and get outta here */
2044                   suggested_action = GDK_ACTION_COPY;
2045                   g_object_unref (parent_thunar_file);
2046                   break;
2047                 }
2048               if (parent_thunar_file != NULL)
2049                 g_object_unref (parent_thunar_file);
2050 
2051               if (ofile != NULL)
2052                 g_object_unref (ofile);
2053             }
2054         }
2055     }
2056   else if (thunar_file_is_executable (file))
2057     {
2058       /* determine the possible actions */
2059       actions &= (GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
2060     }
2061   else
2062     return 0;
2063 
2064   /* determine the preferred action based on the context */
2065   if (G_LIKELY (suggested_action_return != NULL))
2066     {
2067       /* determine a working action */
2068       if (G_LIKELY ((suggested_action & actions) != 0))
2069         *suggested_action_return = suggested_action;
2070       else if ((actions & GDK_ACTION_ASK) != 0)
2071         *suggested_action_return = GDK_ACTION_ASK;
2072       else if ((actions & GDK_ACTION_COPY) != 0)
2073         *suggested_action_return = GDK_ACTION_COPY;
2074       else if ((actions & GDK_ACTION_LINK) != 0)
2075         *suggested_action_return = GDK_ACTION_LINK;
2076       else if ((actions & GDK_ACTION_MOVE) != 0)
2077         *suggested_action_return = GDK_ACTION_MOVE;
2078       else
2079         *suggested_action_return = GDK_ACTION_PRIVATE;
2080     }
2081 
2082   /* yeppa, we can drop here */
2083   return actions;
2084 }
2085 
2086 
2087 
2088 /**
2089  * thunar_file_get_date:
2090  * @file        : a #ThunarFile instance.
2091  * @date_type   : the kind of date you are interested in.
2092  *
2093  * Queries the given @date_type from @file and returns the result.
2094  *
2095  * Return value: the time for @file of the given @date_type.
2096  **/
2097 guint64
thunar_file_get_date(const ThunarFile * file,ThunarFileDateType date_type)2098 thunar_file_get_date (const ThunarFile  *file,
2099                       ThunarFileDateType date_type)
2100 {
2101   const gchar *attribute;
2102 
2103   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0);
2104 
2105   if (file->info == NULL)
2106     return 0;
2107 
2108   switch (date_type)
2109     {
2110     case THUNAR_FILE_DATE_ACCESSED:
2111       attribute = G_FILE_ATTRIBUTE_TIME_ACCESS;
2112       break;
2113     case THUNAR_FILE_DATE_CHANGED:
2114       attribute = G_FILE_ATTRIBUTE_TIME_CHANGED;
2115       break;
2116     case THUNAR_FILE_DATE_MODIFIED:
2117       attribute = G_FILE_ATTRIBUTE_TIME_MODIFIED;
2118       break;
2119     default:
2120       _thunar_assert_not_reached ();
2121     }
2122 
2123   return g_file_info_get_attribute_uint64 (file->info, attribute);
2124 }
2125 
2126 
2127 
2128 /**
2129  * thunar_file_get_date_string:
2130  * @file       : a #ThunarFile instance.
2131  * @date_type  : the kind of date you are interested to know about @file.
2132  * @date_style : the style used to format the date.
2133  * @date_custom_style : custom style to apply, if @date_style is set to custom
2134  *
2135  * Tries to determine the @date_type of @file, and if @file supports the
2136  * given @date_type, it'll be formatted as string and returned. The
2137  * caller is responsible for freeing the string using the g_free()
2138  * function.
2139  *
2140  * Return value: the @date_type of @file formatted as string.
2141  **/
2142 gchar*
thunar_file_get_date_string(const ThunarFile * file,ThunarFileDateType date_type,ThunarDateStyle date_style,const gchar * date_custom_style)2143 thunar_file_get_date_string (const ThunarFile  *file,
2144                              ThunarFileDateType date_type,
2145                              ThunarDateStyle    date_style,
2146                              const gchar       *date_custom_style)
2147 {
2148   return thunar_util_humanize_file_time (thunar_file_get_date (file, date_type), date_style, date_custom_style);
2149 }
2150 
2151 
2152 
2153 /**
2154  * thunar_file_get_mode_string:
2155  * @file : a #ThunarFile instance.
2156  *
2157  * Returns the mode of @file as text. You'll need to free
2158  * the result using g_free() when you're done with it.
2159  *
2160  * Return value: the mode of @file as string.
2161  **/
2162 gchar*
thunar_file_get_mode_string(const ThunarFile * file)2163 thunar_file_get_mode_string (const ThunarFile *file)
2164 {
2165   ThunarFileMode mode;
2166   GFileType      kind;
2167   gchar         *text;
2168 
2169   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2170 
2171   kind = thunar_file_get_kind (file);
2172   mode = thunar_file_get_mode (file);
2173   text = g_new (gchar, 11);
2174 
2175   /* file type */
2176   /* TODO earlier versions of Thunar had 'P' for ports and
2177    * 'D' for doors. Do we still need those? */
2178   switch (kind)
2179     {
2180     case G_FILE_TYPE_SYMBOLIC_LINK: text[0] = 'l'; break;
2181     case G_FILE_TYPE_REGULAR:       text[0] = '-'; break;
2182     case G_FILE_TYPE_DIRECTORY:     text[0] = 'd'; break;
2183     case G_FILE_TYPE_SPECIAL:
2184     case G_FILE_TYPE_UNKNOWN:
2185     default:
2186       if (S_ISCHR (mode))
2187         text[0] = 'c';
2188       else if (S_ISSOCK (mode))
2189         text[0] = 's';
2190       else if (S_ISFIFO (mode))
2191         text[0] = 'f';
2192       else if (S_ISBLK (mode))
2193         text[0] = 'b';
2194       else
2195         text[0] = ' ';
2196     }
2197 
2198   /* permission flags */
2199   text[1] = (mode & THUNAR_FILE_MODE_USR_READ)  ? 'r' : '-';
2200   text[2] = (mode & THUNAR_FILE_MODE_USR_WRITE) ? 'w' : '-';
2201   text[3] = (mode & THUNAR_FILE_MODE_USR_EXEC)  ? 'x' : '-';
2202   text[4] = (mode & THUNAR_FILE_MODE_GRP_READ)  ? 'r' : '-';
2203   text[5] = (mode & THUNAR_FILE_MODE_GRP_WRITE) ? 'w' : '-';
2204   text[6] = (mode & THUNAR_FILE_MODE_GRP_EXEC)  ? 'x' : '-';
2205   text[7] = (mode & THUNAR_FILE_MODE_OTH_READ)  ? 'r' : '-';
2206   text[8] = (mode & THUNAR_FILE_MODE_OTH_WRITE) ? 'w' : '-';
2207   text[9] = (mode & THUNAR_FILE_MODE_OTH_EXEC)  ? 'x' : '-';
2208 
2209   /* special flags */
2210   if (G_UNLIKELY (mode & THUNAR_FILE_MODE_SUID))
2211     text[3] = 's';
2212   if (G_UNLIKELY (mode & THUNAR_FILE_MODE_SGID))
2213     text[6] = 's';
2214   if (G_UNLIKELY (mode & THUNAR_FILE_MODE_STICKY))
2215     text[9] = 't';
2216 
2217   text[10] = '\0';
2218 
2219   return text;
2220 }
2221 
2222 
2223 
2224 /**
2225  * thunar_file_get_size_string:
2226  * @file : a #ThunarFile instance.
2227  *
2228  * Returns the size of the file as text in a human readable
2229  * format. You'll need to free the result using g_free()
2230  * if you're done with it.
2231  *
2232  * Return value: the size of @file in a human readable
2233  *               format.
2234  **/
2235 gchar *
thunar_file_get_size_string(const ThunarFile * file)2236 thunar_file_get_size_string (const ThunarFile *file)
2237 {
2238   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2239   return g_format_size (thunar_file_get_size (file));
2240 }
2241 
2242 
2243 
2244 /**
2245  * thunar_file_get_size_in_bytes_string:
2246  * @file : a #ThunarFile instance.
2247  *
2248  * Returns the size in bytes of the file as text.
2249  * You'll need to free the result using g_free()
2250  * if you're done with it.
2251  *
2252  * Return value: the size of @file in bytes.
2253  **/
2254 gchar *
thunar_file_get_size_in_bytes_string(const ThunarFile * file)2255 thunar_file_get_size_in_bytes_string (const ThunarFile *file)
2256 {
2257   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2258   return g_strdup_printf ("%'"G_GUINT64_FORMAT, thunar_file_get_size (file));
2259 }
2260 
2261 
2262 
2263 /**
2264  * thunar_file_get_size_string_formatted:
2265  * @file             : a #ThunarFile instance.
2266  * @file_size_binary : indicates if file size format
2267  *                     should be binary or not.
2268  *
2269  * Returns the size of the file as text in a human readable
2270  * format in decimal or binary format. You'll need to free
2271  * the result using g_free() if you're done with it.
2272  *
2273  * Return value: the size of @file in a human readable
2274  *               format.
2275  **/
2276 gchar *
thunar_file_get_size_string_formatted(const ThunarFile * file,const gboolean file_size_binary)2277 thunar_file_get_size_string_formatted (const ThunarFile *file, const gboolean file_size_binary)
2278 {
2279   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2280   return g_format_size_full (thunar_file_get_size (file),
2281                              file_size_binary ? G_FORMAT_SIZE_IEC_UNITS : G_FORMAT_SIZE_DEFAULT);
2282 }
2283 
2284 
2285 
2286 /**
2287  * thunar_file_get_size_string_long:
2288  * @file             : a #ThunarFile instance.
2289  * @file_size_binary : indicates if file size format
2290  *                     should be binary or not.
2291  *
2292  * Returns the size of the file as text in a human readable
2293  * format in decimal or binary format, including the exact
2294  * size in bytes. You'll need to free the result using
2295  * g_free() if you're done with it.
2296  *
2297  * Return value: the size of @file in a human readable
2298  *               format, including size in bytes.
2299  **/
2300 gchar *
thunar_file_get_size_string_long(const ThunarFile * file,const gboolean file_size_binary)2301 thunar_file_get_size_string_long (const ThunarFile *file, const gboolean file_size_binary)
2302 {
2303   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2304   return g_format_size_full (thunar_file_get_size (file),
2305                              G_FORMAT_SIZE_LONG_FORMAT | (file_size_binary ? G_FORMAT_SIZE_IEC_UNITS : G_FORMAT_SIZE_DEFAULT));
2306 }
2307 
2308 
2309 
2310 /**
2311  * thunar_file_get_volume:
2312  * @file           : a #ThunarFile instance.
2313  *
2314  * Attempts to determine the #GVolume on which @file is located. If @file cannot
2315  * determine it's volume, then %NULL will be returned. Else a #GVolume instance
2316  * is returned which has to be released by the caller using g_object_unref().
2317  *
2318  * Return value: the #GVolume for @file or %NULL.
2319  **/
2320 GVolume*
thunar_file_get_volume(const ThunarFile * file)2321 thunar_file_get_volume (const ThunarFile *file)
2322 {
2323   GVolume *volume = NULL;
2324   GMount  *mount;
2325 
2326   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2327 
2328   /* TODO make this function call asynchronous */
2329   mount = g_file_find_enclosing_mount (file->gfile, NULL, NULL);
2330   if (mount != NULL)
2331     {
2332       volume = g_mount_get_volume (mount);
2333       g_object_unref (mount);
2334     }
2335 
2336   return volume;
2337 }
2338 
2339 
2340 
2341 /**
2342  * thunar_file_get_group:
2343  * @file : a #ThunarFile instance.
2344  *
2345  * Determines the #ThunarGroup for @file. If there's no
2346  * group associated with @file or if the system is unable to
2347  * determine the group, %NULL will be returned.
2348  *
2349  * The caller is responsible for freeing the returned object
2350  * using g_object_unref().
2351  *
2352  * Return value: the #ThunarGroup for @file or %NULL.
2353  **/
2354 ThunarGroup *
thunar_file_get_group(const ThunarFile * file)2355 thunar_file_get_group (const ThunarFile *file)
2356 {
2357   guint32 gid;
2358 
2359   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2360 
2361   /* TODO what are we going to do on non-UNIX systems? */
2362   gid = g_file_info_get_attribute_uint32 (file->info,
2363                                           G_FILE_ATTRIBUTE_UNIX_GID);
2364 
2365   return thunar_user_manager_get_group_by_id (user_manager, gid);
2366 }
2367 
2368 
2369 
2370 /**
2371  * thunar_file_get_user:
2372  * @file : a #ThunarFile instance.
2373  *
2374  * Determines the #ThunarUser for @file. If there's no
2375  * user associated with @file or if the system is unable
2376  * to determine the user, %NULL will be returned.
2377  *
2378  * The caller is responsible for freeing the returned object
2379  * using g_object_unref().
2380  *
2381  * Return value: the #ThunarUser for @file or %NULL.
2382  **/
2383 ThunarUser*
thunar_file_get_user(const ThunarFile * file)2384 thunar_file_get_user (const ThunarFile *file)
2385 {
2386   guint32 uid;
2387 
2388   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2389 
2390   /* TODO what are we going to do on non-UNIX systems? */
2391   uid = g_file_info_get_attribute_uint32 (file->info,
2392                                           G_FILE_ATTRIBUTE_UNIX_UID);
2393 
2394   return thunar_user_manager_get_user_by_id (user_manager, uid);
2395 }
2396 
2397 
2398 
2399 /**
2400  * thunar_file_get_content_type:
2401  * @file : a #ThunarFile.
2402  *
2403  * Returns the content type of @file.
2404  *
2405  * Return value: content type of @file.
2406  **/
2407 const gchar *
thunar_file_get_content_type(ThunarFile * file)2408 thunar_file_get_content_type (ThunarFile *file)
2409 {
2410   GFileInfo   *info;
2411   GError      *err = NULL;
2412   const gchar *content_type = NULL;
2413 
2414   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2415 
2416   if (G_UNLIKELY (file->content_type == NULL))
2417     {
2418       G_LOCK (file_content_type_mutex);
2419 
2420       /* make sure we weren't waiting for a lock */
2421       if (G_UNLIKELY (file->content_type != NULL))
2422         goto bailout;
2423 
2424       /* make sure this is not loaded in the general info */
2425       _thunar_assert (file->info == NULL
2426           || !g_file_info_has_attribute (file->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE));
2427 
2428       if (G_UNLIKELY (file->kind == G_FILE_TYPE_DIRECTORY))
2429         {
2430           /* this we known for sure */
2431           file->content_type = g_strdup ("inode/directory");
2432         }
2433       else
2434         {
2435           /* async load the content-type */
2436           info = g_file_query_info (file->gfile,
2437                                     G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
2438                                     G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
2439                                     G_FILE_QUERY_INFO_NONE,
2440                                     NULL, &err);
2441 
2442           if (G_LIKELY (info != NULL))
2443             {
2444               /* store the new content type */
2445               content_type = g_file_info_get_content_type (info);
2446               if (G_UNLIKELY (content_type == NULL))
2447                 content_type = g_file_info_get_attribute_string (info,
2448                                                                  G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
2449               if (G_LIKELY (content_type != NULL))
2450                 file->content_type = g_strdup (content_type);
2451               g_object_unref (G_OBJECT (info));
2452             }
2453           else
2454             {
2455               g_warning ("Content type loading failed for %s: %s",
2456                          thunar_file_get_display_name (file),
2457                          err->message);
2458               g_error_free (err);
2459             }
2460 
2461           /* always provide a fallback */
2462           if (file->content_type == NULL)
2463             file->content_type = g_strdup (DEFAULT_CONTENT_TYPE);
2464         }
2465 
2466       bailout:
2467 
2468       G_UNLOCK (file_content_type_mutex);
2469     }
2470 
2471   return file->content_type;
2472 }
2473 
2474 
2475 
2476 gboolean
thunar_file_load_content_type(ThunarFile * file)2477 thunar_file_load_content_type (ThunarFile *file)
2478 {
2479   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), TRUE);
2480 
2481   if (file->content_type != NULL)
2482     return FALSE;
2483 
2484   thunar_file_get_content_type (file);
2485 
2486   return TRUE;
2487 }
2488 
2489 
2490 
2491 /**
2492  * thunar_file_get_symlink_target:
2493  * @file : a #ThunarFile.
2494  *
2495  * Returns the path of the symlink target or %NULL if the @file
2496  * is not a symlink.
2497  *
2498  * Return value: path of the symlink target or %NULL.
2499  **/
2500 const gchar *
thunar_file_get_symlink_target(const ThunarFile * file)2501 thunar_file_get_symlink_target (const ThunarFile *file)
2502 {
2503   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2504 
2505   if (file->info == NULL)
2506     return NULL;
2507 
2508   return g_file_info_get_symlink_target (file->info);
2509 }
2510 
2511 
2512 
2513 /**
2514  * thunar_file_get_basename:
2515  * @file : a #ThunarFile.
2516  *
2517  * Returns the basename of the @file in UTF-8 encoding.
2518  *
2519  * Return value: UTF-8 encoded basename of the @file.
2520  **/
2521 const gchar *
thunar_file_get_basename(const ThunarFile * file)2522 thunar_file_get_basename (const ThunarFile *file)
2523 {
2524   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2525   return file->basename;
2526 }
2527 
2528 
2529 
2530 /**
2531  * thunar_file_is_symlink:
2532  * @file : a #ThunarFile.
2533  *
2534  * Returns %TRUE if @file is a symbolic link.
2535  *
2536  * Return value: %TRUE if @file is a symbolic link.
2537  **/
2538 gboolean
thunar_file_is_symlink(const ThunarFile * file)2539 thunar_file_is_symlink (const ThunarFile *file)
2540 {
2541   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2542 
2543   if (file->info == NULL)
2544     return FALSE;
2545 
2546   return g_file_info_get_is_symlink (file->info);
2547 }
2548 
2549 
2550 
2551 /**
2552  * thunar_file_get_size:
2553  * @file : a #ThunarFile instance.
2554  *
2555  * Tries to determine the size of @file in bytes and
2556  * returns the size.
2557  *
2558  * Return value: the size of @file in bytes.
2559  **/
2560 guint64
thunar_file_get_size(const ThunarFile * file)2561 thunar_file_get_size (const ThunarFile *file)
2562 {
2563   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0);
2564 
2565   if (file->info == NULL)
2566     return 0;
2567 
2568   return g_file_info_get_size (file->info);
2569 }
2570 
2571 
2572 
2573 /**
2574  * thunar_file_get_default_handler:
2575  * @file : a #ThunarFile instance.
2576  *
2577  * Returns the default #GAppInfo for @file or %NULL if there is none.
2578  *
2579  * The caller is responsible to free the returned #GAppInfo using
2580  * g_object_unref().
2581  *
2582  * Return value: Default #GAppInfo for @file or %NULL if there is none.
2583  **/
2584 GAppInfo *
thunar_file_get_default_handler(const ThunarFile * file)2585 thunar_file_get_default_handler (const ThunarFile *file)
2586 {
2587   const gchar *content_type;
2588   GAppInfo    *app_info = NULL;
2589   gboolean     must_support_uris = FALSE;
2590   gchar       *path;
2591 
2592   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2593 
2594   content_type = thunar_file_get_content_type (THUNAR_FILE (file));
2595   if (content_type != NULL)
2596     {
2597       path = g_file_get_path (file->gfile);
2598       must_support_uris = (path == NULL);
2599       g_free (path);
2600 
2601       app_info = g_app_info_get_default_for_type (content_type, must_support_uris);
2602     }
2603 
2604   if (app_info == NULL)
2605     app_info = g_file_query_default_handler (file->gfile, NULL, NULL);
2606 
2607   return app_info;
2608 }
2609 
2610 
2611 
2612 /**
2613  * thunar_file_get_kind:
2614  * @file : a #ThunarFile instance.
2615  *
2616  * Returns the kind of @file.
2617  *
2618  * Return value: the kind of @file.
2619  **/
2620 GFileType
thunar_file_get_kind(const ThunarFile * file)2621 thunar_file_get_kind (const ThunarFile *file)
2622 {
2623   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), G_FILE_TYPE_UNKNOWN);
2624   return file->kind;
2625 }
2626 
2627 
2628 
2629 GFile *
thunar_file_get_target_location(const ThunarFile * file)2630 thunar_file_get_target_location (const ThunarFile *file)
2631 {
2632   const gchar *uri;
2633 
2634   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
2635 
2636   if (file->info == NULL)
2637     return g_object_ref (file->gfile);
2638 
2639   uri = g_file_info_get_attribute_string (file->info,
2640                                           G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
2641 
2642   return (uri != NULL) ? g_file_new_for_uri (uri) : NULL;
2643 }
2644 
2645 
2646 
2647 /**
2648  * thunar_file_get_mode:
2649  * @file : a #ThunarFile instance.
2650  *
2651  * Returns the permission bits of @file.
2652  *
2653  * Return value: the permission bits of @file.
2654  **/
2655 ThunarFileMode
thunar_file_get_mode(const ThunarFile * file)2656 thunar_file_get_mode (const ThunarFile *file)
2657 {
2658   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0);
2659 
2660   if (file->info == NULL)
2661     return 0;
2662 
2663   if (g_file_info_has_attribute (file->info, G_FILE_ATTRIBUTE_UNIX_MODE))
2664     return g_file_info_get_attribute_uint32 (file->info, G_FILE_ATTRIBUTE_UNIX_MODE);
2665   else
2666     return thunar_file_is_directory (file) ? 0777 : 0666;
2667 }
2668 
2669 
2670 
2671 gboolean
thunar_file_is_mounted(const ThunarFile * file)2672 thunar_file_is_mounted (const ThunarFile *file)
2673 {
2674   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2675   return FLAG_IS_SET (file, THUNAR_FILE_FLAG_IS_MOUNTED);
2676 }
2677 
2678 
2679 
2680 gboolean
thunar_file_exists(const ThunarFile * file)2681 thunar_file_exists (const ThunarFile *file)
2682 {
2683   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2684   return g_file_query_exists (file->gfile, NULL);
2685 }
2686 
2687 
2688 
2689 /**
2690  * thunar_file_is_directory:
2691  * @file : a #ThunarFile instance.
2692  *
2693  * Checks whether @file refers to a directory.
2694  *
2695  * Return value: %TRUE if @file is a directory.
2696  **/
2697 gboolean
thunar_file_is_directory(const ThunarFile * file)2698 thunar_file_is_directory (const ThunarFile *file)
2699 {
2700   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2701   return file->kind == G_FILE_TYPE_DIRECTORY;
2702 }
2703 
2704 
2705 
2706 /**
2707  * thunar_file_is_shortcut:
2708  * @file : a #ThunarFile instance.
2709  *
2710  * Checks whether @file refers to a shortcut to something else.
2711  *
2712  * Return value: %TRUE if @file is a shortcut.
2713  **/
2714 gboolean
thunar_file_is_shortcut(const ThunarFile * file)2715 thunar_file_is_shortcut (const ThunarFile *file)
2716 {
2717   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2718   return file->kind == G_FILE_TYPE_SHORTCUT;
2719 }
2720 
2721 
2722 
2723 /**
2724  * thunar_file_is_mountable:
2725  * @file : a #ThunarFile instance.
2726  *
2727  * Checks whether @file refers to a mountable file/directory.
2728  *
2729  * Return value: %TRUE if @file is a mountable file/directory.
2730  **/
2731 gboolean
thunar_file_is_mountable(const ThunarFile * file)2732 thunar_file_is_mountable (const ThunarFile *file)
2733 {
2734   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2735   return file->kind == G_FILE_TYPE_MOUNTABLE;
2736 }
2737 
2738 
2739 
2740 /**
2741  * thunar_file_is_local:
2742  * @file : a #ThunarFile instance.
2743  *
2744  * Returns %TRUE if @file is a local file with the
2745  * file:// URI scheme.
2746  *
2747  * Return value: %TRUE if @file is local.
2748  **/
2749 gboolean
thunar_file_is_local(const ThunarFile * file)2750 thunar_file_is_local (const ThunarFile *file)
2751 {
2752   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2753   return g_file_has_uri_scheme (file->gfile, "file");
2754 }
2755 
2756 
2757 
2758 /**
2759  * thunar_file_is_parent:
2760  * @file  : a #ThunarFile instance.
2761  * @child : another #ThunarFile instance.
2762  *
2763  * Determines whether @file is the parent directory of @child.
2764  *
2765  * Return value: %TRUE if @file is the parent of @child.
2766  **/
2767 gboolean
thunar_file_is_parent(const ThunarFile * file,const ThunarFile * child)2768 thunar_file_is_parent (const ThunarFile *file,
2769                        const ThunarFile *child)
2770 {
2771   gboolean is_parent = FALSE;
2772   GFile   *parent;
2773 
2774   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2775   _thunar_return_val_if_fail (THUNAR_IS_FILE (child), FALSE);
2776 
2777   parent = g_file_get_parent (child->gfile);
2778   if (parent != NULL)
2779     {
2780       is_parent = g_file_equal (file->gfile, parent);
2781       g_object_unref (parent);
2782     }
2783 
2784   return is_parent;
2785 }
2786 
2787 
2788 
2789 /**
2790  * thunar_file_is_ancestor:
2791  * @file     : a #ThunarFile instance.
2792  * @ancestor : another #GFile instance.
2793  *
2794  * Determines whether @file is somewhere inside @ancestor,
2795  * possibly with intermediate folders.
2796  *
2797  * Return value: %TRUE if @ancestor contains @file as a
2798  *               child, grandchild, great grandchild, etc.
2799  **/
2800 gboolean
thunar_file_is_gfile_ancestor(const ThunarFile * file,GFile * ancestor)2801 thunar_file_is_gfile_ancestor (const ThunarFile *file,
2802                                GFile            *ancestor)
2803 {
2804   GFile   *current = NULL;
2805   GFile   *tmp;
2806 
2807   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2808   _thunar_return_val_if_fail (G_IS_FILE (file->gfile), FALSE);
2809   _thunar_return_val_if_fail (G_IS_FILE (ancestor), FALSE);
2810 
2811   current = g_object_ref (file->gfile);
2812   while(TRUE)
2813     {
2814       tmp = g_file_get_parent (current);
2815       g_object_unref (current);
2816       current = tmp;
2817 
2818       if (current == NULL) /* parent of root is NULL */
2819         return FALSE;
2820 
2821       if (G_UNLIKELY (g_file_equal (current, ancestor)))
2822         {
2823           g_object_unref (current);
2824           return TRUE;
2825         }
2826     }
2827 
2828   return FALSE;
2829 }
2830 
2831 
2832 
2833 /**
2834  * thunar_file_is_ancestor:
2835  * @file     : a #ThunarFile instance.
2836  * @ancestor : another #ThunarFile instance.
2837  *
2838  * Determines whether @file is somewhere inside @ancestor,
2839  * possibly with intermediate folders.
2840  *
2841  * Return value: %TRUE if @ancestor contains @file as a
2842  *               child, grandchild, great grandchild, etc.
2843  **/
2844 gboolean
thunar_file_is_ancestor(const ThunarFile * file,const ThunarFile * ancestor)2845 thunar_file_is_ancestor (const ThunarFile *file,
2846                          const ThunarFile *ancestor)
2847 {
2848   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2849   _thunar_return_val_if_fail (THUNAR_IS_FILE (ancestor), FALSE);
2850 
2851   return thunar_file_is_gfile_ancestor (file, ancestor->gfile);
2852 }
2853 
2854 
2855 
2856 /**
2857  * thunar_file_is_executable:
2858  * @file : a #ThunarFile instance.
2859  *
2860  * Determines whether the owner of the current process is allowed
2861  * to execute the @file (or enter the directory refered to by
2862  * @file). On UNIX it also returns %TRUE if @file refers to a
2863  * desktop entry.
2864  *
2865  * Return value: %TRUE if @file can be executed.
2866  **/
2867 gboolean
thunar_file_is_executable(const ThunarFile * file)2868 thunar_file_is_executable (const ThunarFile *file)
2869 {
2870   ThunarPreferences *preferences;
2871   gboolean           desktop_can_execute;
2872   gboolean           exec_shell_scripts = FALSE;
2873   const gchar       *content_type;
2874 
2875   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2876 
2877   if (file->info == NULL)
2878     return FALSE;
2879 
2880   if (thunar_file_is_desktop_file (file, &desktop_can_execute))
2881     return desktop_can_execute;
2882   else
2883     {
2884       if (!g_file_info_get_attribute_boolean (file->info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
2885         return FALSE;
2886 
2887       /* get the content type of the file */
2888       content_type = thunar_file_get_content_type (THUNAR_FILE (file));
2889       if (G_UNLIKELY (content_type == NULL))
2890         return FALSE;
2891 
2892       if (!g_content_type_can_be_executable (content_type))
2893         return FALSE;
2894 
2895       /* do never execute plain text files which are not shell scripts but marked executable */
2896       if (g_content_type_equals (content_type, "text/plain"))
2897         return FALSE;
2898 
2899       /* check if the shell scripts should be executed or opened by default */
2900       preferences = thunar_preferences_get ();
2901       g_object_get (preferences, "misc-exec-shell-scripts-by-default", &exec_shell_scripts, NULL);
2902       g_object_unref (preferences);
2903 
2904       if (g_content_type_is_a (content_type, "text/plain") && ! exec_shell_scripts)
2905         return FALSE;
2906 
2907       return TRUE;
2908     }
2909 }
2910 
2911 
2912 
2913 /**
2914  * thunar_file_is_readable:
2915  * @file : a #ThunarFile instance.
2916  *
2917  * Determines whether the owner of the current process is allowed
2918  * to read the @file.
2919  *
2920  * Return value: %TRUE if @file can be read.
2921  **/
2922 static gboolean
thunar_file_is_readable(const ThunarFile * file)2923 thunar_file_is_readable (const ThunarFile *file)
2924 {
2925   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2926 
2927   if (file->info == NULL)
2928     return FALSE;
2929 
2930   if (!g_file_info_has_attribute (file->info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
2931     return TRUE;
2932 
2933   return g_file_info_get_attribute_boolean (file->info,
2934                                             G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
2935 }
2936 
2937 
2938 
2939 /**
2940  * thunar_file_is_writable:
2941  * @file : a #ThunarFile instance.
2942  *
2943  * Determines whether the owner of the current process is allowed
2944  * to write the @file.
2945  *
2946  * Return value: %TRUE if @file can be read.
2947  **/
2948 gboolean
thunar_file_is_writable(const ThunarFile * file)2949 thunar_file_is_writable (const ThunarFile *file)
2950 {
2951   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2952 
2953   if (file->info == NULL)
2954     return FALSE;
2955 
2956   if (!g_file_info_has_attribute (file->info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
2957     return TRUE;
2958 
2959   return g_file_info_get_attribute_boolean (file->info,
2960                                             G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
2961 }
2962 
2963 
2964 
2965 /**
2966  * thunar_file_is_hidden:
2967  * @file : a #ThunarFile instance.
2968  *
2969  * Checks whether @file can be considered a hidden file.
2970  *
2971  * Return value: %TRUE if @file is a hidden file, else %FALSE.
2972  **/
2973 gboolean
thunar_file_is_hidden(const ThunarFile * file)2974 thunar_file_is_hidden (const ThunarFile *file)
2975 {
2976   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2977 
2978   if (file->info == NULL)
2979     return FALSE;
2980 
2981   return g_file_info_get_is_hidden (file->info)
2982          || g_file_info_get_is_backup (file->info);
2983 }
2984 
2985 
2986 
2987 /**
2988  * thunar_file_is_home:
2989  * @file : a #ThunarFile.
2990  *
2991  * Checks whether @file refers to the users home directory.
2992  *
2993  * Return value: %TRUE if @file is the users home directory.
2994  **/
2995 gboolean
thunar_file_is_home(const ThunarFile * file)2996 thunar_file_is_home (const ThunarFile *file)
2997 {
2998   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
2999   return thunar_g_file_is_home (file->gfile);
3000 }
3001 
3002 
3003 
3004 /**
3005  * thunar_file_is_regular:
3006  * @file : a #ThunarFile.
3007  *
3008  * Checks whether @file refers to a regular file.
3009  *
3010  * Return value: %TRUE if @file is a regular file.
3011  **/
3012 gboolean
thunar_file_is_regular(const ThunarFile * file)3013 thunar_file_is_regular (const ThunarFile *file)
3014 {
3015   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3016   return file->kind == G_FILE_TYPE_REGULAR;
3017 }
3018 
3019 
3020 
3021 /**
3022  * thunar_file_is_trashed:
3023  * @file : a #ThunarFile instance.
3024  *
3025  * Returns %TRUE if @file is a local file that resides in
3026  * the trash bin.
3027  *
3028  * Return value: %TRUE if @file is in the trash, or
3029  *               the trash folder itself.
3030  **/
3031 gboolean
thunar_file_is_trashed(const ThunarFile * file)3032 thunar_file_is_trashed (const ThunarFile *file)
3033 {
3034   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3035   return thunar_g_file_is_trashed (file->gfile);
3036 }
3037 
3038 
3039 
3040 /**
3041  * thunar_file_is_desktop_file:
3042  * @file      : a #ThunarFile.
3043  * @is_secure : if %NULL do a simple check, else it will set this boolean
3044  *              to indicate if the desktop file is safe see bug #5012
3045  *              for more info.
3046  *
3047  * Returns %TRUE if @file is a .desktop file. The @is_secure return value
3048  * will tell if the .desktop file is also secure.
3049  *
3050  * Return value: %TRUE if @file is a .desktop file.
3051  **/
3052 gboolean
thunar_file_is_desktop_file(const ThunarFile * file,gboolean * is_secure)3053 thunar_file_is_desktop_file (const ThunarFile *file,
3054                              gboolean         *is_secure)
3055 {
3056   const gchar * const *data_dirs;
3057   guint                n;
3058   gchar               *path;
3059 
3060   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3061 
3062   if (file->info == NULL)
3063     return FALSE;
3064 
3065   /* only allow regular files with a .desktop extension */
3066   if (!g_str_has_suffix (file->basename, ".desktop")
3067       || file->kind != G_FILE_TYPE_REGULAR)
3068     return FALSE;
3069 
3070   /* don't check more if not needed */
3071   if (is_secure == NULL)
3072     return TRUE;
3073 
3074   /* desktop files outside xdg directories need to be executable for security reasons */
3075   if (g_file_info_get_attribute_boolean (file->info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
3076     {
3077       /* has +x */
3078       *is_secure = TRUE;
3079     }
3080   else
3081     {
3082       /* assume the file is not safe */
3083       *is_secure = FALSE;
3084 
3085       /* deskopt files in xdg directories are also fine... */
3086       if (g_file_is_native (thunar_file_get_file (file)))
3087         {
3088           data_dirs = g_get_system_data_dirs ();
3089           if (G_LIKELY (data_dirs != NULL))
3090             {
3091               path = g_file_get_path (thunar_file_get_file (file));
3092               for (n = 0; data_dirs[n] != NULL; n++)
3093                 {
3094                   if (g_str_has_prefix (path, data_dirs[n]))
3095                     {
3096                       /* has known prefix, can launch without problems */
3097                       *is_secure = TRUE;
3098                       break;
3099                     }
3100                 }
3101               g_free (path);
3102             }
3103         }
3104     }
3105 
3106   return TRUE;
3107 }
3108 
3109 
3110 
3111 /**
3112  * thunar_file_get_display_name:
3113  * @file : a #ThunarFile instance.
3114  *
3115  * Returns the @file name in the UTF-8 encoding, which is
3116  * suitable for displaying the file name in the GUI.
3117  *
3118  * Return value: the @file name suitable for display.
3119  **/
3120 const gchar *
thunar_file_get_display_name(const ThunarFile * file)3121 thunar_file_get_display_name (const ThunarFile *file)
3122 {
3123   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3124   return file->display_name;
3125 }
3126 
3127 
3128 
3129 /**
3130  * thunar_file_get_deletion_date:
3131  * @file       : a #ThunarFile instance.
3132  * @date_style : the style used to format the date.
3133  * @date_custom_style : custom style to apply, if @date_style is set to custom
3134  *
3135  * Returns the deletion date of the @file if the @file
3136  * is located in the trash. Otherwise %NULL will be
3137  * returned.
3138  *
3139  * The caller is responsible to free the returned string
3140  * using g_free() when no longer needed.
3141  *
3142  * Return value: the deletion date of @file if @file is
3143  *               in the trash, %NULL otherwise.
3144  **/
3145 gchar*
thunar_file_get_deletion_date(const ThunarFile * file,ThunarDateStyle date_style,const gchar * date_custom_style)3146 thunar_file_get_deletion_date (const ThunarFile *file,
3147                                ThunarDateStyle   date_style,
3148                                const gchar      *date_custom_style)
3149 {
3150   const gchar *date;
3151   time_t       deletion_time;
3152 
3153   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
3154   _thunar_return_val_if_fail (G_IS_FILE_INFO (file->info), NULL);
3155 
3156   date = g_file_info_get_attribute_string (file->info, G_FILE_ATTRIBUTE_TRASH_DELETION_DATE);
3157   if (G_UNLIKELY (date == NULL))
3158     return NULL;
3159 
3160   /* try to parse the DeletionDate (RFC 3339 string) */
3161   deletion_time = thunar_util_time_from_rfc3339 (date);
3162 
3163   /* humanize the time value */
3164   return thunar_util_humanize_file_time (deletion_time, date_style, date_custom_style);
3165 }
3166 
3167 
3168 
3169 /**
3170  * thunar_file_get_original_path:
3171  * @file : a #ThunarFile instance.
3172  *
3173  * Returns the original path of the @file if the @file
3174  * is located in the trash. Otherwise %NULL will be
3175  * returned.
3176  *
3177  * Return value: the original path of @file if @file is
3178  *               in the trash, %NULL otherwise.
3179  **/
3180 const gchar *
thunar_file_get_original_path(const ThunarFile * file)3181 thunar_file_get_original_path (const ThunarFile *file)
3182 {
3183   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
3184 
3185   if (file->info == NULL)
3186     return NULL;
3187 
3188   return g_file_info_get_attribute_byte_string (file->info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
3189 }
3190 
3191 
3192 
3193 /**
3194  * thunar_file_get_item_count:
3195  * @file : a #ThunarFile instance.
3196  *
3197  * Returns the number of items in the trash, if @file refers to the
3198  * trash root directory. Otherwise returns 0.
3199  *
3200  * Return value: number of files in the trash if @file is the trash
3201  *               root dir, 0 otherwise.
3202  **/
3203 guint32
thunar_file_get_item_count(const ThunarFile * file)3204 thunar_file_get_item_count (const ThunarFile *file)
3205 {
3206   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), 0);
3207 
3208   if (file->info == NULL)
3209     return 0;
3210 
3211   return g_file_info_get_attribute_uint32 (file->info,
3212                                            G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
3213 }
3214 
3215 
3216 
3217 /**
3218  * thunar_file_is_chmodable:
3219  * @file : a #ThunarFile instance.
3220  *
3221  * Determines whether the owner of the current process is allowed
3222  * to changed the file mode of @file.
3223  *
3224  * Return value: %TRUE if the mode of @file can be changed.
3225  **/
3226 gboolean
thunar_file_is_chmodable(const ThunarFile * file)3227 thunar_file_is_chmodable (const ThunarFile *file)
3228 {
3229   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3230 
3231   /* we can only change the mode if we the euid is
3232    *   a) equal to the file owner id
3233    * or
3234    *   b) the super-user id
3235    * and the file is not in the trash.
3236    */
3237   if (file->info == NULL)
3238     {
3239       return (effective_user_id == 0 && !thunar_file_is_trashed (file));
3240     }
3241   else
3242     {
3243       return ((effective_user_id == 0
3244                || effective_user_id == g_file_info_get_attribute_uint32 (file->info,
3245                                                                          G_FILE_ATTRIBUTE_UNIX_UID))
3246               && !thunar_file_is_trashed (file));
3247     }
3248 }
3249 
3250 
3251 
3252 /**
3253  * thunar_file_is_renameable:
3254  * @file : a #ThunarFile instance.
3255  *
3256  * Determines whether @file can be renamed using
3257  * #thunar_file_rename(). Note that the return
3258  * value is just a guess and #thunar_file_rename()
3259  * may fail even if this method returns %TRUE.
3260  *
3261  * Return value: %TRUE if @file can be renamed.
3262  **/
3263 gboolean
thunar_file_is_renameable(const ThunarFile * file)3264 thunar_file_is_renameable (const ThunarFile *file)
3265 {
3266   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3267 
3268   if (file->info == NULL)
3269     return FALSE;
3270 
3271   return g_file_info_get_attribute_boolean (file->info,
3272                                             G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME);
3273 }
3274 
3275 
3276 
3277 gboolean
thunar_file_can_be_trashed(const ThunarFile * file)3278 thunar_file_can_be_trashed (const ThunarFile *file)
3279 {
3280   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3281 
3282   if (file->info == NULL)
3283     return FALSE;
3284 
3285   return g_file_info_get_attribute_boolean (file->info,
3286                                             G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH);
3287 }
3288 
3289 
3290 
3291 /**
3292  * thunar_file_get_emblem_names:
3293  * @file : a #ThunarFile instance.
3294  *
3295  * Determines the names of the emblems that should be displayed for
3296  * @file. The returned list is owned by the caller, but the list
3297  * items - the name strings - are owned by @file. So the caller
3298  * must call g_list_free(), but don't g_free() the list items.
3299  *
3300  * Note that the strings contained in the returned list are
3301  * not garantied to exist over the next iteration of the main
3302  * loop. So in case you need the list of emblem names for
3303  * a longer time, you'll need to take a copy of the strings.
3304  *
3305  * Return value: the names of the emblems for @file.
3306  **/
3307 GList*
thunar_file_get_emblem_names(ThunarFile * file)3308 thunar_file_get_emblem_names (ThunarFile *file)
3309 {
3310   guint32   uid;
3311   gchar   **emblem_names;
3312   GList    *emblems = NULL;
3313 
3314   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
3315 
3316   /* leave if there is no info */
3317   if (file->info == NULL)
3318     return NULL;
3319 
3320   /* determine the custom emblems */
3321   emblem_names = g_file_info_get_attribute_stringv (file->info, "metadata::emblems");
3322   if (G_UNLIKELY (emblem_names != NULL))
3323     {
3324       for (; *emblem_names != NULL; ++emblem_names)
3325         emblems = g_list_append (emblems, *emblem_names);
3326     }
3327 
3328   if (thunar_file_is_symlink (file))
3329     emblems = g_list_prepend (emblems, THUNAR_FILE_EMBLEM_NAME_SYMBOLIC_LINK);
3330 
3331   /* determine the user ID of the file owner */
3332   /* TODO what are we going to do here on non-UNIX systems? */
3333   uid = file->info != NULL
3334         ? g_file_info_get_attribute_uint32 (file->info, G_FILE_ATTRIBUTE_UNIX_UID)
3335         : 0;
3336 
3337   /* we add "cant-read" if either (a) the file is not readable or (b) a directory, that lacks the
3338    * x-bit, see https://bugzilla.xfce.org/show_bug.cgi?id=1408 for the details about this change.
3339    */
3340   if (!thunar_file_is_readable (file)
3341       || (thunar_file_is_directory (file)
3342           && thunar_file_denies_access_permission (file, THUNAR_FILE_MODE_USR_EXEC,
3343                                                          THUNAR_FILE_MODE_GRP_EXEC,
3344                                                          THUNAR_FILE_MODE_OTH_EXEC)))
3345     {
3346       emblems = g_list_prepend (emblems, THUNAR_FILE_EMBLEM_NAME_CANT_READ);
3347     }
3348   else if (G_UNLIKELY (uid == effective_user_id && !thunar_file_is_writable (file) && !thunar_file_is_trashed (file)))
3349     {
3350       /* we own the file, but we cannot write to it, that's why we mark it as "cant-write", so
3351        * users won't be surprised when opening the file in a text editor, but are unable to save.
3352        */
3353       emblems = g_list_prepend (emblems, THUNAR_FILE_EMBLEM_NAME_CANT_WRITE);
3354     }
3355 
3356   return emblems;
3357 }
3358 
3359 
3360 
3361 /**
3362  * thunar_file_set_emblem_names:
3363  * @file         : a #ThunarFile instance.
3364  * @emblem_names : a #GList of emblem names.
3365  *
3366  * Sets the custom emblem name list of @file to @emblem_names
3367  * and stores them in the @file<!---->s metadata.
3368  **/
3369 void
thunar_file_set_emblem_names(ThunarFile * file,GList * emblem_names)3370 thunar_file_set_emblem_names (ThunarFile *file,
3371                               GList      *emblem_names)
3372 {
3373   GList      *lp;
3374   gchar     **emblems = NULL;
3375   gint        n;
3376   GFileInfo  *info;
3377 
3378   _thunar_return_if_fail (THUNAR_IS_FILE (file));
3379   _thunar_return_if_fail (G_IS_FILE_INFO (file->info));
3380 
3381   /* allocate a zero-terminated array for the emblem names */
3382   emblems = g_new0 (gchar *, g_list_length (emblem_names) + 1);
3383 
3384   /* turn the emblem_names list into a zero terminated array */
3385   for (lp = emblem_names, n = 0; lp != NULL; lp = lp->next)
3386     {
3387       /* skip special emblems */
3388       if (strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_SYMBOLIC_LINK) == 0
3389           || strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_CANT_READ) == 0
3390           || strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_CANT_WRITE) == 0
3391           || strcmp (lp->data, THUNAR_FILE_EMBLEM_NAME_DESKTOP) == 0)
3392         continue;
3393 
3394       /* add the emblem to our list */
3395       emblems[n++] = g_strdup (lp->data);
3396     }
3397 
3398   /* set the value in the current info. this call is needed to update the in-memory
3399    * GFileInfo structure to ensure that the new attribute value is available immediately */
3400   if (n == 0)
3401     g_file_info_remove_attribute (file->info, "metadata::emblems");
3402   else
3403     g_file_info_set_attribute_stringv (file->info, "metadata::emblems", emblems);
3404 
3405   /* send meta data to the daemon. this call is needed to store the new value of
3406    * the attribute in the file system */
3407   info = g_file_info_new ();
3408   g_file_info_set_attribute_stringv (info, "metadata::emblems", emblems);
3409   g_file_set_attributes_async (file->gfile, info,
3410                                G_FILE_QUERY_INFO_NONE,
3411                                G_PRIORITY_DEFAULT,
3412                                NULL,
3413                                thunar_file_set_emblem_names_ready,
3414                                file);
3415   g_object_unref (G_OBJECT (info));
3416 
3417   g_strfreev (emblems);
3418 }
3419 
3420 
3421 
3422 /**
3423  * thunar_file_set_custom_icon:
3424  * @file        : a #ThunarFile instance.
3425  * @custom_icon : the new custom icon for the @file.
3426  * @error       : return location for errors or %NULL.
3427  *
3428  * Tries to change the custom icon of the .desktop file referred
3429  * to by @file. If that fails, %FALSE is returned and the
3430  * @error is set accordingly.
3431  *
3432  * Return value: %TRUE if the icon of @file was changed, %FALSE otherwise.
3433  **/
3434 gboolean
thunar_file_set_custom_icon(ThunarFile * file,const gchar * custom_icon,GError ** error)3435 thunar_file_set_custom_icon (ThunarFile  *file,
3436                              const gchar *custom_icon,
3437                              GError     **error)
3438 {
3439   GKeyFile *key_file;
3440 
3441   _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE);
3442   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3443   _thunar_return_val_if_fail (custom_icon != NULL, FALSE);
3444 
3445   key_file = thunar_g_file_query_key_file (file->gfile, NULL, error);
3446 
3447   if (key_file == NULL)
3448     return FALSE;
3449 
3450   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3451                          G_KEY_FILE_DESKTOP_KEY_ICON, custom_icon);
3452 
3453   if (thunar_g_file_write_key_file (file->gfile, key_file, NULL, error))
3454     {
3455       /* tell everybody that we have changed */
3456       thunar_file_changed (file);
3457 
3458       g_key_file_free (key_file);
3459       return TRUE;
3460     }
3461   else
3462     {
3463       g_key_file_free (key_file);
3464       return FALSE;
3465     }
3466 }
3467 
3468 
3469 /**
3470  * thunar_file_is_desktop:
3471  * @file : a #ThunarFile.
3472  *
3473  * Checks whether @file refers to the users desktop directory.
3474  *
3475  * Return value: %TRUE if @file is the users desktop directory.
3476  **/
3477 gboolean
thunar_file_is_desktop(const ThunarFile * file)3478 thunar_file_is_desktop (const ThunarFile *file)
3479 {
3480   GFile   *desktop;
3481   gboolean is_desktop = FALSE;
3482 
3483   desktop = g_file_new_for_path (g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP));
3484   is_desktop = g_file_equal (file->gfile, desktop);
3485   g_object_unref (desktop);
3486 
3487   return is_desktop;
3488 }
3489 
3490 
3491 
3492 const gchar *
thunar_file_get_thumbnail_path(ThunarFile * file,ThunarThumbnailSize thumbnail_size)3493 thunar_file_get_thumbnail_path (ThunarFile *file, ThunarThumbnailSize thumbnail_size)
3494 {
3495   GChecksum *checksum;
3496   gchar     *filename;
3497   gchar     *uri;
3498 
3499   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
3500 
3501   /* if the thumbstate is known to be not there, return null */
3502   if (thunar_file_get_thumb_state (file) == THUNAR_FILE_THUMB_STATE_NONE)
3503     return NULL;
3504 
3505   if (G_UNLIKELY (file->thumbnail_path == NULL))
3506     {
3507       checksum = g_checksum_new (G_CHECKSUM_MD5);
3508       if (G_LIKELY (checksum != NULL))
3509         {
3510           uri = thunar_file_dup_uri (file);
3511           g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
3512           g_free (uri);
3513 
3514           filename = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
3515           g_checksum_free (checksum);
3516 
3517           /* The thumbnail is in the format/location
3518            * $XDG_CACHE_HOME/thumbnails/(nromal|large)/MD5_Hash_Of_URI.png
3519            * for version 0.8.0 if XDG_CACHE_HOME is defined, otherwise
3520            * /homedir/.thumbnails/(normal|large)/MD5_Hash_Of_URI.png
3521            * will be used, which is also always used for versions prior
3522            * to 0.7.0.
3523            */
3524 
3525           /* build and check if the thumbnail is in the new location */
3526           file->thumbnail_path = g_build_path ("/", g_get_user_cache_dir(),
3527                                                "thumbnails", thunar_thumbnail_size_get_nick (thumbnail_size),
3528                                                filename, NULL);
3529 
3530           if (!g_file_test(file->thumbnail_path, G_FILE_TEST_EXISTS))
3531             {
3532               /* Fallback to old version */
3533               g_free(file->thumbnail_path);
3534 
3535               file->thumbnail_path = g_build_filename (xfce_get_homedir (),
3536                                                        ".thumbnails", thunar_thumbnail_size_get_nick (thumbnail_size),
3537                                                        filename, NULL);
3538 
3539               if(!g_file_test(file->thumbnail_path, G_FILE_TEST_EXISTS))
3540               {
3541                 /* Thumbnail doesn't exist in either spot */
3542                 g_free(file->thumbnail_path);
3543                 file->thumbnail_path = NULL;
3544               }
3545             }
3546 
3547           g_free (filename);
3548         }
3549     }
3550 
3551   return file->thumbnail_path;
3552 }
3553 
3554 
3555 
3556 /**
3557  * thunar_file_get_thumb_state:
3558  * @file : a #ThunarFile.
3559  *
3560  * Returns the current #ThunarFileThumbState for @file. This
3561  * method is intended to be used by #ThunarIconFactory only.
3562  *
3563  * Return value: the #ThunarFileThumbState for @file.
3564  **/
3565 ThunarFileThumbState
thunar_file_get_thumb_state(const ThunarFile * file)3566 thunar_file_get_thumb_state (const ThunarFile *file)
3567 {
3568   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), THUNAR_FILE_THUMB_STATE_UNKNOWN);
3569   return FLAG_GET_THUMB_STATE (file);
3570 }
3571 
3572 
3573 
3574 /**
3575  * thunar_file_set_thumb_state:
3576  * @file        : a #ThunarFile.
3577  * @thumb_state : the new #ThunarFileThumbState.
3578  *
3579  * Sets the #ThunarFileThumbState for @file to @thumb_state.
3580  * This will cause a "file-changed" signal to be emitted from
3581  * #ThunarFileMonitor.
3582  **/
3583 void
thunar_file_set_thumb_state(ThunarFile * file,ThunarFileThumbState state)3584 thunar_file_set_thumb_state (ThunarFile          *file,
3585                              ThunarFileThumbState state)
3586 {
3587   _thunar_return_if_fail (THUNAR_IS_FILE (file));
3588 
3589   /* check if the state changes */
3590   if (thunar_file_get_thumb_state (file) == state)
3591     return;
3592 
3593   /* set the new thumbnail state */
3594   FLAG_SET_THUMB_STATE (file, state);
3595 
3596   /* remove path if the type is not supported */
3597   if (state == THUNAR_FILE_THUMB_STATE_NONE
3598       && file->thumbnail_path != NULL)
3599     {
3600       g_free (file->thumbnail_path);
3601       file->thumbnail_path = NULL;
3602     }
3603 
3604   /* if the file has a thumbnail, reload it */
3605   if (state == THUNAR_FILE_THUMB_STATE_READY)
3606     thunar_file_monitor_file_changed (file);
3607 }
3608 
3609 
3610 
3611 /**
3612  * thunar_file_get_custom_icon:
3613  * @file : a #ThunarFile instance.
3614  *
3615  * Queries the custom icon from @file if any, else %NULL is returned.
3616  * The custom icon can be either a themed icon name or an absolute path
3617  * to an icon file in the local file system.
3618  *
3619  * Return value: the custom icon for @file or %NULL.
3620  **/
3621 const gchar *
thunar_file_get_custom_icon(const ThunarFile * file)3622 thunar_file_get_custom_icon (const ThunarFile *file)
3623 {
3624   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
3625   return file->custom_icon_name;
3626 }
3627 
3628 
3629 
3630 /**
3631  * thunar_file_get_preview_icon:
3632  * @file : a #ThunarFile instance.
3633  *
3634  * Returns the preview icon for @file if any, else %NULL is returned.
3635  *
3636  * Return value: the custom icon for @file or %NULL, the GIcon is owner
3637  * by the file, so do not unref it.
3638  **/
3639 GIcon *
thunar_file_get_preview_icon(const ThunarFile * file)3640 thunar_file_get_preview_icon (const ThunarFile *file)
3641 {
3642   GObject *icon;
3643 
3644   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
3645   _thunar_return_val_if_fail (G_IS_FILE_INFO (file->info), NULL);
3646 
3647   icon = g_file_info_get_attribute_object (file->info, G_FILE_ATTRIBUTE_PREVIEW_ICON);
3648   if (G_LIKELY (icon != NULL))
3649     return G_ICON (icon);
3650 
3651   return NULL;
3652 }
3653 
3654 
3655 
3656 GFilesystemPreviewType
thunar_file_get_preview_type(const ThunarFile * file)3657 thunar_file_get_preview_type (const ThunarFile *file)
3658 {
3659   GFilesystemPreviewType  preview;
3660   GFileInfo              *info;
3661 
3662   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), G_FILESYSTEM_PREVIEW_TYPE_NEVER);
3663   _thunar_return_val_if_fail (G_IS_FILE (file->gfile), G_FILESYSTEM_PREVIEW_TYPE_NEVER);
3664 
3665   info = g_file_query_filesystem_info (file->gfile, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, NULL, NULL);
3666   if (G_LIKELY (info != NULL))
3667     {
3668       preview = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW);
3669       g_object_unref (G_OBJECT (info));
3670     }
3671   else
3672     {
3673       /* assume we don't know */
3674       preview = G_FILESYSTEM_PREVIEW_TYPE_NEVER;
3675     }
3676 
3677   return preview;
3678 }
3679 
3680 
3681 
3682 static const gchar *
thunar_file_get_icon_name_for_state(const gchar * icon_name,ThunarFileIconState icon_state)3683 thunar_file_get_icon_name_for_state (const gchar         *icon_name,
3684                                      ThunarFileIconState  icon_state)
3685 {
3686   if (exo_str_is_empty (icon_name))
3687     return NULL;
3688 
3689   /* check if we have an accept icon for the icon we found */
3690   if (icon_state != THUNAR_FILE_ICON_STATE_DEFAULT
3691       && (strcmp (icon_name, "inode-directory") == 0
3692           || strcmp (icon_name, "folder") == 0))
3693     {
3694       if (icon_state == THUNAR_FILE_ICON_STATE_DROP)
3695         return "folder-drag-accept";
3696       else if (icon_state == THUNAR_FILE_ICON_STATE_OPEN)
3697         return "folder-open";
3698     }
3699 
3700   return icon_name;
3701 }
3702 
3703 
3704 
3705 /**
3706  * thunar_file_get_icon_name:
3707  * @file       : a #ThunarFile instance.
3708  * @icon_state : the state of the @file<!---->s icon we are interested in.
3709  * @icon_theme : the #GtkIconTheme on which to lookup up the icon name.
3710  *
3711  * Returns the name of the icon that can be used to present @file, based
3712  * on the given @icon_state and @icon_theme.
3713  *
3714  * Return value: the icon name for @file in @icon_theme.
3715  **/
3716 const gchar *
thunar_file_get_icon_name(ThunarFile * file,ThunarFileIconState icon_state,GtkIconTheme * icon_theme)3717 thunar_file_get_icon_name (ThunarFile          *file,
3718                            ThunarFileIconState  icon_state,
3719                            GtkIconTheme        *icon_theme)
3720 {
3721   GFile               *icon_file;
3722   GIcon               *icon = NULL;
3723   const gchar * const *names;
3724   gchar               *icon_name = NULL;
3725   gchar               *path;
3726   const gchar         *special_names[] = { NULL, "folder", NULL };
3727   guint                i;
3728   const gchar         *special_dir;
3729   GFileInfo           *fileinfo;
3730 
3731   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
3732   _thunar_return_val_if_fail (GTK_IS_ICON_THEME (icon_theme), NULL);
3733 
3734   /* return cached name */
3735   if (G_LIKELY (file->icon_name != NULL))
3736     return thunar_file_get_icon_name_for_state (file->icon_name, icon_state);
3737 
3738   /* the system root folder has a special icon */
3739   if (thunar_file_is_directory (file))
3740     {
3741       if (G_LIKELY (thunar_file_is_local (file)))
3742         {
3743           path = g_file_get_path (file->gfile);
3744           if (G_LIKELY (path != NULL))
3745             {
3746               if (strcmp (path, G_DIR_SEPARATOR_S) == 0)
3747                 *special_names = "drive-harddisk";
3748               else if (strcmp (path, xfce_get_homedir ()) == 0)
3749                 *special_names = "user-home";
3750               else
3751                 {
3752                   for (i = 0; i < G_N_ELEMENTS (thunar_file_dirs); i++)
3753                     {
3754                       special_dir = g_get_user_special_dir (thunar_file_dirs[i].type);
3755                       if (special_dir != NULL
3756                           && strcmp (path, special_dir) == 0)
3757                         {
3758                           *special_names = thunar_file_dirs[i].icon_name;
3759                           break;
3760                         }
3761                     }
3762                 }
3763 
3764               g_free (path);
3765             }
3766         }
3767       else if (!thunar_file_has_parent (file))
3768         {
3769           if (g_file_has_uri_scheme (file->gfile, "trash"))
3770             {
3771               special_names[0] = thunar_file_get_item_count (file) > 0 ? "user-trash-full" : "user-trash";
3772               special_names[1] = "user-trash";
3773             }
3774           else if (g_file_has_uri_scheme (file->gfile, "network"))
3775             {
3776               special_names[0] = "network-workgroup";
3777             }
3778           else if (g_file_has_uri_scheme (file->gfile, "recent"))
3779             {
3780               special_names[0] = "document-open-recent";
3781             }
3782           else if (g_file_has_uri_scheme (file->gfile, "computer"))
3783             {
3784               special_names[0] = "computer";
3785             }
3786         }
3787 
3788       if (*special_names != NULL)
3789         {
3790           names = special_names;
3791           goto check_names;
3792         }
3793     }
3794   else if (thunar_file_is_mountable (file)
3795            || g_file_has_uri_scheme (file->gfile, "network"))
3796     {
3797       /* query the icon (computer:// and network:// backend) */
3798       fileinfo = g_file_query_info (file->gfile,
3799                                     G_FILE_ATTRIBUTE_STANDARD_ICON,
3800                                     G_FILE_QUERY_INFO_NONE, NULL, NULL);
3801       if (G_LIKELY (fileinfo != NULL))
3802         {
3803           /* take the icon from the info */
3804           icon = g_file_info_get_icon (fileinfo);
3805           if (G_LIKELY (icon != NULL))
3806             g_object_ref (icon);
3807 
3808           /* release */
3809           g_object_unref (G_OBJECT (fileinfo));
3810 
3811           if (G_LIKELY (icon != NULL))
3812             goto check_icon;
3813         }
3814     }
3815 
3816   /* try again later */
3817   if (file->info == NULL)
3818     return NULL;
3819 
3820   /* lookup for content type, just like gio does for local files */
3821   icon = g_content_type_get_icon (thunar_file_get_content_type (file));
3822   if (G_LIKELY (icon != NULL))
3823     {
3824       check_icon:
3825       if (G_IS_THEMED_ICON (icon))
3826         {
3827           names = g_themed_icon_get_names (G_THEMED_ICON (icon));
3828 
3829           check_names:
3830 
3831           if (G_LIKELY (names != NULL))
3832             {
3833               for (i = 0; names[i] != NULL; ++i)
3834                 if (*names[i] != '(' /* see gnome bug 688042 */
3835                     && gtk_icon_theme_has_icon (icon_theme, names[i]))
3836                   {
3837                     icon_name = g_strdup (names[i]);
3838                     break;
3839                   }
3840             }
3841         }
3842       else if (G_IS_FILE_ICON (icon))
3843         {
3844           icon_file = g_file_icon_get_file (G_FILE_ICON (icon));
3845           if (icon_file != NULL)
3846             icon_name = g_file_get_path (icon_file);
3847         }
3848 
3849       if (G_LIKELY (icon != NULL))
3850         g_object_unref (icon);
3851     }
3852 
3853   /* store new name, fallback to legacy names, or empty string to avoid recursion */
3854   g_free (file->icon_name);
3855   if (G_LIKELY (icon_name != NULL))
3856     file->icon_name = icon_name;
3857   else if (file->kind == G_FILE_TYPE_DIRECTORY
3858            && gtk_icon_theme_has_icon (icon_theme, "folder"))
3859     file->icon_name = g_strdup ("folder");
3860   else
3861     file->icon_name = g_strdup ("");
3862 
3863   return thunar_file_get_icon_name_for_state (file->icon_name, icon_state);
3864 }
3865 
3866 
3867 
3868 /**
3869  * thunar_file_watch:
3870  * @file : a #ThunarFile instance.
3871  *
3872  * Tells @file to watch itself for changes. Not all #ThunarFile
3873  * implementations must support this, but if a #ThunarFile
3874  * implementation implements the thunar_file_watch() method,
3875  * it must also implement the thunar_file_unwatch() method.
3876  *
3877  * The #ThunarFile base class implements automatic "ref
3878  * counting" for watches, that says, you can call thunar_file_watch()
3879  * multiple times, but the virtual method will be invoked only
3880  * once. This also means that you MUST call thunar_file_unwatch()
3881  * for every thunar_file_watch() invokation, else the application
3882  * will abort.
3883  **/
3884 void
thunar_file_watch(ThunarFile * file)3885 thunar_file_watch (ThunarFile *file)
3886 {
3887   ThunarFileWatch *file_watch;
3888   GError          *error = NULL;
3889 
3890   _thunar_return_if_fail (THUNAR_IS_FILE (file));
3891 
3892   file_watch = g_object_get_qdata (G_OBJECT (file), thunar_file_watch_quark);
3893   if (file_watch == NULL)
3894     {
3895       file_watch = g_slice_new (ThunarFileWatch);
3896       file_watch->watch_count = 1;
3897 
3898       /* create a file or directory monitor */
3899       file_watch->monitor = g_file_monitor (file->gfile, G_FILE_MONITOR_WATCH_MOUNTS |
3900                                             G_FILE_MONITOR_WATCH_MOVES, NULL, &error);
3901 
3902       if (G_UNLIKELY (file_watch->monitor == NULL))
3903         {
3904           g_debug ("Failed to create file monitor: %s", error->message);
3905           g_error_free (error);
3906           file->no_file_watch = TRUE;
3907         }
3908       else
3909         {
3910           /* watch monitor for file changes */
3911           g_signal_connect (file_watch->monitor, "changed", G_CALLBACK (thunar_file_monitor), file);
3912         }
3913 
3914       /* attach to file */
3915       g_object_set_qdata_full (G_OBJECT (file), thunar_file_watch_quark, file_watch, thunar_file_watch_destroyed);
3916     }
3917   else if (G_LIKELY (!file->no_file_watch))
3918     {
3919       /* increase watch count */
3920       _thunar_return_if_fail (G_IS_FILE_MONITOR (file_watch->monitor));
3921       file_watch->watch_count++;
3922     }
3923 }
3924 
3925 
3926 
3927 /**
3928  * thunar_file_unwatch:
3929  * @file : a #ThunarFile instance.
3930  *
3931  * See thunar_file_watch() for a description of how watching
3932  * #ThunarFile<!---->s works.
3933  **/
3934 void
thunar_file_unwatch(ThunarFile * file)3935 thunar_file_unwatch (ThunarFile *file)
3936 {
3937   ThunarFileWatch *file_watch;
3938 
3939   _thunar_return_if_fail (THUNAR_IS_FILE (file));
3940 
3941   if (G_UNLIKELY (file->no_file_watch))
3942     {
3943       return;
3944     }
3945 
3946   file_watch = g_object_get_qdata (G_OBJECT (file), thunar_file_watch_quark);
3947   if (file_watch != NULL)
3948     {
3949       /* remove if this was the last ref */
3950       if (--file_watch->watch_count == 0)
3951         g_object_set_qdata (G_OBJECT (file), thunar_file_watch_quark, NULL);
3952     }
3953   else
3954     {
3955       _thunar_assert_not_reached ();
3956     }
3957 }
3958 
3959 
3960 
3961 /**
3962  * thunar_file_reload:
3963  * @file : a #ThunarFile instance.
3964  *
3965  * Tells @file to reload its internal state, e.g. by reacquiring
3966  * the file info from the underlying media.
3967  *
3968  * You must be able to handle the case that @file is
3969  * destroyed during the reload call.
3970  *
3971  * Return value: As this function can be used as a callback function
3972  * for thunar_file_reload_idle, it will always return FALSE to prevent
3973  * being called repeatedly.
3974  **/
3975 gboolean
thunar_file_reload(ThunarFile * file)3976 thunar_file_reload (ThunarFile *file)
3977 {
3978   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
3979 
3980   /* clear file pxmap cache */
3981   thunar_icon_factory_clear_pixmap_cache (file);
3982 
3983   if (!thunar_file_load (file, NULL, NULL))
3984     {
3985       /* destroy the file if we cannot query any file information */
3986       thunar_file_destroy (file);
3987       return FALSE;
3988     }
3989 
3990   /* ... and tell others */
3991   thunar_file_changed (file);
3992 
3993   return FALSE;
3994 }
3995 
3996 
3997 
3998 /**
3999  * thunar_file_reload_idle:
4000  * @file : a #ThunarFile instance.
4001  *
4002  * Schedules a reload of the @file by calling thunar_file_reload
4003  * when idle.
4004  *
4005  **/
4006 void
thunar_file_reload_idle(ThunarFile * file)4007 thunar_file_reload_idle (ThunarFile *file)
4008 {
4009   _thunar_return_if_fail (THUNAR_IS_FILE (file));
4010 
4011   g_idle_add ((GSourceFunc) thunar_file_reload, file);
4012 }
4013 
4014 
4015 
4016 /**
4017  * thunar_file_reload_idle_unref:
4018  * @file : a #ThunarFile instance.
4019  *
4020  * Schedules a reload of the @file by calling thunar_file_reload
4021  * when idle. When scheduled function returns @file object will be
4022  * unreferenced.
4023  *
4024  **/
4025 void
thunar_file_reload_idle_unref(ThunarFile * file)4026 thunar_file_reload_idle_unref (ThunarFile *file)
4027 {
4028   _thunar_return_if_fail (THUNAR_IS_FILE (file));
4029 
4030   g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
4031                    (GSourceFunc) thunar_file_reload,
4032                    file,
4033                    (GDestroyNotify) g_object_unref);
4034 }
4035 
4036 
4037 
4038 /**
4039  * thunar_file_destroy:
4040  * @file : a #ThunarFile instance.
4041  *
4042  * Emits the ::destroy signal notifying all reference holders
4043  * that they should release their references to the @file.
4044  *
4045  * This method is very similar to what gtk_object_destroy()
4046  * does for #GtkObject<!---->s.
4047  **/
4048 void
thunar_file_destroy(ThunarFile * file)4049 thunar_file_destroy (ThunarFile *file)
4050 {
4051   _thunar_return_if_fail (THUNAR_IS_FILE (file));
4052 
4053   if (!FLAG_IS_SET (file, THUNAR_FILE_FLAG_IN_DESTRUCTION))
4054     {
4055       /* take an additional reference on the file, as the file-destroyed
4056        * invocation may already release the last reference.
4057        */
4058       g_object_ref (G_OBJECT (file));
4059 
4060       /* tell the file monitor that this file was destroyed */
4061       thunar_file_monitor_file_destroyed (file);
4062 
4063       /* run the dispose handler */
4064       g_object_run_dispose (G_OBJECT (file));
4065 
4066       /* release our reference */
4067       g_object_unref (G_OBJECT (file));
4068     }
4069 }
4070 
4071 
4072 
4073 /**
4074  * thunar_file_compare_by_type:
4075  * @a : the first #ThunarFile.
4076  * @b : the second #ThunarFile.
4077  *
4078  * Compares items so that directories come before files and ancestors come
4079  * before descendants.
4080  *
4081  * Return value: -1 if @file_a should be sorted before @file_b, 1 if
4082  *               @file_b should be sorted before @file_a, 0 if equal.
4083  **/
4084 gint
thunar_file_compare_by_type(ThunarFile * a,ThunarFile * b)4085 thunar_file_compare_by_type (ThunarFile *a,
4086                              ThunarFile *b)
4087 {
4088   GFile *file_a;
4089   GFile *file_b;
4090   GFile *parent_a;
4091   GFile *parent_b;
4092   gint   ret = 0;
4093 
4094   file_a = thunar_file_get_file (a);
4095   file_b = thunar_file_get_file (b);
4096 
4097   /* check whether the files are equal */
4098   if (g_file_equal (file_a, file_b))
4099     return 0;
4100 
4101   /* directories always come first */
4102   if (a->kind == G_FILE_TYPE_DIRECTORY
4103       && b->kind != G_FILE_TYPE_DIRECTORY)
4104     {
4105       return -1;
4106     }
4107   if (a->kind != G_FILE_TYPE_DIRECTORY
4108       && b->kind == G_FILE_TYPE_DIRECTORY)
4109     {
4110       return 1;
4111     }
4112 
4113   /* ancestors come first */
4114   if (g_file_has_prefix (file_b, file_a))
4115     return -1;
4116   if (g_file_has_prefix (file_a, file_b))
4117     return 1;
4118 
4119   parent_a = g_file_get_parent (file_a);
4120   parent_b = g_file_get_parent (file_b);
4121 
4122   if (g_file_equal (parent_a, parent_b))
4123     {
4124       /* compare siblings by their display name */
4125       ret = g_utf8_collate (a->display_name,
4126                             b->display_name);
4127     }
4128   /* again, ancestors come first */
4129   else if (g_file_has_prefix (file_b, parent_a))
4130       ret = -1;
4131   else if (g_file_has_prefix (file_a, parent_b))
4132       ret = 1;
4133 
4134   g_object_unref (parent_a);
4135   g_object_unref (parent_b);
4136   return ret;
4137 }
4138 
4139 
4140 
4141 /**
4142  * thunar_file_compare_by_name:
4143  * @file_a         : the first #ThunarFile.
4144  * @file_b         : the second #ThunarFile.
4145  * @case_sensitive : whether the comparison should be case-sensitive.
4146  *
4147  * Compares @file_a and @file_b by their display names. If @case_sensitive
4148  * is %TRUE the comparison will be case-sensitive.
4149  *
4150  * Return value: -1 if @file_a should be sorted before @file_b, 1 if
4151  *               @file_b should be sorted before @file_a, 0 if equal.
4152  **/
4153 gint
thunar_file_compare_by_name(const ThunarFile * file_a,const ThunarFile * file_b,gboolean case_sensitive)4154 thunar_file_compare_by_name (const ThunarFile *file_a,
4155                              const ThunarFile *file_b,
4156                              gboolean          case_sensitive)
4157 {
4158   gint result = 0;
4159 
4160 #ifdef G_ENABLE_DEBUG
4161   /* probably too expensive to do the instance check every time
4162    * this function is called, so only for debugging builds.
4163    */
4164   _thunar_return_val_if_fail (THUNAR_IS_FILE (file_a), 0);
4165   _thunar_return_val_if_fail (THUNAR_IS_FILE (file_b), 0);
4166 #endif
4167 
4168   /* case insensitive checking */
4169   if (G_LIKELY (!case_sensitive))
4170     result = g_strcmp0 (file_a->collate_key_nocase, file_b->collate_key_nocase);
4171 
4172   /* fall-back to case sensitive */
4173   if (result == 0)
4174     result = g_strcmp0 (file_a->collate_key, file_b->collate_key);
4175 
4176   /* this happens in the trash */
4177   if (result == 0)
4178     {
4179       result = g_strcmp0 (thunar_file_get_original_path (file_a),
4180                           thunar_file_get_original_path (file_b));
4181     }
4182 
4183   return result;
4184 }
4185 
4186 
4187 
4188 static gboolean
thunar_file_same_filesystem(const ThunarFile * file_a,const ThunarFile * file_b)4189 thunar_file_same_filesystem (const ThunarFile *file_a,
4190                              const ThunarFile *file_b)
4191 {
4192   const gchar *filesystem_id_a;
4193   const gchar *filesystem_id_b;
4194 
4195   _thunar_return_val_if_fail (THUNAR_IS_FILE (file_a), FALSE);
4196   _thunar_return_val_if_fail (THUNAR_IS_FILE (file_b), FALSE);
4197 
4198   /* return false if we have no information about one of the files */
4199   if (file_a->info == NULL || file_b->info == NULL)
4200     return FALSE;
4201 
4202   /* determine the filesystem IDs */
4203   filesystem_id_a = g_file_info_get_attribute_string (file_a->info,
4204                                                       G_FILE_ATTRIBUTE_ID_FILESYSTEM);
4205 
4206   filesystem_id_b = g_file_info_get_attribute_string (file_b->info,
4207                                                       G_FILE_ATTRIBUTE_ID_FILESYSTEM);
4208 
4209   /* compare the filesystem IDs */
4210   return exo_str_is_equal (filesystem_id_a, filesystem_id_b);
4211 }
4212 
4213 
4214 
4215 /**
4216  * thunar_file_cache_lookup:
4217  * @file : a #GFile.
4218  *
4219  * Looks up the #ThunarFile for @file in the internal file
4220  * cache and returns the file present for @file in the
4221  * cache or %NULL if no #ThunarFile is cached for @file.
4222  *
4223  * Note that no reference is taken for the caller.
4224  *
4225  * This method should not be used but in very rare cases.
4226  * Consider using thunar_file_get() instead.
4227  *
4228  * Return value: the #ThunarFile for @file in the internal
4229  *               cache, or %NULL. If you are done with the
4230  *               file, use g_object_unref to release.
4231  **/
4232 ThunarFile *
thunar_file_cache_lookup(const GFile * file)4233 thunar_file_cache_lookup (const GFile *file)
4234 {
4235   GWeakRef   *ref;
4236   ThunarFile *cached_file;
4237 
4238   _thunar_return_val_if_fail (G_IS_FILE (file), NULL);
4239 
4240   G_LOCK (file_cache_mutex);
4241 
4242   /* allocate the ThunarFile cache on-demand */
4243   if (G_UNLIKELY (file_cache == NULL))
4244     {
4245       file_cache = g_hash_table_new_full (g_file_hash,
4246                                           (GEqualFunc) g_file_equal,
4247                                           (GDestroyNotify) g_object_unref,
4248                                           (GDestroyNotify) weak_ref_free);
4249     }
4250 
4251   ref = g_hash_table_lookup (file_cache, file);
4252 
4253   if (ref == NULL)
4254     cached_file = NULL;
4255   else
4256     cached_file = g_weak_ref_get (ref);
4257 
4258   G_UNLOCK (file_cache_mutex);
4259 
4260   return cached_file;
4261 }
4262 
4263 
4264 
4265 gchar *
thunar_file_cached_display_name(const GFile * file)4266 thunar_file_cached_display_name (const GFile *file)
4267 {
4268   ThunarFile *cached_file;
4269   gchar      *display_name;
4270 
4271   /* check if we have a ThunarFile for it in the cache (usually is the case) */
4272   cached_file = thunar_file_cache_lookup (file);
4273   if (cached_file != NULL)
4274     {
4275       /* determine the display name of the file */
4276       display_name = g_strdup (thunar_file_get_display_name (cached_file));
4277       g_object_unref (cached_file);
4278     }
4279   else
4280     {
4281       /* determine something a hopefully good approximation of the display name */
4282       display_name = thunar_g_file_get_display_name (G_FILE (file));
4283     }
4284 
4285   return display_name;
4286 }
4287 
4288 
4289 
4290 static gint
compare_app_infos(gconstpointer a,gconstpointer b)4291 compare_app_infos (gconstpointer a,
4292                    gconstpointer b)
4293 {
4294   return g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b)) ? 0 : 1;
4295 }
4296 
4297 
4298 
4299 /**
4300  * thunar_file_list_get_applications:
4301  * @file_list : a #GList of #ThunarFile<!---->s.
4302  *
4303  * Returns the #GList of #GAppInfo<!---->s that can be used to open
4304  * all #ThunarFile<!---->s in the given @file_list.
4305  *
4306  * The caller is responsible to free the returned list using something like:
4307  * <informalexample><programlisting>
4308  * g_list_free_full (list, g_object_unref);
4309  * </programlisting></informalexample>
4310  *
4311  * Return value: the list of #GAppInfo<!---->s that can be used to open all
4312  *               items in the @file_list.
4313  **/
4314 GList*
thunar_file_list_get_applications(GList * file_list)4315 thunar_file_list_get_applications (GList *file_list)
4316 {
4317   GList       *applications = NULL;
4318   GList       *list;
4319   GList       *next;
4320   GList       *ap;
4321   GList       *lp;
4322   GAppInfo    *default_application;
4323   const gchar *previous_type = NULL;
4324   const gchar *current_type;
4325 
4326   /* determine the set of applications that can open all files */
4327   for (lp = file_list; lp != NULL; lp = lp->next)
4328     {
4329       current_type = thunar_file_get_content_type (lp->data);
4330 
4331       /* no need to check anything if this file has the same mimetype as the previous file */
4332       if (current_type != NULL && previous_type != NULL)
4333         if (G_LIKELY (g_content_type_equals (previous_type, current_type)))
4334           continue;
4335 
4336       /* store the previous type */
4337       previous_type = current_type;
4338 
4339       /* determine the list of applications that can open this file */
4340       if (G_UNLIKELY (current_type != NULL))
4341         {
4342           list = g_app_info_get_all_for_type (current_type);
4343 
4344           /* move any default application in front of the list */
4345           default_application = g_app_info_get_default_for_type (current_type, FALSE);
4346           if (G_LIKELY (default_application != NULL))
4347             {
4348               for (ap = list; ap != NULL; ap = ap->next)
4349                 {
4350                   if (g_app_info_equal (ap->data, default_application))
4351                     {
4352                       g_object_unref (ap->data);
4353                       list = g_list_delete_link (list, ap);
4354                       break;
4355                     }
4356                 }
4357               list = g_list_prepend (list, default_application);
4358             }
4359         }
4360       else
4361         list = NULL;
4362 
4363       if (G_UNLIKELY (applications == NULL))
4364         {
4365           /* first file, so just use the applications list */
4366           applications = list;
4367         }
4368       else
4369         {
4370           /* keep only the applications that are also present in list */
4371           for (ap = applications; ap != NULL; ap = next)
4372             {
4373               /* grab a pointer on the next application */
4374               next = ap->next;
4375 
4376               /* check if the application is present in list */
4377               if (g_list_find_custom (list, ap->data, compare_app_infos) == NULL)
4378                 {
4379                   /* drop our reference on the application */
4380                   g_object_unref (G_OBJECT (ap->data));
4381 
4382                   /* drop this application from the list */
4383                   applications = g_list_delete_link (applications, ap);
4384                 }
4385             }
4386 
4387           /* release the list of applications for this file */
4388           g_list_free_full (list, g_object_unref);
4389         }
4390 
4391       /* check if the set is still not empty */
4392       if (G_LIKELY (applications == NULL))
4393         break;
4394     }
4395 
4396   /* remove hidden applications */
4397   for (ap = applications; ap != NULL; ap = next)
4398     {
4399       /* grab a pointer on the next application */
4400       next = ap->next;
4401 
4402       if (!thunar_g_app_info_should_show (ap->data))
4403         {
4404           /* drop our reference on the application */
4405           g_object_unref (G_OBJECT (ap->data));
4406 
4407           /* drop this application from the list */
4408           applications = g_list_delete_link (applications, ap);
4409         }
4410     }
4411 
4412   return applications;
4413 }
4414 
4415 
4416 
4417 /**
4418  * thunar_file_list_to_thunar_g_file_list:
4419  * @file_list : a #GList of #ThunarFile<!---->s.
4420  *
4421  * Transforms the @file_list to a #GList of #GFile<!---->s for
4422  * the #ThunarFile<!---->s contained within @file_list.
4423  *
4424  * The caller is responsible to free the returned list using
4425  * thunar_g_file_list_free() when no longer needed.
4426  *
4427  * Return value: the list of #GFile<!---->s for @file_list.
4428  **/
4429 GList*
thunar_file_list_to_thunar_g_file_list(GList * file_list)4430 thunar_file_list_to_thunar_g_file_list (GList *file_list)
4431 {
4432   GList *list = NULL;
4433   GList *lp;
4434 
4435   for (lp = g_list_last (file_list); lp != NULL; lp = lp->prev)
4436     list = g_list_prepend (list, g_object_ref (THUNAR_FILE (lp->data)->gfile));
4437 
4438   return list;
4439 }
4440 
4441 
4442 
4443 /**
4444  * thunar_file_get_metadata_setting:
4445  * @file         : a #ThunarFile instance.
4446  * @setting_name : the name of the setting to get
4447  *
4448  * Gets the stored value of the metadata setting @setting_name for @file. Returns %NULL
4449  * if there is no stored setting.
4450  *
4451  * Return value: (transfer none): the stored value of the setting for @file, or %NULL
4452  **/
4453 const gchar*
thunar_file_get_metadata_setting(ThunarFile * file,const gchar * setting_name)4454 thunar_file_get_metadata_setting (ThunarFile  *file,
4455                                   const gchar *setting_name)
4456 {
4457   gchar       *attr_name;
4458   const gchar *attr_value;
4459 
4460   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
4461 
4462   if (file->info == NULL)
4463     return NULL;
4464 
4465   /* convert the setting name to an attribute name */
4466   attr_name = g_strdup_printf ("metadata::thunar-%s", setting_name);
4467 
4468   if (!g_file_info_has_attribute (file->info, attr_name))
4469     {
4470       g_free (attr_name);
4471       return NULL;
4472     }
4473 
4474   attr_value = g_file_info_get_attribute_string (file->info, attr_name);
4475   g_free (attr_name);
4476 
4477   return attr_value;
4478 }
4479 
4480 
4481 
4482 static void
thunar_file_set_metadata_setting_finish(GObject * source_object,GAsyncResult * result,gpointer user_data)4483 thunar_file_set_metadata_setting_finish (GObject      *source_object,
4484                                          GAsyncResult *result,
4485                                          gpointer      user_data)
4486 {
4487   ThunarFile *file = THUNAR_FILE (user_data);
4488   GError     *error = NULL;
4489 
4490   if (!g_file_set_attributes_finish (G_FILE (source_object), result, NULL, &error))
4491     {
4492       g_warning ("Failed to set metadata: %s", error->message);
4493       g_error_free (error);
4494     }
4495 
4496   thunar_file_changed (file);
4497 }
4498 
4499 
4500 
4501 /**
4502  * thunar_file_set_metadata_setting:
4503  * @file          : a #ThunarFile instance.
4504  * @setting_name  : the name of the setting to set
4505  * @setting_value : the value to set
4506  *
4507  * Sets the setting @setting_name of @file to @setting_value and stores it in
4508  * the @file<!---->s metadata.
4509  **/
4510 void
thunar_file_set_metadata_setting(ThunarFile * file,const gchar * setting_name,const gchar * setting_value)4511 thunar_file_set_metadata_setting (ThunarFile  *file,
4512                                   const gchar *setting_name,
4513                                   const gchar *setting_value)
4514 {
4515   GFileInfo *info;
4516   gchar     *attr_name;
4517 
4518   _thunar_return_if_fail (THUNAR_IS_FILE (file));
4519   _thunar_return_if_fail (G_IS_FILE_INFO (file->info));
4520 
4521   /* convert the setting name to an attribute name */
4522   attr_name = g_strdup_printf ("metadata::thunar-%s", setting_name);
4523 
4524   /* set the value in the current info. this call is needed to update the in-memory
4525    * GFileInfo structure to ensure that the new attribute value is available immediately */
4526   g_file_info_set_attribute_string (file->info, attr_name, setting_value);
4527 
4528   /* send meta data to the daemon. this call is needed to store the new value of
4529    * the attribute in the file system */
4530   info = g_file_info_new ();
4531   g_file_info_set_attribute_string (info, attr_name, setting_value);
4532   g_file_set_attributes_async (file->gfile, info,
4533                                G_FILE_QUERY_INFO_NONE,
4534                                G_PRIORITY_DEFAULT,
4535                                NULL,
4536                                thunar_file_set_metadata_setting_finish,
4537                                file);
4538   g_free (attr_name);
4539   g_object_unref (G_OBJECT (info));
4540 }
4541 
4542 
4543 
4544 /**
4545  * thunar_file_clear_directory_specific_settings:
4546  * @file : a #ThunarFile instance.
4547  *
4548  * Clears all directory specific settings stored in the metadata of the folder represented by @file
4549  **/
4550 void
thunar_file_clear_directory_specific_settings(ThunarFile * file)4551 thunar_file_clear_directory_specific_settings (ThunarFile *file)
4552 {
4553   _thunar_return_if_fail (THUNAR_IS_FILE (file));
4554 
4555   if (file->info == NULL)
4556     return;
4557 
4558   g_file_info_remove_attribute (file->info, "metadata::thunar-view-type");
4559   g_file_info_remove_attribute (file->info, "metadata::thunar-sort-column");
4560   g_file_info_remove_attribute (file->info, "metadata::thunar-sort-order");
4561 
4562   g_file_set_attribute (file->gfile, "metadata::thunar-view-type", G_FILE_ATTRIBUTE_TYPE_INVALID,
4563                         NULL, G_FILE_QUERY_INFO_NONE, NULL, NULL);
4564   g_file_set_attribute (file->gfile, "metadata::thunar-sort-column", G_FILE_ATTRIBUTE_TYPE_INVALID,
4565                         NULL, G_FILE_QUERY_INFO_NONE, NULL, NULL);
4566   g_file_set_attribute (file->gfile, "metadata::thunar-sort-order", G_FILE_ATTRIBUTE_TYPE_INVALID,
4567                         NULL, G_FILE_QUERY_INFO_NONE, NULL, NULL);
4568 
4569   thunar_file_changed (file);
4570 }
4571 
4572 
4573 
4574 /**
4575  * thunar_file_has_directory_specific_settings:
4576  * @file : a #ThunarFile instance.
4577  *
4578  * Checks whether @file has any directory specific settings stored in its metadata.
4579  *
4580  * Return value: %TRUE if @file has any directory specific settings stored in its metadata, and %FALSE otherwise
4581  **/
4582 gboolean
thunar_file_has_directory_specific_settings(ThunarFile * file)4583 thunar_file_has_directory_specific_settings (ThunarFile *file)
4584 {
4585   _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE);
4586 
4587   if (file->info == NULL)
4588     return FALSE;
4589 
4590   if (g_file_info_has_attribute (file->info, "metadata::thunar-view-type"))
4591     return TRUE;
4592   if (g_file_info_has_attribute (file->info, "metadata::thunar-sort-column"))
4593     return TRUE;
4594   if (g_file_info_has_attribute (file->info, "metadata::thunar-sort-order"))
4595     return TRUE;
4596 
4597   return FALSE;
4598 }
4599