r2180: Code tidying (Bernard Jungen).
[rox-filer.git] / ROX-Filer / src / dir.c
blob2e69289bb3913a1bcff4e01cb6c50dd8a398f67a
1 /*
2 * $Id$
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)
9 * any later version.
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
14 * more details.
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 */
23 /* How it works:
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
38 * missing.
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.
51 #include "config.h"
53 #include <gtk/gtk.h>
54 #include <errno.h>
55 #include <stdio.h>
56 #include <string.h>
58 #include "global.h"
60 #include "dir.h"
61 #include "diritem.h"
62 #include "support.h"
63 #include "gui_support.h"
64 #include "dir.h"
65 #include "fscache.h"
66 #include "mount.h"
67 #include "pixmaps.h"
68 #include "type.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 ****************************************************************/
88 void dir_init(void)
90 dir_cache = g_fscache_new((GFSLoadFunc) dir_new,
91 (GFSUpdateFunc) update, NULL);
94 /* Periodically calls callback to notify about changes to the contents
95 * of the directory.
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)
102 DirUser *user;
103 GPtrArray *items;
105 g_return_if_fail(dir != NULL);
106 g_return_if_fail(callback != NULL);
108 user = g_new(DirUser, 1);
109 user->callback = callback;
110 user->data = data;
112 dir->users = g_list_prepend(dir->users, user);
114 g_object_ref(dir);
116 items = hash_to_array(dir->known_items);
117 if (items->len)
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);
127 if (!dir->scanning)
128 callback(dir, DIR_END_SCAN, NULL, data);
130 if (dir->error)
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)
138 DirUser *user;
139 GList *list;
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)
149 g_free(user);
150 dir->users = g_list_remove(dir->users, user);
151 g_object_unref(dir);
153 /* May stop scanning if noone's watching */
154 set_idle_callback(dir);
156 return;
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)
179 guchar *real_path;
180 guchar *dir_path;
181 Directory *dir;
183 dir_path = g_path_get_dirname(path);
184 real_path = pathdup(dir_path);
185 g_free(dir_path);
187 dir = g_fscache_lookup_full(dir_cache, real_path,
188 FSCACHE_LOOKUP_PEEK, NULL);
189 if (dir)
191 dir_recheck(dir, real_path, g_basename(path));
192 g_object_unref(dir);
195 g_free(real_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)
203 gchar *dir_path;
204 Directory *dir;
206 g_return_if_fail(path[0] == '/');
208 dir_path = g_path_get_dirname(path);
210 dir = g_fscache_lookup_full(dir_cache, dir_path, FSCACHE_LOOKUP_PEEK,
211 NULL);
212 if (dir)
214 dir_force_update_item(dir, g_basename(path));
215 g_object_unref(dir);
218 g_free(dir_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)
226 DirItem *item;
228 item = insert_item(dir, leafname);
229 dir_merge_new(dir);
231 return item;
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)
241 destroy_glist(&dir->recheck_list);
244 /* If scanning state has changed then notify all filer windows */
245 static void dir_set_scanning(Directory *dir, gboolean scanning)
247 GList *next;
249 if (scanning == dir->scanning)
250 return;
252 dir->scanning = scanning;
254 for (next = dir->users; next; next = next->next)
256 DirUser *user = (DirUser *) next->data;
258 user->callback(dir,
259 scanning ? DIR_START_SCAN : DIR_END_SCAN,
260 NULL, user->data);
264 /* This is called in the background when there are items on the
265 * dir->recheck_list to process.
267 static gboolean recheck_callback(gpointer data)
269 Directory *dir = (Directory *) data;
270 GList *next;
271 guchar *leaf;
273 g_return_val_if_fail(dir != NULL, FALSE);
274 g_return_val_if_fail(dir->recheck_list != NULL, FALSE);
276 /* Remove the first name from the list */
277 next = dir->recheck_list;
278 dir->recheck_list = g_list_remove_link(dir->recheck_list, next);
279 leaf = (guchar *) next->data;
280 g_list_free_1(next);
282 /* usleep(800); */
284 insert_item(dir, leaf);
286 g_free(leaf);
288 if (dir->recheck_list)
289 return TRUE; /* Call again */
291 /* The recheck_list list empty. Stop scanning, unless
292 * needs_update, in which case we start scanning again.
295 dir_merge_new(dir);
297 dir->have_scanned = TRUE;
298 dir_set_scanning(dir, FALSE);
299 gtk_idle_remove(dir->idle_callback);
300 dir->idle_callback = 0;
302 if (dir->needs_update)
303 dir_rescan(dir, dir->pathname);
305 return FALSE;
308 /* Get the names of all files in the directory.
309 * Remove any DirItems that are no longer listed.
310 * Replace the recheck_list with the items found.
312 void dir_rescan(Directory *dir, const guchar *pathname)
314 GPtrArray *names;
315 DIR *d;
316 struct dirent *ent;
317 int i;
319 g_return_if_fail(dir != NULL);
320 g_return_if_fail(pathname != NULL);
322 dir->needs_update = FALSE;
324 names = g_ptr_array_new();
326 read_globicons();
327 mount_update(FALSE);
328 null_g_free(&dir->error);
330 /* Saves statting the parent for each item... */
331 if (mc_stat(pathname, &dir->stat_info))
333 dir->error = g_strdup_printf(_("Can't stat directory: %s"),
334 g_strerror(errno));
335 return; /* Report on attach */
338 d = mc_opendir(pathname);
339 if (!d)
341 dir->error = g_strdup_printf(_("Can't open directory: %s"),
342 g_strerror(errno));
343 return; /* Report on attach */
346 dir_set_scanning(dir, TRUE);
347 dir_merge_new(dir);
348 gdk_flush();
350 /* Make a list of all the names in the directory */
351 while ((ent = mc_readdir(d)))
353 if (ent->d_name[0] == '.')
355 if (ent->d_name[1] == '\0')
356 continue; /* Ignore '.' */
357 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
358 continue; /* Ignore '..' */
361 g_ptr_array_add(names, g_strdup(ent->d_name));
364 /* Sort, so the names are scanned in a sensible order */
365 qsort(names->pdata, names->len, sizeof(guchar *), sort_names);
367 /* Compare the list with the current DirItems, removing
368 * any that are missing.
370 remove_missing(dir, names);
372 free_recheck_list(dir);
374 /* For each name found, add it to the recheck_list.
375 * If the item is new, put a blank place-holder item in the directory.
377 for (i = 0; i < names->len; i++)
379 guchar *name = names->pdata[i];
380 dir->recheck_list = g_list_prepend(dir->recheck_list, name);
381 if (!g_hash_table_lookup(dir->known_items, name))
383 DirItem *new;
385 new = diritem_new(name);
386 g_ptr_array_add(dir->new_items, new);
389 dir_merge_new(dir);
391 dir->recheck_list = g_list_reverse(dir->recheck_list);
393 g_ptr_array_free(names, TRUE);
394 mc_closedir(d);
396 set_idle_callback(dir);
399 /* Add all the new items to the items array.
400 * Notify everyone who is watching us.
402 void dir_merge_new(Directory *dir)
404 GPtrArray *new = dir->new_items;
405 GPtrArray *up = dir->up_items;
406 GPtrArray *gone = dir->gone_items;
407 GList *list;
408 int i;
410 for (list = dir->users; list; list = list->next)
412 DirUser *user = (DirUser *) list->data;
414 if (new->len)
415 user->callback(dir, DIR_ADD, new, user->data);
416 if (up->len)
417 user->callback(dir, DIR_UPDATE, up, user->data);
418 if (gone->len)
419 user->callback(dir, DIR_REMOVE, gone, user->data);
422 for (i = 0; i < new->len; i++)
424 DirItem *item = (DirItem *) new->pdata[i];
426 g_hash_table_insert(dir->known_items, item->leafname, item);
429 for (i = 0; i < gone->len; i++)
431 DirItem *item = (DirItem *) gone->pdata[i];
433 diritem_free(item);
436 g_ptr_array_set_size(gone, 0);
437 g_ptr_array_set_size(new, 0);
438 g_ptr_array_set_size(up, 0);
441 /****************************************************************
442 * INTERNAL FUNCTIONS *
443 ****************************************************************/
445 static void free_items_array(GPtrArray *array)
447 int i;
449 for (i = 0; i < array->len; i++)
451 DirItem *item = (DirItem *) array->pdata[i];
453 diritem_free(item);
456 g_ptr_array_free(array, TRUE);
459 /* Tell everyone watching that these items have gone */
460 static void notify_deleted(Directory *dir, GPtrArray *deleted)
462 GList *next;
464 if (!deleted->len)
465 return;
467 for (next = dir->users; next; next = next->next)
469 DirUser *user = (DirUser *) next->data;
471 user->callback(dir, DIR_REMOVE, deleted, user->data);
475 static void mark_unused(gpointer key, gpointer value, gpointer data)
477 DirItem *item = (DirItem *) value;
479 item->may_delete = TRUE;
482 static void keep_deleted(gpointer key, gpointer value, gpointer data)
484 DirItem *item = (DirItem *) value;
485 GPtrArray *deleted = (GPtrArray *) data;
487 if (item->may_delete)
488 g_ptr_array_add(deleted, item);
491 static gboolean check_unused(gpointer key, gpointer value, gpointer data)
493 DirItem *item = (DirItem *) value;
495 return item->may_delete;
498 /* Remove all the old items that have gone.
499 * Notify everyone who is watching us of the removed items.
501 static void remove_missing(Directory *dir, GPtrArray *keep)
503 GPtrArray *deleted;
504 int i;
506 deleted = g_ptr_array_new();
508 /* Mark all current items as may_delete */
509 g_hash_table_foreach(dir->known_items, mark_unused, NULL);
511 /* Unmark all items also in 'keep' */
512 for (i = 0; i < keep->len; i++)
514 guchar *leaf = (guchar *) keep->pdata[i];
515 DirItem *item;
517 item = g_hash_table_lookup(dir->known_items, leaf);
519 if (item)
520 item->may_delete = FALSE;
523 /* Add each item still marked to 'deleted' */
524 g_hash_table_foreach(dir->known_items, keep_deleted, deleted);
526 /* Remove all items still marked */
527 g_hash_table_foreach_remove(dir->known_items, check_unused, NULL);
529 notify_deleted(dir, deleted);
531 free_items_array(deleted);
534 static gint notify_timeout(gpointer data)
536 Directory *dir = (Directory *) data;
538 g_return_val_if_fail(dir->notify_active == TRUE, FALSE);
540 dir_merge_new(dir);
542 dir->notify_active = FALSE;
543 g_object_unref(dir);
545 return FALSE;
548 /* Call dir_merge_new() after a while. */
549 static void delayed_notify(Directory *dir)
551 if (dir->notify_active)
552 return;
553 g_object_ref(dir);
554 gtk_timeout_add(1500, notify_timeout, dir);
555 dir->notify_active = TRUE;
558 /* Stat this item and add, update or remove it.
559 * Returns the new/updated item, if any.
560 * (leafname may be from the current DirItem item)
562 static DirItem *insert_item(Directory *dir, const guchar *leafname)
564 const gchar *full_path;
565 DirItem *item;
566 DirItem old;
567 gboolean do_compare = FALSE; /* (old is filled in) */
569 full_path = make_path(dir->pathname, leafname);
570 item = g_hash_table_lookup(dir->known_items, leafname);
572 if (item)
574 if (item->base_type != TYPE_UNKNOWN)
576 /* Preserve the old details so we can compare */
577 old = *item;
578 if (old.image)
579 g_object_ref(old.image);
580 do_compare = TRUE;
582 diritem_restat(full_path, item, &dir->stat_info);
584 else
586 /* Item isn't already here. This won't normally happen,
587 * because blank items are added when scanning, before
588 * we get here.
590 item = diritem_new(leafname);
591 diritem_restat(full_path, item, &dir->stat_info);
592 if (item->base_type == TYPE_ERROR &&
593 item->lstat_errno == ENOENT)
595 diritem_free(item);
596 return NULL;
598 g_ptr_array_add(dir->new_items, item);
602 if (item->base_type == TYPE_ERROR && item->lstat_errno == ENOENT)
604 /* Item has been deleted */
605 g_hash_table_remove(dir->known_items, item->leafname);
606 g_ptr_array_add(dir->gone_items, item);
607 if (do_compare && old.image)
608 g_object_unref(old.image);
609 delayed_notify(dir);
610 return NULL;
613 if (do_compare)
615 if (item->lstat_errno == old.lstat_errno
616 && item->base_type == old.base_type
617 && item->flags == old.flags
618 && item->size == old.size
619 && item->mode == old.mode
620 && item->atime == old.atime
621 && item->ctime == old.ctime
622 && item->mtime == old.mtime
623 && item->uid == old.uid
624 && item->gid == old.gid
625 && item->image == old.image
626 && item->mime_type == old.mime_type)
628 if (old.image)
629 g_object_unref(old.image);
630 return item;
632 if (old.image)
633 g_object_unref(old.image);
636 g_ptr_array_add(dir->up_items, item);
637 delayed_notify(dir);
639 return item;
642 static void update(Directory *dir, gchar *pathname, gpointer data)
644 g_free(dir->pathname);
645 dir->pathname = pathdup(pathname);
647 if (dir->scanning)
648 dir->needs_update = TRUE;
649 else
650 dir_rescan(dir, pathname);
652 if (dir->error)
653 delayed_error(_("Error scanning '%s':\n%s"),
654 dir->pathname, dir->error);
657 /* If there is work to do, set the idle callback.
658 * Otherwise, stop scanning and unset the idle callback.
660 static void set_idle_callback(Directory *dir)
662 if (dir->recheck_list && dir->users)
664 /* Work to do, and someone's watching */
665 dir_set_scanning(dir, TRUE);
666 if (dir->idle_callback)
667 return;
668 dir->idle_callback = gtk_idle_add(recheck_callback, dir);
670 else
672 dir_set_scanning(dir, FALSE);
673 if (dir->idle_callback)
675 gtk_idle_remove(dir->idle_callback);
676 dir->idle_callback = 0;
681 /* See dir_force_update_path() */
682 static void dir_force_update_item(Directory *dir, const gchar *leaf)
684 GList *list;
685 GPtrArray *items;
686 DirItem *item;
688 items = g_ptr_array_new();
690 item = g_hash_table_lookup(dir->known_items, leaf);
691 if (!item)
692 goto out;
694 g_ptr_array_add(items, item);
696 for (list = dir->users; list; list = list->next)
698 DirUser *user = (DirUser *) list->data;
700 user->callback(dir, DIR_UPDATE, items, user->data);
703 out:
704 g_ptr_array_free(items, TRUE);
707 static void dir_recheck(Directory *dir,
708 const guchar *path, const guchar *leafname)
710 guchar *old = dir->pathname;
712 dir->pathname = g_strdup(path);
713 g_free(old);
715 insert_item(dir, leafname);
718 static void to_array(gpointer key, gpointer value, gpointer data)
720 GPtrArray *array = (GPtrArray *) data;
722 g_ptr_array_add(array, value);
725 /* Convert a hash table to an unsorted GPtrArray.
726 * g_ptr_array_free() the result.
728 static GPtrArray *hash_to_array(GHashTable *hash)
730 GPtrArray *array;
732 array = g_ptr_array_new();
734 g_hash_table_foreach(hash, to_array, array);
736 return array;
739 static gpointer parent_class;
741 /* Note: dir_cache is never purged, so this shouldn't get called */
742 static void dir_finialize(GObject *object)
744 GPtrArray *items;
745 Directory *dir = (Directory *) object;
747 g_return_if_fail(dir->users == NULL);
749 g_print("[ dir finalize ]\n");
751 free_recheck_list(dir);
752 set_idle_callback(dir);
754 dir_merge_new(dir); /* Ensures new, up and gone are empty */
756 g_ptr_array_free(dir->up_items, TRUE);
757 g_ptr_array_free(dir->new_items, TRUE);
758 g_ptr_array_free(dir->gone_items, TRUE);
760 items = hash_to_array(dir->known_items);
761 free_items_array(items);
762 g_hash_table_destroy(dir->known_items);
764 g_free(dir->error);
765 g_free(dir->pathname);
767 G_OBJECT_CLASS(parent_class)->finalize(object);
770 static void directory_class_init(gpointer gclass, gpointer data)
772 GObjectClass *object = (GObjectClass *) gclass;
774 parent_class = g_type_class_peek_parent(gclass);
776 object->finalize = dir_finialize;
779 static void directory_init(GTypeInstance *object, gpointer gclass)
781 Directory *dir = (Directory *) object;
783 dir->known_items = g_hash_table_new(g_str_hash, g_str_equal);
784 dir->recheck_list = NULL;
785 dir->idle_callback = 0;
786 dir->scanning = FALSE;
787 dir->have_scanned = FALSE;
789 dir->users = NULL;
790 dir->needs_update = TRUE;
791 dir->notify_active = FALSE;
792 dir->pathname = NULL;
793 dir->error = NULL;
795 dir->new_items = g_ptr_array_new();
796 dir->up_items = g_ptr_array_new();
797 dir->gone_items = g_ptr_array_new();
800 static GType dir_get_type(void)
802 static GType type = 0;
804 if (!type)
806 static const GTypeInfo info =
808 sizeof (DirectoryClass),
809 NULL, /* base_init */
810 NULL, /* base_finalise */
811 directory_class_init,
812 NULL, /* class_finalise */
813 NULL, /* class_data */
814 sizeof(Directory),
815 0, /* n_preallocs */
816 directory_init
819 type = g_type_register_static(G_TYPE_OBJECT, "Directory",
820 &info, 0);
823 return type;
826 static Directory *dir_new(const char *pathname)
828 Directory *dir;
830 dir = g_object_new(dir_get_type(), NULL);
832 dir->pathname = g_strdup(pathname);
834 return dir;