4 * Copyright (C) 2002, 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"
71 GFSCache
*dir_cache
= NULL
;
73 /* Static prototypes */
74 static void update(Directory
*dir
, gchar
*pathname
, gpointer data
);
75 static void set_idle_callback(Directory
*dir
);
76 static DirItem
*insert_item(Directory
*dir
, const guchar
*leafname
);
77 static void remove_missing(Directory
*dir
, GPtrArray
*keep
);
78 static void dir_recheck(Directory
*dir
,
79 const guchar
*path
, const guchar
*leafname
);
80 static GPtrArray
*hash_to_array(GHashTable
*hash
);
81 static void dir_force_update_item(Directory
*dir
, const gchar
*leaf
);
82 static Directory
*dir_new(const char *pathname
);
84 /****************************************************************
85 * EXTERNAL INTERFACE *
86 ****************************************************************/
90 dir_cache
= g_fscache_new((GFSLoadFunc
) dir_new
,
91 (GFSUpdateFunc
) update
, NULL
);
94 /* Periodically calls callback to notify about changes to the contents
96 * Before this function returns, it calls the callback once to add all
97 * the items currently in the directory (unless the dir is empty).
98 * If we are not scanning, it also calls update(DIR_END_SCAN).
100 void dir_attach(Directory
*dir
, DirCallback callback
, gpointer data
)
105 g_return_if_fail(dir
!= NULL
);
106 g_return_if_fail(callback
!= NULL
);
108 user
= g_new(DirUser
, 1);
109 user
->callback
= callback
;
112 dir
->users
= g_list_prepend(dir
->users
, user
);
116 items
= hash_to_array(dir
->known_items
);
118 callback(dir
, DIR_ADD
, items
, data
);
119 g_ptr_array_free(items
, TRUE
);
121 if (dir
->needs_update
&& !dir
->scanning
)
122 dir_rescan(dir
, dir
->pathname
);
124 /* May start scanning if noone was watching before */
125 set_idle_callback(dir
);
128 callback(dir
, DIR_END_SCAN
, NULL
, data
);
131 delayed_error(_("Error scanning '%s':\n%s"),
132 dir
->pathname
, dir
->error
);
135 /* Undo the effect of dir_attach */
136 void dir_detach(Directory
*dir
, DirCallback callback
, gpointer data
)
141 g_return_if_fail(dir
!= NULL
);
142 g_return_if_fail(callback
!= NULL
);
144 for (list
= dir
->users
; list
; list
= list
->next
)
146 user
= (DirUser
*) list
->data
;
147 if (user
->callback
== callback
&& user
->data
== data
)
150 dir
->users
= g_list_remove(dir
->users
, user
);
153 /* May stop scanning if noone's watching */
154 set_idle_callback(dir
);
160 g_warning("dir_detach: Callback/data pair not attached!\n");
163 void dir_update(Directory
*dir
, gchar
*pathname
)
165 update(dir
, pathname
, NULL
);
168 /* Rescan this directory */
169 void refresh_dirs(const char *path
)
171 g_fscache_update(dir_cache
, path
);
174 /* When something has happened to a particular object, call this
175 * and all appropriate changes will be made.
177 void dir_check_this(const guchar
*path
)
183 dir_path
= g_dirname(path
);
184 real_path
= pathdup(dir_path
);
187 dir
= g_fscache_lookup_full(dir_cache
, real_path
,
188 FSCACHE_LOOKUP_PEEK
, NULL
);
191 dir_recheck(dir
, real_path
, g_basename(path
));
198 /* Tell watchers that this item has changed, but don't rescan.
199 * (used when thumbnail has been created for an item)
201 void dir_force_update_path(const gchar
*path
)
206 g_return_if_fail(path
[0] == '/');
208 dir_path
= g_dirname(path
);
210 dir
= g_fscache_lookup_full(dir_cache
, dir_path
, FSCACHE_LOOKUP_PEEK
,
214 dir_force_update_item(dir
, g_basename(path
));
221 /* Ensure that 'leafname' is up-to-date. Returns the new/updated
222 * DirItem, or NULL if the file no longer exists.
224 DirItem
*dir_update_item(Directory
*dir
, const gchar
*leafname
)
228 item
= insert_item(dir
, leafname
);
234 static int sort_names(const void *a
, const void *b
)
236 return strcmp(*((char **) a
), *((char **) b
));
239 static void free_recheck_list(Directory
*dir
)
243 for (next
= dir
->recheck_list
; next
; next
= next
->next
)
246 g_list_free(dir
->recheck_list
);
248 dir
->recheck_list
= NULL
;
251 /* If scanning state has changed then notify all filer windows */
252 static void dir_set_scanning(Directory
*dir
, gboolean scanning
)
256 if (scanning
== dir
->scanning
)
259 dir
->scanning
= scanning
;
261 for (next
= dir
->users
; next
; next
= next
->next
)
263 DirUser
*user
= (DirUser
*) next
->data
;
266 scanning
? DIR_START_SCAN
: DIR_END_SCAN
,
271 /* This is called in the background when there are items on the
272 * dir->recheck_list to process.
274 static gboolean
recheck_callback(gpointer data
)
276 Directory
*dir
= (Directory
*) data
;
280 g_return_val_if_fail(dir
!= NULL
, FALSE
);
281 g_return_val_if_fail(dir
->recheck_list
!= NULL
, FALSE
);
283 /* Remove the first name from the list */
284 next
= dir
->recheck_list
;
285 dir
->recheck_list
= g_list_remove_link(dir
->recheck_list
, next
);
286 leaf
= (guchar
*) next
->data
;
291 insert_item(dir
, leaf
);
295 if (dir
->recheck_list
)
296 return TRUE
; /* Call again */
298 /* The recheck_list list empty. Stop scanning, unless
299 * needs_update, in which case we start scanning again.
304 dir
->have_scanned
= TRUE
;
305 dir_set_scanning(dir
, FALSE
);
306 gtk_idle_remove(dir
->idle_callback
);
307 dir
->idle_callback
= 0;
309 if (dir
->needs_update
)
310 dir_rescan(dir
, dir
->pathname
);
315 /* Get the names of all files in the directory.
316 * Remove any DirItems that are no longer listed.
317 * Replace the recheck_list with the items found.
319 void dir_rescan(Directory
*dir
, const guchar
*pathname
)
326 g_return_if_fail(dir
!= NULL
);
327 g_return_if_fail(pathname
!= NULL
);
329 dir
->needs_update
= FALSE
;
331 names
= g_ptr_array_new();
341 /* Saves statting the parent for each item... */
342 if (mc_stat(pathname
, &dir
->stat_info
))
344 dir
->error
= g_strdup_printf(_("Can't stat directory: %s"),
346 return; /* Report on attach */
349 d
= mc_opendir(pathname
);
352 dir
->error
= g_strdup_printf(_("Can't open directory: %s"),
354 return; /* Report on attach */
357 dir_set_scanning(dir
, TRUE
);
361 /* Make a list of all the names in the directory */
362 while ((ent
= mc_readdir(d
)))
364 if (ent
->d_name
[0] == '.')
366 if (ent
->d_name
[1] == '\0')
367 continue; /* Ignore '.' */
368 if (ent
->d_name
[1] == '.' && ent
->d_name
[2] == '\0')
369 continue; /* Ignore '..' */
372 g_ptr_array_add(names
, g_strdup(ent
->d_name
));
375 /* Sort, so the names are scanned in a sensible order */
376 qsort(names
->pdata
, names
->len
, sizeof(guchar
*), sort_names
);
378 /* Compare the list with the current DirItems, removing
379 * any that are missing.
381 remove_missing(dir
, names
);
383 free_recheck_list(dir
);
385 /* For each name found, add it to the recheck_list.
386 * If the item is new, put a blank place-holder item in the directory.
388 for (i
= 0; i
< names
->len
; i
++)
390 guchar
*name
= names
->pdata
[i
];
391 dir
->recheck_list
= g_list_prepend(dir
->recheck_list
, name
);
392 if (!g_hash_table_lookup(dir
->known_items
, name
))
396 new = diritem_new(name
);
397 g_ptr_array_add(dir
->new_items
, new);
402 dir
->recheck_list
= g_list_reverse(dir
->recheck_list
);
404 g_ptr_array_free(names
, TRUE
);
407 set_idle_callback(dir
);
410 /* Add all the new items to the items array.
411 * Notify everyone who is watching us.
413 void dir_merge_new(Directory
*dir
)
415 GPtrArray
*new = dir
->new_items
;
416 GPtrArray
*up
= dir
->up_items
;
417 GPtrArray
*gone
= dir
->gone_items
;
421 for (list
= dir
->users
; list
; list
= list
->next
)
423 DirUser
*user
= (DirUser
*) list
->data
;
426 user
->callback(dir
, DIR_ADD
, new, user
->data
);
428 user
->callback(dir
, DIR_UPDATE
, up
, user
->data
);
430 user
->callback(dir
, DIR_REMOVE
, gone
, user
->data
);
433 for (i
= 0; i
< new->len
; i
++)
435 DirItem
*item
= (DirItem
*) new->pdata
[i
];
437 g_hash_table_insert(dir
->known_items
, item
->leafname
, item
);
440 for (i
= 0; i
< gone
->len
; i
++)
442 DirItem
*item
= (DirItem
*) gone
->pdata
[i
];
447 g_ptr_array_set_size(gone
, 0);
448 g_ptr_array_set_size(new, 0);
449 g_ptr_array_set_size(up
, 0);
452 /****************************************************************
453 * INTERNAL FUNCTIONS *
454 ****************************************************************/
456 static void free_items_array(GPtrArray
*array
)
460 for (i
= 0; i
< array
->len
; i
++)
462 DirItem
*item
= (DirItem
*) array
->pdata
[i
];
467 g_ptr_array_free(array
, TRUE
);
470 /* Tell everyone watching that these items have gone */
471 static void notify_deleted(Directory
*dir
, GPtrArray
*deleted
)
478 for (next
= dir
->users
; next
; next
= next
->next
)
480 DirUser
*user
= (DirUser
*) next
->data
;
482 user
->callback(dir
, DIR_REMOVE
, deleted
, user
->data
);
486 static void mark_unused(gpointer key
, gpointer value
, gpointer data
)
488 DirItem
*item
= (DirItem
*) value
;
490 item
->may_delete
= TRUE
;
493 static void keep_deleted(gpointer key
, gpointer value
, gpointer data
)
495 DirItem
*item
= (DirItem
*) value
;
496 GPtrArray
*deleted
= (GPtrArray
*) data
;
498 if (item
->may_delete
)
499 g_ptr_array_add(deleted
, item
);
502 static gboolean
check_unused(gpointer key
, gpointer value
, gpointer data
)
504 DirItem
*item
= (DirItem
*) value
;
506 return item
->may_delete
;
509 /* Remove all the old items that have gone.
510 * Notify everyone who is watching us of the removed items.
512 static void remove_missing(Directory
*dir
, GPtrArray
*keep
)
517 deleted
= g_ptr_array_new();
519 /* Mark all current items as may_delete */
520 g_hash_table_foreach(dir
->known_items
, mark_unused
, NULL
);
522 /* Unmark all items also in 'keep' */
523 for (i
= 0; i
< keep
->len
; i
++)
525 guchar
*leaf
= (guchar
*) keep
->pdata
[i
];
528 item
= g_hash_table_lookup(dir
->known_items
, leaf
);
531 item
->may_delete
= FALSE
;
534 /* Add each item still marked to 'deleted' */
535 g_hash_table_foreach(dir
->known_items
, keep_deleted
, deleted
);
537 /* Remove all items still marked */
538 g_hash_table_foreach_remove(dir
->known_items
, check_unused
, NULL
);
540 notify_deleted(dir
, deleted
);
542 free_items_array(deleted
);
545 static gint
notify_timeout(gpointer data
)
547 Directory
*dir
= (Directory
*) data
;
549 g_return_val_if_fail(dir
->notify_active
== TRUE
, FALSE
);
553 dir
->notify_active
= FALSE
;
559 /* Call dir_merge_new() after a while. */
560 static void delayed_notify(Directory
*dir
)
562 if (dir
->notify_active
)
565 gtk_timeout_add(500, notify_timeout
, dir
);
566 dir
->notify_active
= TRUE
;
569 /* Stat this item and add, update or remove it.
570 * Returns the new/updated item, if any.
571 * (leafname may be from the current DirItem item)
573 static DirItem
*insert_item(Directory
*dir
, const guchar
*leafname
)
575 const gchar
*full_path
;
578 gboolean do_compare
= FALSE
; /* (old is filled in) */
580 full_path
= make_path(dir
->pathname
, leafname
)->str
;
581 item
= g_hash_table_lookup(dir
->known_items
, leafname
);
585 if (item
->base_type
!= TYPE_UNKNOWN
)
587 /* Preserve the old details so we can compare */
590 g_object_ref(old
.image
);
593 diritem_restat(full_path
, item
, &dir
->stat_info
);
597 /* Item isn't already here. This won't normally happen,
598 * because blank items are added when scanning, before
601 item
= diritem_new(leafname
);
602 diritem_restat(full_path
, item
, &dir
->stat_info
);
603 if (item
->base_type
== TYPE_ERROR
&&
604 item
->lstat_errno
== ENOENT
)
609 g_ptr_array_add(dir
->new_items
, item
);
613 if (item
->base_type
== TYPE_ERROR
&& item
->lstat_errno
== ENOENT
)
615 /* Item has been deleted */
616 g_hash_table_remove(dir
->known_items
, item
->leafname
);
617 g_ptr_array_add(dir
->gone_items
, item
);
618 if (do_compare
&& old
.image
)
619 g_object_unref(old
.image
);
626 if (item
->lstat_errno
== old
.lstat_errno
627 && item
->base_type
== old
.base_type
628 && item
->flags
== old
.flags
629 && item
->size
== old
.size
630 && item
->mode
== old
.mode
631 && item
->atime
== old
.atime
632 && item
->ctime
== old
.ctime
633 && item
->mtime
== old
.mtime
634 && item
->uid
== old
.uid
635 && item
->gid
== old
.gid
636 && item
->image
== old
.image
637 && item
->mime_type
== old
.mime_type
)
640 g_object_unref(old
.image
);
644 g_object_unref(old
.image
);
647 g_ptr_array_add(dir
->up_items
, item
);
653 static void update(Directory
*dir
, gchar
*pathname
, gpointer data
)
655 g_free(dir
->pathname
);
656 dir
->pathname
= pathdup(pathname
);
659 dir
->needs_update
= TRUE
;
661 dir_rescan(dir
, pathname
);
664 delayed_error(_("Error scanning '%s':\n%s"),
665 dir
->pathname
, dir
->error
);
668 /* If there is work to do, set the idle callback.
669 * Otherwise, stop scanning and unset the idle callback.
671 static void set_idle_callback(Directory
*dir
)
673 if (dir
->recheck_list
&& dir
->users
)
675 /* Work to do, and someone's watching */
676 dir_set_scanning(dir
, TRUE
);
677 if (dir
->idle_callback
)
679 dir
->idle_callback
= gtk_idle_add(recheck_callback
, dir
);
683 dir_set_scanning(dir
, FALSE
);
684 if (dir
->idle_callback
)
686 gtk_idle_remove(dir
->idle_callback
);
687 dir
->idle_callback
= 0;
692 /* See dir_force_update_path() */
693 static void dir_force_update_item(Directory
*dir
, const gchar
*leaf
)
699 items
= g_ptr_array_new();
701 item
= g_hash_table_lookup(dir
->known_items
, leaf
);
705 g_ptr_array_add(items
, item
);
707 for (list
= dir
->users
; list
; list
= list
->next
)
709 DirUser
*user
= (DirUser
*) list
->data
;
711 user
->callback(dir
, DIR_UPDATE
, items
, user
->data
);
715 g_ptr_array_free(items
, TRUE
);
718 static void dir_recheck(Directory
*dir
,
719 const guchar
*path
, const guchar
*leafname
)
721 guchar
*old
= dir
->pathname
;
723 dir
->pathname
= g_strdup(path
);
726 insert_item(dir
, leafname
);
729 static void to_array(gpointer key
, gpointer value
, gpointer data
)
731 GPtrArray
*array
= (GPtrArray
*) data
;
733 g_ptr_array_add(array
, value
);
736 /* Convert a hash table to an unsorted GPtrArray.
737 * g_ptr_array_free() the result.
739 static GPtrArray
*hash_to_array(GHashTable
*hash
)
743 array
= g_ptr_array_new();
745 g_hash_table_foreach(hash
, to_array
, array
);
750 static gpointer parent_class
;
752 /* Note: dir_cache is never purged, so this shouldn't get called */
753 static void dir_finialize(GObject
*object
)
756 Directory
*dir
= (Directory
*) object
;
758 g_return_if_fail(dir
->users
== NULL
);
760 g_print("[ dir finalize ]\n");
762 free_recheck_list(dir
);
763 set_idle_callback(dir
);
765 dir_merge_new(dir
); /* Ensures new, up and gone are empty */
767 g_ptr_array_free(dir
->up_items
, TRUE
);
768 g_ptr_array_free(dir
->new_items
, TRUE
);
769 g_ptr_array_free(dir
->gone_items
, TRUE
);
771 items
= hash_to_array(dir
->known_items
);
772 free_items_array(items
);
773 g_hash_table_destroy(dir
->known_items
);
776 g_free(dir
->pathname
);
778 G_OBJECT_CLASS(parent_class
)->finalize(object
);
781 static void directory_class_init(gpointer gclass
, gpointer data
)
783 GObjectClass
*object
= (GObjectClass
*) gclass
;
785 parent_class
= g_type_class_peek_parent(gclass
);
787 object
->finalize
= dir_finialize
;
790 static void directory_init(GTypeInstance
*object
, gpointer gclass
)
792 Directory
*dir
= (Directory
*) object
;
794 dir
->known_items
= g_hash_table_new(g_str_hash
, g_str_equal
);
795 dir
->recheck_list
= NULL
;
796 dir
->idle_callback
= 0;
797 dir
->scanning
= FALSE
;
798 dir
->have_scanned
= FALSE
;
801 dir
->needs_update
= TRUE
;
802 dir
->notify_active
= FALSE
;
803 dir
->pathname
= NULL
;
806 dir
->new_items
= g_ptr_array_new();
807 dir
->up_items
= g_ptr_array_new();
808 dir
->gone_items
= g_ptr_array_new();
811 static GType
dir_get_type(void)
813 static GType type
= 0;
817 static const GTypeInfo info
=
819 sizeof (DirectoryClass
),
820 NULL
, /* base_init */
821 NULL
, /* base_finalise */
822 directory_class_init
,
823 NULL
, /* class_finalise */
824 NULL
, /* class_data */
830 type
= g_type_register_static(G_TYPE_OBJECT
, "Directory",
837 static Directory
*dir_new(const char *pathname
)
841 dir
= g_object_new(dir_get_type(), NULL
);
843 dir
->pathname
= g_strdup(pathname
);