r1322: Converted MaskedPixmap to GObject.
[rox-filer.git] / ROX-Filer / src / dir.c
blob9d11f8797cadb6c5bbf0096e6c5f337376e66979
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 Directory *load(char *pathname, gpointer data);
75 static void ref(Directory *dir, gpointer data);
76 static void unref(Directory *dir, gpointer data);
77 static int getref(Directory *dir, gpointer data);
78 static void update(Directory *dir, gchar *pathname, gpointer data);
79 static void destroy(Directory *dir);
80 static void set_idle_callback(Directory *dir);
81 static DirItem *insert_item(Directory *dir, const guchar *leafname);
82 static void remove_missing(Directory *dir, GPtrArray *keep);
83 static void dir_recheck(Directory *dir,
84 const guchar *path, const guchar *leafname);
85 static GPtrArray *hash_to_array(GHashTable *hash);
86 static void dir_force_update_item(Directory *dir, const gchar *leaf);
88 /****************************************************************
89 * EXTERNAL INTERFACE *
90 ****************************************************************/
92 void dir_init(void)
94 dir_cache = g_fscache_new((GFSLoadFunc) load,
95 (GFSRefFunc) ref,
96 (GFSRefFunc) unref,
97 (GFSGetRefFunc) getref,
98 (GFSUpdateFunc) update, NULL);
101 /* Periodically calls callback to notify about changes to the contents
102 * of the directory.
103 * Before this function returns, it calls the callback once to add all
104 * the items currently in the directory (unless the dir is empty).
105 * If we are not scanning, it also calls update(DIR_END_SCAN).
107 void dir_attach(Directory *dir, DirCallback callback, gpointer data)
109 DirUser *user;
110 GPtrArray *items;
112 g_return_if_fail(dir != NULL);
113 g_return_if_fail(callback != NULL);
115 user = g_new(DirUser, 1);
116 user->callback = callback;
117 user->data = data;
119 dir->users = g_list_prepend(dir->users, user);
121 ref(dir, NULL);
123 items = hash_to_array(dir->known_items);
124 if (items->len)
125 callback(dir, DIR_ADD, items, data);
126 g_ptr_array_free(items, TRUE);
128 if (dir->needs_update && !dir->scanning)
129 dir_rescan(dir, dir->pathname);
131 /* May start scanning if noone was watching before */
132 set_idle_callback(dir);
134 if (!dir->scanning)
135 callback(dir, DIR_END_SCAN, NULL, data);
137 if (dir->error)
138 delayed_error(_("Error scanning '%s':\n%s"),
139 dir->pathname, dir->error);
142 /* Undo the effect of dir_attach */
143 void dir_detach(Directory *dir, DirCallback callback, gpointer data)
145 DirUser *user;
146 GList *list = dir->users;
148 g_return_if_fail(dir != NULL);
149 g_return_if_fail(callback != NULL);
151 while (list)
153 user = (DirUser *) list->data;
154 if (user->callback == callback && user->data == data)
156 g_free(user);
157 dir->users = g_list_remove(dir->users, user);
158 unref(dir, NULL);
160 /* May stop scanning if noone's watching */
161 set_idle_callback(dir);
163 return;
165 list = list->next;
168 g_warning("dir_detach: Callback/data pair not attached!\n");
171 void dir_update(Directory *dir, gchar *pathname)
173 update(dir, pathname, NULL);
176 int dir_item_cmp(const void *a, const void *b)
178 DirItem *aa = *((DirItem **) a);
179 DirItem *bb = *((DirItem **) b);
181 return strcmp(aa->leafname, bb->leafname);
184 /* Rescan this directory */
185 void refresh_dirs(const char *path)
187 g_fscache_update(dir_cache, path);
190 /* When something has happened to a particular object, call this
191 * and all appropriate changes will be made.
193 void dir_check_this(const guchar *path)
195 guchar *real_path;
196 guchar *dir_path;
197 Directory *dir;
199 dir_path = g_dirname(path);
200 real_path = pathdup(dir_path);
201 g_free(dir_path);
203 dir = g_fscache_lookup_full(dir_cache, real_path,
204 FSCACHE_LOOKUP_PEEK, NULL);
205 if (dir)
207 dir_recheck(dir, real_path, g_basename(path));
208 g_fscache_data_unref(dir_cache, dir);
211 g_free(real_path);
214 /* Tell watchers that this item has changed, but don't rescan.
215 * (used when thumbnail has been created for an item)
217 void dir_force_update_path(const gchar *path)
219 gchar *dir_path;
220 Directory *dir;
222 g_return_if_fail(path[0] == '/');
224 dir_path = g_dirname(path);
226 dir = g_fscache_lookup_full(dir_cache, dir_path, FSCACHE_LOOKUP_PEEK,
227 NULL);
228 if (dir)
230 dir_force_update_item(dir, g_basename(path));
231 g_fscache_data_unref(dir_cache, dir);
234 g_free(dir_path);
237 /* Ensure that 'leafname' is up-to-date. Returns the new/updated
238 * DirItem, or NULL if the file no longer exists.
240 DirItem *dir_update_item(Directory *dir, const gchar *leafname)
242 DirItem *item;
244 item = insert_item(dir, leafname);
245 dir_merge_new(dir);
247 return item;
250 static int sort_names(const void *a, const void *b)
252 return strcmp(*((char **) a), *((char **) b));
255 static void free_recheck_list(Directory *dir)
257 GList *next;
259 for (next = dir->recheck_list; next; next = next->next)
260 g_free(next->data);
262 g_list_free(dir->recheck_list);
264 dir->recheck_list = NULL;
267 /* If scanning state has changed then notify all filer windows */
268 void dir_set_scanning(Directory *dir, gboolean scanning)
270 GList *next;
272 if (scanning == dir->scanning)
273 return;
275 dir->scanning = scanning;
277 for (next = dir->users; next; next = next->next)
279 DirUser *user = (DirUser *) next->data;
281 user->callback(dir,
282 scanning ? DIR_START_SCAN : DIR_END_SCAN,
283 NULL, user->data);
287 /* This is called in the background when there are items on the
288 * dir->recheck_list to process.
290 static gboolean recheck_callback(gpointer data)
292 Directory *dir = (Directory *) data;
293 GList *next;
294 guchar *leaf;
296 g_return_val_if_fail(dir != NULL, FALSE);
297 g_return_val_if_fail(dir->recheck_list != NULL, FALSE);
299 /* Remove the first name from the list */
300 next = dir->recheck_list;
301 dir->recheck_list = g_list_remove_link(dir->recheck_list, next);
302 leaf = (guchar *) next->data;
303 g_list_free_1(next);
305 /* usleep(800); */
307 insert_item(dir, leaf);
309 g_free(leaf);
311 if (dir->recheck_list)
312 return TRUE; /* Call again */
314 /* The recheck_list list empty. Stop scanning, unless
315 * needs_update, in which case we start scanning again.
318 dir_merge_new(dir);
320 dir->have_scanned = TRUE;
321 dir_set_scanning(dir, FALSE);
322 gtk_idle_remove(dir->idle_callback);
323 dir->idle_callback = 0;
325 if (dir->needs_update)
326 dir_rescan(dir, dir->pathname);
328 return FALSE;
331 /* Get the names of all files in the directory.
332 * Remove any DirItems that are no longer listed.
333 * Replace the recheck_list with the items found.
335 void dir_rescan(Directory *dir, const guchar *pathname)
337 GPtrArray *names;
338 DIR *d;
339 struct dirent *ent;
340 int i;
342 g_return_if_fail(dir != NULL);
343 g_return_if_fail(pathname != NULL);
345 dir->needs_update = FALSE;
347 names = g_ptr_array_new();
349 read_globicons();
350 mount_update(FALSE);
351 if (dir->error)
353 g_free(dir->error);
354 dir->error = NULL;
357 d = mc_opendir(pathname);
358 if (!d)
360 dir->error = g_strdup_printf(_("Can't open directory: %s"),
361 g_strerror(errno));
362 return; /* Report on attach */
365 dir_set_scanning(dir, TRUE);
366 dir_merge_new(dir);
367 gdk_flush();
369 /* Make a list of all the names in the directory */
370 while ((ent = mc_readdir(d)))
372 if (ent->d_name[0] == '.')
374 if (ent->d_name[1] == '\0')
375 continue; /* Ignore '.' */
376 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
377 continue; /* Ignore '..' */
380 g_ptr_array_add(names, g_strdup(ent->d_name));
383 /* Sort, so the names are scanned in a sensible order */
384 qsort(names->pdata, names->len, sizeof(guchar *), sort_names);
386 /* Compare the list with the current DirItems, removing
387 * any that are missing.
389 remove_missing(dir, names);
391 free_recheck_list(dir);
393 /* For each name found, add it to the recheck_list.
394 * If the item is new, put a blank place-holder item in the directory.
396 for (i = 0; i < names->len; i++)
398 guchar *name = names->pdata[i];
399 dir->recheck_list = g_list_prepend(dir->recheck_list, name);
400 if (!g_hash_table_lookup(dir->known_items, name))
402 DirItem *new;
404 new = diritem_new(name);
405 g_ptr_array_add(dir->new_items, new);
408 dir_merge_new(dir);
410 dir->recheck_list = g_list_reverse(dir->recheck_list);
412 g_ptr_array_free(names, TRUE);
413 mc_closedir(d);
415 set_idle_callback(dir);
418 /* Add all the new items to the items array.
419 * Notify everyone who is watching us.
421 void dir_merge_new(Directory *dir)
423 GList *list = dir->users;
424 GPtrArray *new = dir->new_items;
425 GPtrArray *up = dir->up_items;
426 GPtrArray *gone = dir->gone_items;
427 int i;
429 while (list)
431 DirUser *user = (DirUser *) list->data;
433 if (new->len)
434 user->callback(dir, DIR_ADD, new, user->data);
435 if (up->len)
436 user->callback(dir, DIR_UPDATE, up, user->data);
437 if (gone->len)
438 user->callback(dir, DIR_REMOVE, gone, user->data);
440 list = list->next;
443 for (i = 0; i < new->len; i++)
445 DirItem *item = (DirItem *) new->pdata[i];
447 g_hash_table_insert(dir->known_items, item->leafname, item);
450 for (i = 0; i < gone->len; i++)
452 DirItem *item = (DirItem *) gone->pdata[i];
454 diritem_free(item);
457 g_ptr_array_set_size(gone, 0);
458 g_ptr_array_set_size(new, 0);
459 g_ptr_array_set_size(up, 0);
462 /****************************************************************
463 * INTERNAL FUNCTIONS *
464 ****************************************************************/
466 static void free_items_array(GPtrArray *array)
468 int i;
470 for (i = 0; i < array->len; i++)
472 DirItem *item = (DirItem *) array->pdata[i];
474 diritem_free(item);
477 g_ptr_array_free(array, TRUE);
480 /* Tell everyone watching that these items have gone */
481 static void notify_deleted(Directory *dir, GPtrArray *deleted)
483 GList *next;
485 if (!deleted->len)
486 return;
488 for (next = dir->users; next; next = next->next)
490 DirUser *user = (DirUser *) next->data;
492 user->callback(dir, DIR_REMOVE, deleted, user->data);
496 static void mark_unused(gpointer key, gpointer value, gpointer data)
498 DirItem *item = (DirItem *) value;
500 item->may_delete = TRUE;
503 static void keep_deleted(gpointer key, gpointer value, gpointer data)
505 DirItem *item = (DirItem *) value;
506 GPtrArray *deleted = (GPtrArray *) data;
508 if (item->may_delete)
509 g_ptr_array_add(deleted, item);
512 static gboolean check_unused(gpointer key, gpointer value, gpointer data)
514 DirItem *item = (DirItem *) value;
516 return item->may_delete;
519 /* Remove all the old items that have gone.
520 * Notify everyone who is watching us of the removed items.
522 static void remove_missing(Directory *dir, GPtrArray *keep)
524 GPtrArray *deleted;
525 int i;
527 deleted = g_ptr_array_new();
529 /* Mark all current items as may_delete */
530 g_hash_table_foreach(dir->known_items, mark_unused, NULL);
532 /* Unmark all items also in 'keep' */
533 for (i = 0; i < keep->len; i++)
535 guchar *leaf = (guchar *) keep->pdata[i];
536 DirItem *item;
538 item = g_hash_table_lookup(dir->known_items, leaf);
540 if (item)
541 item->may_delete = FALSE;
544 /* Add each item still marked to 'deleted' */
545 g_hash_table_foreach(dir->known_items, keep_deleted, deleted);
547 /* Remove all items still marked */
548 g_hash_table_foreach_remove(dir->known_items, check_unused, NULL);
550 notify_deleted(dir, deleted);
552 free_items_array(deleted);
555 static gint notify_timeout(gpointer data)
557 Directory *dir = (Directory *) data;
559 g_return_val_if_fail(dir->notify_active == TRUE, FALSE);
561 dir_merge_new(dir);
563 dir->notify_active = FALSE;
564 unref(dir, NULL);
566 return FALSE;
569 /* Call dir_merge_new() after a while. */
570 static void delayed_notify(Directory *dir)
572 if (dir->notify_active)
573 return;
574 ref(dir, NULL);
575 gtk_timeout_add(500, notify_timeout, dir);
576 dir->notify_active = TRUE;
579 /* Stat this item and add, update or remove it.
580 * Returns the new/updated item, if any.
581 * (leafname may be from the current DirItem item)
583 static DirItem *insert_item(Directory *dir, const guchar *leafname)
585 static GString *tmp = NULL;
587 DirItem *item;
588 DirItem old;
589 gboolean do_compare = FALSE; /* (old is filled in) */
591 tmp = make_path(dir->pathname, leafname);
592 item = g_hash_table_lookup(dir->known_items, leafname);
594 if (item)
596 if (item->base_type != TYPE_UNKNOWN)
598 /* Preserve the old details so we can compare */
599 memcpy(&old, item, sizeof(DirItem));
600 if (old.image)
601 g_object_ref(old.image);
602 do_compare = TRUE;
604 diritem_restat(tmp->str, item);
606 else
608 /* Item isn't already here. This won't normally happen,
609 * because blank items are added when scanning, before
610 * we get here.
612 item = diritem_new(leafname);
613 diritem_restat(tmp->str, item);
614 if (item->base_type == TYPE_ERROR &&
615 item->lstat_errno == ENOENT)
617 diritem_free(item);
618 return NULL;
620 g_ptr_array_add(dir->new_items, item);
624 if (item->base_type == TYPE_ERROR && item->lstat_errno == ENOENT)
626 /* Item has been deleted */
627 g_hash_table_remove(dir->known_items, item->leafname);
628 g_ptr_array_add(dir->gone_items, item);
629 if (do_compare && old.image)
630 g_object_unref(old.image);
631 delayed_notify(dir);
632 return NULL;
635 if (do_compare)
637 if (item->lstat_errno == old.lstat_errno
638 && item->base_type == old.base_type
639 && item->flags == old.flags
640 && item->size == old.size
641 && item->mode == old.mode
642 && item->atime == old.atime
643 && item->ctime == old.ctime
644 && item->mtime == old.mtime
645 && item->uid == old.uid
646 && item->gid == old.gid
647 && item->image == old.image
648 && item->mime_type == old.mime_type)
650 if (old.image)
651 g_object_unref(old.image);
652 return item;
654 if (old.image)
655 g_object_unref(old.image);
658 g_ptr_array_add(dir->up_items, item);
659 delayed_notify(dir);
661 return item;
664 static Directory *load(char *pathname, gpointer data)
666 Directory *dir;
668 dir = g_new(Directory, 1);
669 dir->ref = 1;
670 dir->known_items = g_hash_table_new(g_str_hash, g_str_equal);
671 dir->recheck_list = NULL;
672 dir->idle_callback = 0;
673 dir->scanning = FALSE;
674 dir->have_scanned = FALSE;
676 dir->users = NULL;
677 dir->needs_update = TRUE;
678 dir->notify_active = FALSE;
679 dir->pathname = g_strdup(pathname);
680 dir->error = NULL;
682 dir->new_items = g_ptr_array_new();
683 dir->up_items = g_ptr_array_new();
684 dir->gone_items = g_ptr_array_new();
686 return dir;
689 /* Note: dir_cache is never purged, so this shouldn't get called */
690 static void destroy(Directory *dir)
692 GPtrArray *items;
694 g_return_if_fail(dir->users == NULL);
696 g_print("[ destroy %p ]\n", dir);
698 free_recheck_list(dir);
699 set_idle_callback(dir);
701 dir_merge_new(dir); /* Ensures new, up and gone are empty */
703 g_ptr_array_free(dir->up_items, TRUE);
704 g_ptr_array_free(dir->new_items, TRUE);
705 g_ptr_array_free(dir->gone_items, TRUE);
707 items = hash_to_array(dir->known_items);
708 free_items_array(items);
709 g_hash_table_destroy(dir->known_items);
711 g_free(dir->error);
712 g_free(dir->pathname);
713 g_free(dir);
716 static void ref(Directory *dir, gpointer data)
718 dir->ref++;
721 static void unref(Directory *dir, gpointer data)
723 if (--dir->ref == 0)
724 destroy(dir);
727 static int getref(Directory *dir, gpointer data)
729 return dir->ref;
732 static void update(Directory *dir, gchar *pathname, gpointer data)
734 g_free(dir->pathname);
735 dir->pathname = pathdup(pathname);
737 if (dir->scanning)
738 dir->needs_update = TRUE;
739 else
740 dir_rescan(dir, pathname);
742 if (dir->error)
743 delayed_error(_("Error scanning '%s':\n%s"),
744 dir->pathname, dir->error);
747 /* If there is work to do, set the idle callback.
748 * Otherwise, stop scanning and unset the idle callback.
750 static void set_idle_callback(Directory *dir)
752 if (dir->recheck_list && dir->users)
754 /* Work to do, and someone's watching */
755 dir_set_scanning(dir, TRUE);
756 if (dir->idle_callback)
757 return;
758 dir->idle_callback = gtk_idle_add(recheck_callback, dir);
760 else
762 dir_set_scanning(dir, FALSE);
763 if (dir->idle_callback)
765 gtk_idle_remove(dir->idle_callback);
766 dir->idle_callback = 0;
771 /* See dir_force_update_path() */
772 static void dir_force_update_item(Directory *dir, const gchar *leaf)
774 GList *list = dir->users;
775 GPtrArray *items;
776 DirItem *item;
778 items = g_ptr_array_new();
780 item = g_hash_table_lookup(dir->known_items, leaf);
781 if (!item)
782 goto out;
784 g_ptr_array_add(items, item);
786 while (list)
788 DirUser *user = (DirUser *) list->data;
790 user->callback(dir, DIR_UPDATE, items, user->data);
792 list = list->next;
795 out:
796 g_ptr_array_free(items, TRUE);
799 static void dir_recheck(Directory *dir,
800 const guchar *path, const guchar *leafname)
802 guchar *old = dir->pathname;
804 dir->pathname = g_strdup(path);
805 g_free(old);
807 insert_item(dir, leafname);
810 static void to_array(gpointer key, gpointer value, gpointer data)
812 GPtrArray *array = (GPtrArray *) data;
814 g_ptr_array_add(array, value);
817 /* Convert a hash table to an unsorted GPtrArray.
818 * g_ptr_array_free() the result.
820 static GPtrArray *hash_to_array(GHashTable *hash)
822 GPtrArray *array;
824 array = g_ptr_array_new();
826 g_hash_table_foreach(hash, to_array, array);
828 return array;