4 * Copyright (C) 2003, the ROX-Filer team.
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2 of the License, or (at your option)
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 * You should have received a copy of the GNU General Public License along with
17 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
18 * Place, Suite 330, Boston, MA 02111-1307 USA
21 /* dir.c - directory scanning and caching */
25 * A Directory contains a list DirItems, each having a name and some details
26 * (size, image, owner, etc).
28 * There is a list of file names that need to be rechecked. While this
29 * list is non-empty, items are taken from the list in an idle callback
30 * and checked. Missing items are removed from the Directory, new items are
31 * added and existing items are updated if they've changed.
33 * When a whole directory is to be rescanned:
35 * - A list of all filenames in the directory is fetched, without any
36 * of the extra details.
37 * - This list is compared to the current DirItems, removing any that are now
39 * - The recheck list is replaced with this new list.
41 * This system is designed to get the number of items and their names quickly,
42 * so that the auto-sizer can make a good guess.
44 * To get the Directory object, use dir_cache, which will automatically
45 * trigger a rescan if needed.
47 * To get notified when the Directory changes, use the dir_attach() and
48 * dir_detach() functions.
63 #include "gui_support.h"
69 #include "usericons.h"
73 /* Newer Linux kernels can tell use when the directories we are watching
74 * change using the dnotify system.
76 static GHashTable
*dnotify_fd_to_dir
= NULL
;
77 gboolean dnotify_wakeup_flag
= FALSE
;
78 static int dnotify_last_fd
= -1;
81 GFSCache
*dir_cache
= NULL
;
83 /* Static prototypes */
84 static void update(Directory
*dir
, gchar
*pathname
, gpointer data
);
85 static void set_idle_callback(Directory
*dir
);
86 static DirItem
*insert_item(Directory
*dir
, const guchar
*leafname
);
87 static void remove_missing(Directory
*dir
, GPtrArray
*keep
);
88 static void dir_recheck(Directory
*dir
,
89 const guchar
*path
, const guchar
*leafname
);
90 static GPtrArray
*hash_to_array(GHashTable
*hash
);
91 static void dir_force_update_item(Directory
*dir
, const gchar
*leaf
);
92 static Directory
*dir_new(const char *pathname
);
93 static void dir_rescan(Directory
*dir
);
95 static void dnotify_handler(int sig
, siginfo_t
*si
, void *data
);
98 /****************************************************************
99 * EXTERNAL INTERFACE *
100 ****************************************************************/
104 dir_cache
= g_fscache_new((GFSLoadFunc
) dir_new
,
105 (GFSUpdateFunc
) update
, NULL
);
107 /* Check for dnotify support in the kernel */
110 struct sigaction act
;
112 act
.sa_sigaction
= dnotify_handler
;
113 sigemptyset(&act
.sa_mask
);
114 act
.sa_flags
= SA_SIGINFO
;
115 sigaction(SIGRTMIN
, &act
, NULL
);
117 /* Sometimes we get this instead of SIGRTMIN.
118 * Don't know why :-( but don't crash...
120 act
.sa_handler
= SIG_IGN
;
121 sigemptyset(&act
.sa_mask
);
123 sigaction(SIGIO
, &act
, NULL
);
125 dnotify_fd_to_dir
= g_hash_table_new(NULL
, NULL
);
130 /* Periodically calls callback to notify about changes to the contents
132 * Before this function returns, it calls the callback once to add all
133 * the items currently in the directory (unless the dir is empty).
134 * If we are not scanning, it also calls update(DIR_END_SCAN).
136 void dir_attach(Directory
*dir
, DirCallback callback
, gpointer data
)
141 g_return_if_fail(dir
!= NULL
);
142 g_return_if_fail(callback
!= NULL
);
144 user
= g_new(DirUser
, 1);
145 user
->callback
= callback
;
153 if (dir
->dnotify_fd
!= -1)
154 g_warning("dir_attach: dnotify error\n");
156 fd
= open(dir
->pathname
, O_RDONLY
);
157 g_return_if_fail(g_hash_table_lookup(dnotify_fd_to_dir
,
158 GINT_TO_POINTER(fd
)) == NULL
);
161 dir
->dnotify_fd
= fd
;
162 g_hash_table_insert(dnotify_fd_to_dir
,
163 GINT_TO_POINTER(fd
), dir
);
164 fcntl(fd
, F_SETSIG
, SIGRTMIN
);
165 fcntl(fd
, F_NOTIFY
, DN_CREATE
| DN_DELETE
| DN_RENAME
|
166 DN_ATTRIB
| DN_MULTISHOT
);
171 dir
->users
= g_list_prepend(dir
->users
, user
);
175 items
= hash_to_array(dir
->known_items
);
177 callback(dir
, DIR_ADD
, items
, data
);
178 g_ptr_array_free(items
, TRUE
);
180 if (dir
->needs_update
&& !dir
->scanning
)
183 /* May start scanning if noone was watching before */
184 set_idle_callback(dir
);
187 callback(dir
, DIR_END_SCAN
, NULL
, data
);
190 /* Undo the effect of dir_attach */
191 void dir_detach(Directory
*dir
, DirCallback callback
, gpointer data
)
196 g_return_if_fail(dir
!= NULL
);
197 g_return_if_fail(callback
!= NULL
);
199 for (list
= dir
->users
; list
; list
= list
->next
)
201 user
= (DirUser
*) list
->data
;
202 if (user
->callback
== callback
&& user
->data
== data
)
205 dir
->users
= g_list_remove(dir
->users
, user
);
208 /* May stop scanning if noone's watching */
209 set_idle_callback(dir
);
212 if (!dir
->users
&& dir
->dnotify_fd
!= -1)
214 close(dir
->dnotify_fd
);
215 g_hash_table_remove(dnotify_fd_to_dir
,
216 GINT_TO_POINTER(dir
->dnotify_fd
));
217 dir
->dnotify_fd
= -1;
224 g_warning("dir_detach: Callback/data pair not attached!\n");
227 void dir_update(Directory
*dir
, gchar
*pathname
)
229 update(dir
, pathname
, NULL
);
232 /* Rescan this directory */
233 void refresh_dirs(const char *path
)
235 g_fscache_update(dir_cache
, path
);
238 /* When something has happened to a particular object, call this
239 * and all appropriate changes will be made.
241 void dir_check_this(const guchar
*path
)
247 dir_path
= g_path_get_dirname(path
);
248 real_path
= pathdup(dir_path
);
251 dir
= g_fscache_lookup_full(dir_cache
, real_path
,
252 FSCACHE_LOOKUP_PEEK
, NULL
);
255 dir_recheck(dir
, real_path
, g_basename(path
));
263 static void drop_dnotify(gpointer key
, gpointer value
, gpointer data
)
265 close(GPOINTER_TO_INT(key
));
269 /* Used when we fork an action child, otherwise we can't delete or unmount
270 * any directory which we're watching!
272 void dir_drop_all_dnotifies(void)
275 g_hash_table_foreach(dnotify_fd_to_dir
, drop_dnotify
, NULL
);
279 /* Tell watchers that this item has changed, but don't rescan.
280 * (used when thumbnail has been created for an item)
282 void dir_force_update_path(const gchar
*path
)
287 g_return_if_fail(path
[0] == '/');
289 dir_path
= g_path_get_dirname(path
);
291 dir
= g_fscache_lookup_full(dir_cache
, dir_path
, FSCACHE_LOOKUP_PEEK
,
295 dir_force_update_item(dir
, g_basename(path
));
302 /* Ensure that 'leafname' is up-to-date. Returns the new/updated
303 * DirItem, or NULL if the file no longer exists.
305 DirItem
*dir_update_item(Directory
*dir
, const gchar
*leafname
)
309 time(&diritem_recent_time
);
310 item
= insert_item(dir
, leafname
);
316 static int sort_names(const void *a
, const void *b
)
318 return strcmp(*((char **) a
), *((char **) b
));
321 static void free_recheck_list(Directory
*dir
)
323 destroy_glist(&dir
->recheck_list
);
326 /* If scanning state has changed then notify all filer windows */
327 static void dir_set_scanning(Directory
*dir
, gboolean scanning
)
331 if (scanning
== dir
->scanning
)
334 dir
->scanning
= scanning
;
336 for (next
= dir
->users
; next
; next
= next
->next
)
338 DirUser
*user
= (DirUser
*) next
->data
;
341 scanning
? DIR_START_SCAN
: DIR_END_SCAN
,
346 /* Notify everyone that the error status of the directory has changed */
347 static void dir_error_changed(Directory
*dir
)
351 for (next
= dir
->users
; next
; next
= next
->next
)
353 DirUser
*user
= (DirUser
*) next
->data
;
355 user
->callback(dir
, DIR_ERROR_CHANGED
, NULL
, user
->data
);
359 /* This is called in the background when there are items on the
360 * dir->recheck_list to process.
362 static gboolean
recheck_callback(gpointer data
)
364 Directory
*dir
= (Directory
*) data
;
368 g_return_val_if_fail(dir
!= NULL
, FALSE
);
369 g_return_val_if_fail(dir
->recheck_list
!= NULL
, FALSE
);
371 /* Remove the first name from the list */
372 next
= dir
->recheck_list
;
373 dir
->recheck_list
= g_list_remove_link(dir
->recheck_list
, next
);
374 leaf
= (guchar
*) next
->data
;
379 insert_item(dir
, leaf
);
383 if (dir
->recheck_list
)
384 return TRUE
; /* Call again */
386 /* The recheck_list list empty. Stop scanning, unless
387 * needs_update, in which case we start scanning again.
392 dir
->have_scanned
= TRUE
;
393 dir_set_scanning(dir
, FALSE
);
394 gtk_idle_remove(dir
->idle_callback
);
395 dir
->idle_callback
= 0;
397 if (dir
->needs_update
)
403 /* Add all the new items to the items array.
404 * Notify everyone who is watching us.
406 void dir_merge_new(Directory
*dir
)
408 GPtrArray
*new = dir
->new_items
;
409 GPtrArray
*up
= dir
->up_items
;
410 GPtrArray
*gone
= dir
->gone_items
;
414 for (list
= dir
->users
; list
; list
= list
->next
)
416 DirUser
*user
= (DirUser
*) list
->data
;
419 user
->callback(dir
, DIR_ADD
, new, user
->data
);
421 user
->callback(dir
, DIR_UPDATE
, up
, user
->data
);
423 user
->callback(dir
, DIR_REMOVE
, gone
, user
->data
);
426 for (i
= 0; i
< new->len
; i
++)
428 DirItem
*item
= (DirItem
*) new->pdata
[i
];
430 g_hash_table_insert(dir
->known_items
, item
->leafname
, item
);
433 for (i
= 0; i
< gone
->len
; i
++)
435 DirItem
*item
= (DirItem
*) gone
->pdata
[i
];
440 g_ptr_array_set_size(gone
, 0);
441 g_ptr_array_set_size(new, 0);
442 g_ptr_array_set_size(up
, 0);
446 /* Called from the mainloop shortly after dnotify_handler */
447 void dnotify_wakeup(void)
451 dnotify_wakeup_flag
= FALSE
;
453 dir
= g_hash_table_lookup(dnotify_fd_to_dir
,
454 GINT_TO_POINTER(dnotify_last_fd
));
460 dir
->needs_update
= TRUE
;
466 /****************************************************************
467 * INTERNAL FUNCTIONS *
468 ****************************************************************/
470 static void free_items_array(GPtrArray
*array
)
474 for (i
= 0; i
< array
->len
; i
++)
476 DirItem
*item
= (DirItem
*) array
->pdata
[i
];
481 g_ptr_array_free(array
, TRUE
);
484 /* Tell everyone watching that these items have gone */
485 static void notify_deleted(Directory
*dir
, GPtrArray
*deleted
)
492 for (next
= dir
->users
; next
; next
= next
->next
)
494 DirUser
*user
= (DirUser
*) next
->data
;
496 user
->callback(dir
, DIR_REMOVE
, deleted
, user
->data
);
500 static void mark_unused(gpointer key
, gpointer value
, gpointer data
)
502 DirItem
*item
= (DirItem
*) value
;
504 item
->may_delete
= TRUE
;
507 static void keep_deleted(gpointer key
, gpointer value
, gpointer data
)
509 DirItem
*item
= (DirItem
*) value
;
510 GPtrArray
*deleted
= (GPtrArray
*) data
;
512 if (item
->may_delete
)
513 g_ptr_array_add(deleted
, item
);
516 static gboolean
check_unused(gpointer key
, gpointer value
, gpointer data
)
518 DirItem
*item
= (DirItem
*) value
;
520 return item
->may_delete
;
523 /* Remove all the old items that have gone.
524 * Notify everyone who is watching us of the removed items.
526 static void remove_missing(Directory
*dir
, GPtrArray
*keep
)
531 deleted
= g_ptr_array_new();
533 /* Mark all current items as may_delete */
534 g_hash_table_foreach(dir
->known_items
, mark_unused
, NULL
);
536 /* Unmark all items also in 'keep' */
537 for (i
= 0; i
< keep
->len
; i
++)
539 guchar
*leaf
= (guchar
*) keep
->pdata
[i
];
542 item
= g_hash_table_lookup(dir
->known_items
, leaf
);
545 item
->may_delete
= FALSE
;
548 /* Add each item still marked to 'deleted' */
549 g_hash_table_foreach(dir
->known_items
, keep_deleted
, deleted
);
551 /* Remove all items still marked */
552 g_hash_table_foreach_remove(dir
->known_items
, check_unused
, NULL
);
554 notify_deleted(dir
, deleted
);
556 free_items_array(deleted
);
559 static gint
notify_timeout(gpointer data
)
561 Directory
*dir
= (Directory
*) data
;
563 g_return_val_if_fail(dir
->notify_active
== TRUE
, FALSE
);
567 dir
->notify_active
= FALSE
;
573 /* Call dir_merge_new() after a while. */
574 static void delayed_notify(Directory
*dir
)
576 if (dir
->notify_active
)
579 gtk_timeout_add(1500, notify_timeout
, dir
);
580 dir
->notify_active
= TRUE
;
583 /* Stat this item and add, update or remove it.
584 * Returns the new/updated item, if any.
585 * (leafname may be from the current DirItem item)
586 * Ensure diritem_recent_time is reasonably up-to-date before calling this.
588 static DirItem
*insert_item(Directory
*dir
, const guchar
*leafname
)
590 const gchar
*full_path
;
593 gboolean do_compare
= FALSE
; /* (old is filled in) */
595 if (leafname
[0] == '.' && (leafname
[1] == '\n' ||
596 (leafname
[1] == '.' && leafname
[2] == '\n')))
597 return NULL
; /* Ignore '.' and '..' */
599 full_path
= make_path(dir
->pathname
, leafname
);
600 item
= g_hash_table_lookup(dir
->known_items
, leafname
);
604 if (item
->base_type
!= TYPE_UNKNOWN
)
606 /* Preserve the old details so we can compare */
609 g_object_ref(old
.image
);
612 diritem_restat(full_path
, item
, &dir
->stat_info
);
616 /* Item isn't already here. This won't normally happen,
617 * because blank items are added when scanning, before
620 item
= diritem_new(leafname
);
621 diritem_restat(full_path
, item
, &dir
->stat_info
);
622 if (item
->base_type
== TYPE_ERROR
&&
623 item
->lstat_errno
== ENOENT
)
628 g_ptr_array_add(dir
->new_items
, item
);
632 if (item
->base_type
== TYPE_ERROR
&& item
->lstat_errno
== ENOENT
)
634 /* Item has been deleted */
635 g_hash_table_remove(dir
->known_items
, item
->leafname
);
636 g_ptr_array_add(dir
->gone_items
, item
);
637 if (do_compare
&& old
.image
)
638 g_object_unref(old
.image
);
645 if (item
->lstat_errno
== old
.lstat_errno
646 && item
->base_type
== old
.base_type
647 && item
->flags
== old
.flags
648 && item
->size
== old
.size
649 && item
->mode
== old
.mode
650 && item
->atime
== old
.atime
651 && item
->ctime
== old
.ctime
652 && item
->mtime
== old
.mtime
653 && item
->uid
== old
.uid
654 && item
->gid
== old
.gid
655 && item
->image
== old
.image
656 && item
->mime_type
== old
.mime_type
)
659 g_object_unref(old
.image
);
663 g_object_unref(old
.image
);
666 g_ptr_array_add(dir
->up_items
, item
);
672 static void update(Directory
*dir
, gchar
*pathname
, gpointer data
)
674 g_free(dir
->pathname
);
675 dir
->pathname
= pathdup(pathname
);
678 dir
->needs_update
= TRUE
;
683 /* If there is work to do, set the idle callback.
684 * Otherwise, stop scanning and unset the idle callback.
686 static void set_idle_callback(Directory
*dir
)
688 if (dir
->recheck_list
&& dir
->users
)
690 /* Work to do, and someone's watching */
691 dir_set_scanning(dir
, TRUE
);
692 if (dir
->idle_callback
)
694 time(&diritem_recent_time
);
695 dir
->idle_callback
= gtk_idle_add(recheck_callback
, dir
);
696 /* Do the first call now (will remove the callback itself) */
697 recheck_callback(dir
);
701 dir_set_scanning(dir
, FALSE
);
702 if (dir
->idle_callback
)
704 gtk_idle_remove(dir
->idle_callback
);
705 dir
->idle_callback
= 0;
710 /* See dir_force_update_path() */
711 static void dir_force_update_item(Directory
*dir
, const gchar
*leaf
)
717 items
= g_ptr_array_new();
719 item
= g_hash_table_lookup(dir
->known_items
, leaf
);
723 g_ptr_array_add(items
, item
);
725 for (list
= dir
->users
; list
; list
= list
->next
)
727 DirUser
*user
= (DirUser
*) list
->data
;
729 user
->callback(dir
, DIR_UPDATE
, items
, user
->data
);
733 g_ptr_array_free(items
, TRUE
);
736 static void dir_recheck(Directory
*dir
,
737 const guchar
*path
, const guchar
*leafname
)
739 guchar
*old
= dir
->pathname
;
741 dir
->pathname
= g_strdup(path
);
744 time(&diritem_recent_time
);
745 insert_item(dir
, leafname
);
748 static void to_array(gpointer key
, gpointer value
, gpointer data
)
750 GPtrArray
*array
= (GPtrArray
*) data
;
752 g_ptr_array_add(array
, value
);
755 /* Convert a hash table to an unsorted GPtrArray.
756 * g_ptr_array_free() the result.
758 static GPtrArray
*hash_to_array(GHashTable
*hash
)
762 array
= g_ptr_array_new();
764 g_hash_table_foreach(hash
, to_array
, array
);
769 static gpointer parent_class
;
771 /* Note: dir_cache is never purged, so this shouldn't get called */
772 static void dir_finialize(GObject
*object
)
775 Directory
*dir
= (Directory
*) object
;
777 g_return_if_fail(dir
->users
== NULL
);
779 g_print("[ dir finalize ]\n");
781 free_recheck_list(dir
);
782 set_idle_callback(dir
);
784 dir_merge_new(dir
); /* Ensures new, up and gone are empty */
786 g_ptr_array_free(dir
->up_items
, TRUE
);
787 g_ptr_array_free(dir
->new_items
, TRUE
);
788 g_ptr_array_free(dir
->gone_items
, TRUE
);
790 items
= hash_to_array(dir
->known_items
);
791 free_items_array(items
);
792 g_hash_table_destroy(dir
->known_items
);
795 g_free(dir
->pathname
);
797 G_OBJECT_CLASS(parent_class
)->finalize(object
);
800 static void directory_class_init(gpointer gclass
, gpointer data
)
802 GObjectClass
*object
= (GObjectClass
*) gclass
;
804 parent_class
= g_type_class_peek_parent(gclass
);
806 object
->finalize
= dir_finialize
;
809 static void directory_init(GTypeInstance
*object
, gpointer gclass
)
811 Directory
*dir
= (Directory
*) object
;
813 dir
->known_items
= g_hash_table_new(g_str_hash
, g_str_equal
);
814 dir
->recheck_list
= NULL
;
815 dir
->idle_callback
= 0;
816 dir
->scanning
= FALSE
;
817 dir
->have_scanned
= FALSE
;
820 dir
->needs_update
= TRUE
;
821 dir
->notify_active
= FALSE
;
822 dir
->pathname
= NULL
;
825 dir
->dnotify_fd
= -1;
828 dir
->new_items
= g_ptr_array_new();
829 dir
->up_items
= g_ptr_array_new();
830 dir
->gone_items
= g_ptr_array_new();
833 static GType
dir_get_type(void)
835 static GType type
= 0;
839 static const GTypeInfo info
=
841 sizeof (DirectoryClass
),
842 NULL
, /* base_init */
843 NULL
, /* base_finalise */
844 directory_class_init
,
845 NULL
, /* class_finalise */
846 NULL
, /* class_data */
852 type
= g_type_register_static(G_TYPE_OBJECT
, "Directory",
859 static Directory
*dir_new(const char *pathname
)
863 dir
= g_object_new(dir_get_type(), NULL
);
865 dir
->pathname
= g_strdup(pathname
);
870 /* Get the names of all files in the directory.
871 * Remove any DirItems that are no longer listed.
872 * Replace the recheck_list with the items found.
874 static void dir_rescan(Directory
*dir
)
880 const char *pathname
;
881 int longest_len
= -1;
882 GList
*longest
= NULL
;
884 g_return_if_fail(dir
!= NULL
);
886 pathname
= dir
->pathname
;
888 dir
->needs_update
= FALSE
;
890 names
= g_ptr_array_new();
896 null_g_free(&dir
->error
);
897 dir_error_changed(dir
);
900 /* Saves statting the parent for each item... */
901 if (mc_stat(pathname
, &dir
->stat_info
))
903 dir
->error
= g_strdup_printf(_("Can't stat directory: %s"),
905 dir_error_changed(dir
);
906 return; /* Report on attach */
909 d
= mc_opendir(pathname
);
912 dir
->error
= g_strdup_printf(_("Can't open directory: %s"),
914 dir_error_changed(dir
);
915 return; /* Report on attach */
918 dir_set_scanning(dir
, TRUE
);
922 /* Make a list of all the names in the directory */
923 while ((ent
= mc_readdir(d
)))
925 if (ent
->d_name
[0] == '.')
927 if (ent
->d_name
[1] == '\0')
928 continue; /* Ignore '.' */
929 if (ent
->d_name
[1] == '.' && ent
->d_name
[2] == '\0')
930 continue; /* Ignore '..' */
933 g_ptr_array_add(names
, g_strdup(ent
->d_name
));
937 /* Sort, so the names are scanned in a sensible order */
938 qsort(names
->pdata
, names
->len
, sizeof(guchar
*), sort_names
);
940 /* Compare the list with the current DirItems, removing
941 * any that are missing.
943 remove_missing(dir
, names
);
945 free_recheck_list(dir
);
947 /* For each name found, add it to the recheck_list.
948 * If the item is new, put a blank place-holder item in the directory.
950 for (i
= 0; i
< names
->len
; i
++)
952 guchar
*name
= names
->pdata
[i
];
953 int len
= strlen(name
);
955 dir
->recheck_list
= g_list_prepend(dir
->recheck_list
, name
);
956 if (!g_hash_table_lookup(dir
->known_items
, name
))
960 new = diritem_new(name
);
961 g_ptr_array_add(dir
->new_items
, new);
964 if (name
[0] != '.' && len
> longest_len
)
967 longest
= dir
->recheck_list
;
973 dir
->recheck_list
= g_list_reverse(dir
->recheck_list
);
977 gpointer data
= longest
->data
;
979 /* Move the longest item to the start. Helps with
982 dir
->recheck_list
= g_list_remove_link(dir
->recheck_list
,
984 dir
->recheck_list
= g_list_prepend(dir
->recheck_list
, data
);
987 g_ptr_array_free(names
, TRUE
);
989 set_idle_callback(dir
);
994 /* Signal handler - don't do anything dangerous here */
995 static void dnotify_handler(int sig
, siginfo_t
*si
, void *data
)
997 /* Note: there is currently only one place to store the fd,
998 * so we'll miss updates to several directories if they happen
1001 dnotify_last_fd
= si
->si_fd
;
1002 dnotify_wakeup_flag
= TRUE
;
1003 write(to_wakeup_pipe
, "\0", 1); /* Wake up! */