r1601: Turned on more compiler warnings, and fixed some minor issues it threw up.
[rox-filer.git] / ROX-Filer / src / dir.c
blobfe25e79ce95c5b6cfc0707f8f060f86d07b96150
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_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_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 GList *next;
243 for (next = dir->recheck_list; next; next = next->next)
244 g_free(next->data);
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)
254 GList *next;
256 if (scanning == dir->scanning)
257 return;
259 dir->scanning = scanning;
261 for (next = dir->users; next; next = next->next)
263 DirUser *user = (DirUser *) next->data;
265 user->callback(dir,
266 scanning ? DIR_START_SCAN : DIR_END_SCAN,
267 NULL, user->data);
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;
277 GList *next;
278 guchar *leaf;
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;
287 g_list_free_1(next);
289 /* usleep(800); */
291 insert_item(dir, leaf);
293 g_free(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.
302 dir_merge_new(dir);
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);
312 return FALSE;
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)
321 GPtrArray *names;
322 DIR *d;
323 struct dirent *ent;
324 int i;
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();
333 read_globicons();
334 mount_update(FALSE);
335 if (dir->error)
337 g_free(dir->error);
338 dir->error = NULL;
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"),
345 g_strerror(errno));
346 return; /* Report on attach */
349 d = mc_opendir(pathname);
350 if (!d)
352 dir->error = g_strdup_printf(_("Can't open directory: %s"),
353 g_strerror(errno));
354 return; /* Report on attach */
357 dir_set_scanning(dir, TRUE);
358 dir_merge_new(dir);
359 gdk_flush();
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))
394 DirItem *new;
396 new = diritem_new(name);
397 g_ptr_array_add(dir->new_items, new);
400 dir_merge_new(dir);
402 dir->recheck_list = g_list_reverse(dir->recheck_list);
404 g_ptr_array_free(names, TRUE);
405 mc_closedir(d);
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;
418 GList *list;
419 int i;
421 for (list = dir->users; list; list = list->next)
423 DirUser *user = (DirUser *) list->data;
425 if (new->len)
426 user->callback(dir, DIR_ADD, new, user->data);
427 if (up->len)
428 user->callback(dir, DIR_UPDATE, up, user->data);
429 if (gone->len)
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];
444 diritem_free(item);
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)
458 int i;
460 for (i = 0; i < array->len; i++)
462 DirItem *item = (DirItem *) array->pdata[i];
464 diritem_free(item);
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)
473 GList *next;
475 if (!deleted->len)
476 return;
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)
514 GPtrArray *deleted;
515 int i;
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];
526 DirItem *item;
528 item = g_hash_table_lookup(dir->known_items, leaf);
530 if (item)
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);
551 dir_merge_new(dir);
553 dir->notify_active = FALSE;
554 g_object_unref(dir);
556 return FALSE;
559 /* Call dir_merge_new() after a while. */
560 static void delayed_notify(Directory *dir)
562 if (dir->notify_active)
563 return;
564 g_object_ref(dir);
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;
576 DirItem *item;
577 DirItem old;
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);
583 if (item)
585 if (item->base_type != TYPE_UNKNOWN)
587 /* Preserve the old details so we can compare */
588 old = *item;
589 if (old.image)
590 g_object_ref(old.image);
591 do_compare = TRUE;
593 diritem_restat(full_path, item, &dir->stat_info);
595 else
597 /* Item isn't already here. This won't normally happen,
598 * because blank items are added when scanning, before
599 * we get here.
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)
606 diritem_free(item);
607 return NULL;
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);
620 delayed_notify(dir);
621 return NULL;
624 if (do_compare)
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)
639 if (old.image)
640 g_object_unref(old.image);
641 return item;
643 if (old.image)
644 g_object_unref(old.image);
647 g_ptr_array_add(dir->up_items, item);
648 delayed_notify(dir);
650 return item;
653 static void update(Directory *dir, gchar *pathname, gpointer data)
655 g_free(dir->pathname);
656 dir->pathname = pathdup(pathname);
658 if (dir->scanning)
659 dir->needs_update = TRUE;
660 else
661 dir_rescan(dir, pathname);
663 if (dir->error)
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)
678 return;
679 dir->idle_callback = gtk_idle_add(recheck_callback, dir);
681 else
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)
695 GList *list;
696 GPtrArray *items;
697 DirItem *item;
699 items = g_ptr_array_new();
701 item = g_hash_table_lookup(dir->known_items, leaf);
702 if (!item)
703 goto out;
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);
714 out:
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);
724 g_free(old);
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)
741 GPtrArray *array;
743 array = g_ptr_array_new();
745 g_hash_table_foreach(hash, to_array, array);
747 return 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)
755 GPtrArray *items;
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);
775 g_free(dir->error);
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;
800 dir->users = NULL;
801 dir->needs_update = TRUE;
802 dir->notify_active = FALSE;
803 dir->pathname = NULL;
804 dir->error = 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;
815 if (!type)
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 */
825 sizeof(Directory),
826 0, /* n_preallocs */
827 directory_init
830 type = g_type_register_static(G_TYPE_OBJECT, "Directory",
831 &info, 0);
834 return type;
837 static Directory *dir_new(const char *pathname)
839 Directory *dir;
841 dir = g_object_new(dir_get_type(), NULL);
843 dir->pathname = g_strdup(pathname);
845 return dir;