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