r1447: Middle button clicks on the pinboard are passed to the window manager.
[rox-filer.git] / ROX-Filer / src / dir.c
blobe025b4cd7f88d16b91809e836a896b69f406b79a
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 = dir->users;
141 g_return_if_fail(dir != NULL);
142 g_return_if_fail(callback != NULL);
144 while (list)
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;
158 list = list->next;
161 g_warning("dir_detach: Callback/data pair not attached!\n");
164 void dir_update(Directory *dir, gchar *pathname)
166 update(dir, pathname, NULL);
169 /* Rescan this directory */
170 void refresh_dirs(const char *path)
172 g_fscache_update(dir_cache, path);
175 /* When something has happened to a particular object, call this
176 * and all appropriate changes will be made.
178 void dir_check_this(const guchar *path)
180 guchar *real_path;
181 guchar *dir_path;
182 Directory *dir;
184 dir_path = g_dirname(path);
185 real_path = pathdup(dir_path);
186 g_free(dir_path);
188 dir = g_fscache_lookup_full(dir_cache, real_path,
189 FSCACHE_LOOKUP_PEEK, NULL);
190 if (dir)
192 dir_recheck(dir, real_path, g_basename(path));
193 g_object_unref(dir);
196 g_free(real_path);
199 /* Tell watchers that this item has changed, but don't rescan.
200 * (used when thumbnail has been created for an item)
202 void dir_force_update_path(const gchar *path)
204 gchar *dir_path;
205 Directory *dir;
207 g_return_if_fail(path[0] == '/');
209 dir_path = g_dirname(path);
211 dir = g_fscache_lookup_full(dir_cache, dir_path, FSCACHE_LOOKUP_PEEK,
212 NULL);
213 if (dir)
215 dir_force_update_item(dir, g_basename(path));
216 g_object_unref(dir);
219 g_free(dir_path);
222 /* Ensure that 'leafname' is up-to-date. Returns the new/updated
223 * DirItem, or NULL if the file no longer exists.
225 DirItem *dir_update_item(Directory *dir, const gchar *leafname)
227 DirItem *item;
229 item = insert_item(dir, leafname);
230 dir_merge_new(dir);
232 return item;
235 static int sort_names(const void *a, const void *b)
237 return strcmp(*((char **) a), *((char **) b));
240 static void free_recheck_list(Directory *dir)
242 GList *next;
244 for (next = dir->recheck_list; next; next = next->next)
245 g_free(next->data);
247 g_list_free(dir->recheck_list);
249 dir->recheck_list = NULL;
252 /* If scanning state has changed then notify all filer windows */
253 void dir_set_scanning(Directory *dir, gboolean scanning)
255 GList *next;
257 if (scanning == dir->scanning)
258 return;
260 dir->scanning = scanning;
262 for (next = dir->users; next; next = next->next)
264 DirUser *user = (DirUser *) next->data;
266 user->callback(dir,
267 scanning ? DIR_START_SCAN : DIR_END_SCAN,
268 NULL, user->data);
272 /* This is called in the background when there are items on the
273 * dir->recheck_list to process.
275 static gboolean recheck_callback(gpointer data)
277 Directory *dir = (Directory *) data;
278 GList *next;
279 guchar *leaf;
281 g_return_val_if_fail(dir != NULL, FALSE);
282 g_return_val_if_fail(dir->recheck_list != NULL, FALSE);
284 /* Remove the first name from the list */
285 next = dir->recheck_list;
286 dir->recheck_list = g_list_remove_link(dir->recheck_list, next);
287 leaf = (guchar *) next->data;
288 g_list_free_1(next);
290 /* usleep(800); */
292 insert_item(dir, leaf);
294 g_free(leaf);
296 if (dir->recheck_list)
297 return TRUE; /* Call again */
299 /* The recheck_list list empty. Stop scanning, unless
300 * needs_update, in which case we start scanning again.
303 dir_merge_new(dir);
305 dir->have_scanned = TRUE;
306 dir_set_scanning(dir, FALSE);
307 gtk_idle_remove(dir->idle_callback);
308 dir->idle_callback = 0;
310 if (dir->needs_update)
311 dir_rescan(dir, dir->pathname);
313 return FALSE;
316 /* Get the names of all files in the directory.
317 * Remove any DirItems that are no longer listed.
318 * Replace the recheck_list with the items found.
320 void dir_rescan(Directory *dir, const guchar *pathname)
322 GPtrArray *names;
323 DIR *d;
324 struct dirent *ent;
325 int i;
327 g_return_if_fail(dir != NULL);
328 g_return_if_fail(pathname != NULL);
330 dir->needs_update = FALSE;
332 names = g_ptr_array_new();
334 read_globicons();
335 mount_update(FALSE);
336 if (dir->error)
338 g_free(dir->error);
339 dir->error = NULL;
342 /* Saves statting the parent for each item... */
343 if (mc_stat(pathname, &dir->stat_info))
345 dir->error = g_strdup_printf(_("Can't stat directory: %s"),
346 g_strerror(errno));
347 return; /* Report on attach */
350 d = mc_opendir(pathname);
351 if (!d)
353 dir->error = g_strdup_printf(_("Can't open directory: %s"),
354 g_strerror(errno));
355 return; /* Report on attach */
358 dir_set_scanning(dir, TRUE);
359 dir_merge_new(dir);
360 gdk_flush();
362 /* Make a list of all the names in the directory */
363 while ((ent = mc_readdir(d)))
365 if (ent->d_name[0] == '.')
367 if (ent->d_name[1] == '\0')
368 continue; /* Ignore '.' */
369 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
370 continue; /* Ignore '..' */
373 g_ptr_array_add(names, g_strdup(ent->d_name));
376 /* Sort, so the names are scanned in a sensible order */
377 qsort(names->pdata, names->len, sizeof(guchar *), sort_names);
379 /* Compare the list with the current DirItems, removing
380 * any that are missing.
382 remove_missing(dir, names);
384 free_recheck_list(dir);
386 /* For each name found, add it to the recheck_list.
387 * If the item is new, put a blank place-holder item in the directory.
389 for (i = 0; i < names->len; i++)
391 guchar *name = names->pdata[i];
392 dir->recheck_list = g_list_prepend(dir->recheck_list, name);
393 if (!g_hash_table_lookup(dir->known_items, name))
395 DirItem *new;
397 new = diritem_new(name);
398 g_ptr_array_add(dir->new_items, new);
401 dir_merge_new(dir);
403 dir->recheck_list = g_list_reverse(dir->recheck_list);
405 g_ptr_array_free(names, TRUE);
406 mc_closedir(d);
408 set_idle_callback(dir);
411 /* Add all the new items to the items array.
412 * Notify everyone who is watching us.
414 void dir_merge_new(Directory *dir)
416 GList *list = dir->users;
417 GPtrArray *new = dir->new_items;
418 GPtrArray *up = dir->up_items;
419 GPtrArray *gone = dir->gone_items;
420 int i;
422 while (list)
424 DirUser *user = (DirUser *) list->data;
426 if (new->len)
427 user->callback(dir, DIR_ADD, new, user->data);
428 if (up->len)
429 user->callback(dir, DIR_UPDATE, up, user->data);
430 if (gone->len)
431 user->callback(dir, DIR_REMOVE, gone, user->data);
433 list = list->next;
436 for (i = 0; i < new->len; i++)
438 DirItem *item = (DirItem *) new->pdata[i];
440 g_hash_table_insert(dir->known_items, item->leafname, item);
443 for (i = 0; i < gone->len; i++)
445 DirItem *item = (DirItem *) gone->pdata[i];
447 diritem_free(item);
450 g_ptr_array_set_size(gone, 0);
451 g_ptr_array_set_size(new, 0);
452 g_ptr_array_set_size(up, 0);
455 /****************************************************************
456 * INTERNAL FUNCTIONS *
457 ****************************************************************/
459 static void free_items_array(GPtrArray *array)
461 int i;
463 for (i = 0; i < array->len; i++)
465 DirItem *item = (DirItem *) array->pdata[i];
467 diritem_free(item);
470 g_ptr_array_free(array, TRUE);
473 /* Tell everyone watching that these items have gone */
474 static void notify_deleted(Directory *dir, GPtrArray *deleted)
476 GList *next;
478 if (!deleted->len)
479 return;
481 for (next = dir->users; next; next = next->next)
483 DirUser *user = (DirUser *) next->data;
485 user->callback(dir, DIR_REMOVE, deleted, user->data);
489 static void mark_unused(gpointer key, gpointer value, gpointer data)
491 DirItem *item = (DirItem *) value;
493 item->may_delete = TRUE;
496 static void keep_deleted(gpointer key, gpointer value, gpointer data)
498 DirItem *item = (DirItem *) value;
499 GPtrArray *deleted = (GPtrArray *) data;
501 if (item->may_delete)
502 g_ptr_array_add(deleted, item);
505 static gboolean check_unused(gpointer key, gpointer value, gpointer data)
507 DirItem *item = (DirItem *) value;
509 return item->may_delete;
512 /* Remove all the old items that have gone.
513 * Notify everyone who is watching us of the removed items.
515 static void remove_missing(Directory *dir, GPtrArray *keep)
517 GPtrArray *deleted;
518 int i;
520 deleted = g_ptr_array_new();
522 /* Mark all current items as may_delete */
523 g_hash_table_foreach(dir->known_items, mark_unused, NULL);
525 /* Unmark all items also in 'keep' */
526 for (i = 0; i < keep->len; i++)
528 guchar *leaf = (guchar *) keep->pdata[i];
529 DirItem *item;
531 item = g_hash_table_lookup(dir->known_items, leaf);
533 if (item)
534 item->may_delete = FALSE;
537 /* Add each item still marked to 'deleted' */
538 g_hash_table_foreach(dir->known_items, keep_deleted, deleted);
540 /* Remove all items still marked */
541 g_hash_table_foreach_remove(dir->known_items, check_unused, NULL);
543 notify_deleted(dir, deleted);
545 free_items_array(deleted);
548 static gint notify_timeout(gpointer data)
550 Directory *dir = (Directory *) data;
552 g_return_val_if_fail(dir->notify_active == TRUE, FALSE);
554 dir_merge_new(dir);
556 dir->notify_active = FALSE;
557 g_object_unref(dir);
559 return FALSE;
562 /* Call dir_merge_new() after a while. */
563 static void delayed_notify(Directory *dir)
565 if (dir->notify_active)
566 return;
567 g_object_ref(dir);
568 gtk_timeout_add(500, notify_timeout, dir);
569 dir->notify_active = TRUE;
572 /* Stat this item and add, update or remove it.
573 * Returns the new/updated item, if any.
574 * (leafname may be from the current DirItem item)
576 static DirItem *insert_item(Directory *dir, const guchar *leafname)
578 static GString *tmp = NULL;
580 DirItem *item;
581 DirItem old;
582 gboolean do_compare = FALSE; /* (old is filled in) */
584 tmp = make_path(dir->pathname, leafname);
585 item = g_hash_table_lookup(dir->known_items, leafname);
587 if (item)
589 if (item->base_type != TYPE_UNKNOWN)
591 /* Preserve the old details so we can compare */
592 memcpy(&old, item, sizeof(DirItem));
593 if (old.image)
594 g_object_ref(old.image);
595 do_compare = TRUE;
597 diritem_restat(tmp->str, item, &dir->stat_info);
599 else
601 /* Item isn't already here. This won't normally happen,
602 * because blank items are added when scanning, before
603 * we get here.
605 item = diritem_new(leafname);
606 diritem_restat(tmp->str, item, &dir->stat_info);
607 if (item->base_type == TYPE_ERROR &&
608 item->lstat_errno == ENOENT)
610 diritem_free(item);
611 return NULL;
613 g_ptr_array_add(dir->new_items, item);
617 if (item->base_type == TYPE_ERROR && item->lstat_errno == ENOENT)
619 /* Item has been deleted */
620 g_hash_table_remove(dir->known_items, item->leafname);
621 g_ptr_array_add(dir->gone_items, item);
622 if (do_compare && old.image)
623 g_object_unref(old.image);
624 delayed_notify(dir);
625 return NULL;
628 if (do_compare)
630 if (item->lstat_errno == old.lstat_errno
631 && item->base_type == old.base_type
632 && item->flags == old.flags
633 && item->size == old.size
634 && item->mode == old.mode
635 && item->atime == old.atime
636 && item->ctime == old.ctime
637 && item->mtime == old.mtime
638 && item->uid == old.uid
639 && item->gid == old.gid
640 && item->image == old.image
641 && item->mime_type == old.mime_type)
643 if (old.image)
644 g_object_unref(old.image);
645 return item;
647 if (old.image)
648 g_object_unref(old.image);
651 g_ptr_array_add(dir->up_items, item);
652 delayed_notify(dir);
654 return item;
657 static void update(Directory *dir, gchar *pathname, gpointer data)
659 g_free(dir->pathname);
660 dir->pathname = pathdup(pathname);
662 if (dir->scanning)
663 dir->needs_update = TRUE;
664 else
665 dir_rescan(dir, pathname);
667 if (dir->error)
668 delayed_error(_("Error scanning '%s':\n%s"),
669 dir->pathname, dir->error);
672 /* If there is work to do, set the idle callback.
673 * Otherwise, stop scanning and unset the idle callback.
675 static void set_idle_callback(Directory *dir)
677 if (dir->recheck_list && dir->users)
679 /* Work to do, and someone's watching */
680 dir_set_scanning(dir, TRUE);
681 if (dir->idle_callback)
682 return;
683 dir->idle_callback = gtk_idle_add(recheck_callback, dir);
685 else
687 dir_set_scanning(dir, FALSE);
688 if (dir->idle_callback)
690 gtk_idle_remove(dir->idle_callback);
691 dir->idle_callback = 0;
696 /* See dir_force_update_path() */
697 static void dir_force_update_item(Directory *dir, const gchar *leaf)
699 GList *list = dir->users;
700 GPtrArray *items;
701 DirItem *item;
703 items = g_ptr_array_new();
705 item = g_hash_table_lookup(dir->known_items, leaf);
706 if (!item)
707 goto out;
709 g_ptr_array_add(items, item);
711 while (list)
713 DirUser *user = (DirUser *) list->data;
715 user->callback(dir, DIR_UPDATE, items, user->data);
717 list = list->next;
720 out:
721 g_ptr_array_free(items, TRUE);
724 static void dir_recheck(Directory *dir,
725 const guchar *path, const guchar *leafname)
727 guchar *old = dir->pathname;
729 dir->pathname = g_strdup(path);
730 g_free(old);
732 insert_item(dir, leafname);
735 static void to_array(gpointer key, gpointer value, gpointer data)
737 GPtrArray *array = (GPtrArray *) data;
739 g_ptr_array_add(array, value);
742 /* Convert a hash table to an unsorted GPtrArray.
743 * g_ptr_array_free() the result.
745 static GPtrArray *hash_to_array(GHashTable *hash)
747 GPtrArray *array;
749 array = g_ptr_array_new();
751 g_hash_table_foreach(hash, to_array, array);
753 return array;
756 static gpointer parent_class;
758 /* Note: dir_cache is never purged, so this shouldn't get called */
759 static void dir_finialize(GObject *object)
761 GPtrArray *items;
762 Directory *dir = (Directory *) object;
764 g_return_if_fail(dir->users == NULL);
766 g_print("[ dir finalize ]\n");
768 free_recheck_list(dir);
769 set_idle_callback(dir);
771 dir_merge_new(dir); /* Ensures new, up and gone are empty */
773 g_ptr_array_free(dir->up_items, TRUE);
774 g_ptr_array_free(dir->new_items, TRUE);
775 g_ptr_array_free(dir->gone_items, TRUE);
777 items = hash_to_array(dir->known_items);
778 free_items_array(items);
779 g_hash_table_destroy(dir->known_items);
781 g_free(dir->error);
782 g_free(dir->pathname);
784 G_OBJECT_CLASS(parent_class)->finalize(object);
787 static void directory_class_init(gpointer gclass, gpointer data)
789 GObjectClass *object = (GObjectClass *) gclass;
791 parent_class = g_type_class_peek_parent(gclass);
793 object->finalize = dir_finialize;
796 static void directory_init(GTypeInstance *object, gpointer gclass)
798 Directory *dir = (Directory *) object;
800 dir->known_items = g_hash_table_new(g_str_hash, g_str_equal);
801 dir->recheck_list = NULL;
802 dir->idle_callback = 0;
803 dir->scanning = FALSE;
804 dir->have_scanned = FALSE;
806 dir->users = NULL;
807 dir->needs_update = TRUE;
808 dir->notify_active = FALSE;
809 dir->pathname = NULL;
810 dir->error = NULL;
812 dir->new_items = g_ptr_array_new();
813 dir->up_items = g_ptr_array_new();
814 dir->gone_items = g_ptr_array_new();
817 static GType dir_get_type(void)
819 static GType type = 0;
821 if (!type)
823 static const GTypeInfo info =
825 sizeof (DirectoryClass),
826 NULL, /* base_init */
827 NULL, /* base_finalise */
828 directory_class_init,
829 NULL, /* class_finalise */
830 NULL, /* class_data */
831 sizeof(Directory),
832 0, /* n_preallocs */
833 directory_init
836 type = g_type_register_static(G_TYPE_OBJECT, "Directory",
837 &info, 0);
840 return type;
843 static Directory *dir_new(const char *pathname)
845 Directory *dir;
847 dir = g_object_new(dir_get_type(), NULL);
849 dir->pathname = g_strdup(pathname);
851 return dir;