1 /*
2  * ROX-Filer, filer for the ROX desktop project
3  * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the Free
6  * Software Foundation; either version 2 of the License, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16  * Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18 
19 /* dir.c - directory scanning and caching */
20 
21 /* How it works:
22  *
23  * A Directory contains a list DirItems, each having a name and some details
24  * (size, image, owner, etc).
25  *
26  * There is a list of file names that need to be rechecked. While this
27  * list is non-empty, items are taken from the list in an idle callback
28  * and checked. Missing items are removed from the Directory, new items are
29  * added and existing items are updated if they've changed.
30  *
31  * When a whole directory is to be rescanned:
32  *
33  * - A list of all filenames in the directory is fetched, without any
34  *   of the extra details.
35  * - This list is compared to the current DirItems, removing any that are now
36  *   missing.
37  * - Each window onto the directory is asked which items it will actually
38  *   display, and the union of these sets is the new recheck list.
39  *
40  * This system is designed to get the number of items and their names quickly,
41  * so that the auto-sizer can make a good guess. It also prevents checking
42  * hidden files if they're not going to be displayed.
43  *
44  * To get the Directory object, use dir_cache, which will automatically
45  * trigger a rescan if needed.
46  *
47  * To get notified when the Directory changes, use the dir_attach() and
48  * dir_detach() functions.
49  */
50 
51 #include "config.h"
52 
53 #include <gtk/gtk.h>
54 #include <errno.h>
55 #include <stdio.h>
56 #include <string.h>
57 
58 #include "global.h"
59 
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"
70 #include "main.h"
71 
72 #ifdef USE_NOTIFY
73 static GHashTable *notify_fd_to_dir = NULL;
74 #endif
75 #ifdef USE_INOTIFY
76 # include <sys/inotify.h>
77 GIOChannel *inotify_channel;
78 static int inotify_fd;
79 #endif
80 #ifdef USE_DNOTIFY
81 /* Newer Linux kernels can tell us when the directories we are watching
82  * change, using the dnotify system.
83  */
84 gboolean dnotify_wakeup_flag = FALSE;
85 static int dnotify_last_fd = -1;
86 #endif
87 
88 /* For debugging. Can't detach when this is non-zero. */
89 static int in_callback = 0;
90 
91 GFSCache *dir_cache = NULL;
92 
93 /* Static prototypes */
94 static void update(Directory *dir, gchar *pathname, gpointer data);
95 static void set_idle_callback(Directory *dir);
96 static DirItem *insert_item(Directory *dir, const guchar *leafname);
97 static void remove_missing(Directory *dir, GPtrArray *keep);
98 static void dir_recheck(Directory *dir,
99 			const guchar *path, const guchar *leafname);
100 static GPtrArray *hash_to_array(GHashTable *hash);
101 static void dir_force_update_item(Directory *dir, const gchar *leaf);
102 static Directory *dir_new(const char *pathname);
103 static void dir_rescan(Directory *dir);
104 #ifdef USE_NOTIFY
105 static void dir_rescan_soon(Directory *dir);
106 # ifdef USE_INOTIFY
107 static gboolean inotify_handler(GIOChannel *source, GIOCondition condition,
108 			    gpointer udata);
109 # else
110 static void dnotify_handler(int sig, siginfo_t *si, void *data);
111 # endif
112 #endif
113 
114 /****************************************************************
115  *			EXTERNAL INTERFACE			*
116  ****************************************************************/
117 
dir_init(void)118 void dir_init(void)
119 {
120 	dir_cache = g_fscache_new((GFSLoadFunc) dir_new,
121 				(GFSUpdateFunc) update, NULL);
122 
123 #ifdef USE_NOTIFY
124 	notify_fd_to_dir = g_hash_table_new(NULL, NULL);
125 
126 # ifdef USE_INOTIFY
127 	inotify_fd = inotify_init();
128 	inotify_channel = g_io_channel_unix_new(inotify_fd);
129 	g_io_add_watch(inotify_channel, G_IO_IN, inotify_handler, NULL);
130 # endif
131 
132 # ifdef USE_DNOTIFY
133 	{
134 		struct sigaction act;
135 
136 		act.sa_sigaction = dnotify_handler;
137 		sigemptyset(&act.sa_mask);
138 		act.sa_flags = SA_SIGINFO;
139 		sigaction(SIGRTMIN, &act, NULL);
140 
141 		/* Sometimes we get this instead of SIGRTMIN.
142 		 * Don't know why :-( but don't crash...
143 		 */
144 		act.sa_handler = SIG_IGN;
145 		sigemptyset(&act.sa_mask);
146 		act.sa_flags = 0;
147 		sigaction(SIGIO, &act, NULL);
148 
149 	}
150 # endif
151 #endif
152 }
153 
154 /* Periodically calls callback to notify about changes to the contents
155  * of the directory.
156  * Before this function returns, it calls the callback once to add all
157  * the items currently in the directory (unless the dir is empty).
158  * It then calls callback(DIR_QUEUE_INTERESTING) to find out which items the
159  * caller cares about.
160  * If we are not scanning, it also calls callback(DIR_END_SCAN).
161  */
dir_attach(Directory * dir,DirCallback callback,gpointer data)162 void dir_attach(Directory *dir, DirCallback callback, gpointer data)
163 {
164 	DirUser	*user;
165 	GPtrArray *items;
166 
167 	g_return_if_fail(dir != NULL);
168 	g_return_if_fail(callback != NULL);
169 
170 	user = g_new(DirUser, 1);
171 	user->callback = callback;
172 	user->data = data;
173 
174 #ifdef USE_INOTIFY
175 	if (!dir->users)
176 	{
177 		int fd;
178 
179 		if (dir->notify_fd != -1)
180 			g_warning("dir_attach: inotify error\n");
181 
182 		fd = inotify_add_watch( inotify_fd,
183 					dir->pathname,
184 					IN_CREATE | IN_DELETE | IN_MOVE |
185 					IN_ATTRIB);
186 
187 		g_return_if_fail(g_hash_table_lookup(notify_fd_to_dir,
188 						 GINT_TO_POINTER(fd)) == NULL);
189 		if (fd != -1)
190 		{
191 
192 			dir->notify_fd = fd;
193 			g_hash_table_insert(notify_fd_to_dir,
194 					    GINT_TO_POINTER(fd), dir);
195 		}
196 	}
197 #endif
198 #ifdef USE_DNOTIFY
199 	if (!dir->users)
200 	{
201 		int fd;
202 
203 		if (dir->notify_fd != -1)
204 			g_warning("dir_attach: dnotify error\n");
205 
206 		fd = open(dir->pathname, O_RDONLY);
207 		g_return_if_fail(g_hash_table_lookup(notify_fd_to_dir,
208 				 GINT_TO_POINTER(fd)) == NULL);
209 		if (fd != -1)
210 		{
211 			dir->notify_fd = fd;
212 			g_hash_table_insert(notify_fd_to_dir,
213 					GINT_TO_POINTER(fd), dir);
214 			fcntl(fd, F_SETSIG, SIGRTMIN);
215 			fcntl(fd, F_NOTIFY, DN_CREATE | DN_DELETE | DN_RENAME |
216 					    DN_ATTRIB | DN_MULTISHOT);
217 		}
218 	}
219 #endif
220 
221 	dir->users = g_list_prepend(dir->users, user);
222 
223 	g_object_ref(dir);
224 
225 	items = hash_to_array(dir->known_items);
226 	if (items->len)
227 		callback(dir, DIR_ADD, items, data);
228 	g_ptr_array_free(items, TRUE);
229 
230 	if (dir->needs_update && !dir->scanning)
231 		dir_rescan(dir);
232 	else
233 		callback(dir, DIR_QUEUE_INTERESTING, NULL, data);
234 
235 	/* May start scanning if noone was watching before */
236 	set_idle_callback(dir);
237 
238 	if (!dir->scanning)
239 		callback(dir, DIR_END_SCAN, NULL, data);
240 }
241 
242 /* Undo the effect of dir_attach */
dir_detach(Directory * dir,DirCallback callback,gpointer data)243 void dir_detach(Directory *dir, DirCallback callback, gpointer data)
244 {
245 	DirUser	*user;
246 	GList	*list;
247 
248 	g_return_if_fail(dir != NULL);
249 	g_return_if_fail(callback != NULL);
250 	g_return_if_fail(in_callback == 0);
251 
252 	for (list = dir->users; list; list = list->next)
253 	{
254 		user = (DirUser *) list->data;
255 		if (user->callback == callback && user->data == data)
256 		{
257 			g_free(user);
258 			dir->users = g_list_remove(dir->users, user);
259 			g_object_unref(dir);
260 
261 			/* May stop scanning if noone's watching */
262 			set_idle_callback(dir);
263 
264 #ifdef USE_NOTIFY
265 			if (!dir->users && dir->notify_fd != -1)
266 			{
267 # ifdef USE_DNOTIFY
268 				close(dir->notify_fd);
269 # endif
270 				g_hash_table_remove(notify_fd_to_dir,
271 					GINT_TO_POINTER(dir->notify_fd));
272 				dir->notify_fd = -1;
273 			}
274 # ifdef USE_INOTIFY
275 			if (dir->inotify_source) {
276 				g_source_remove(dir->inotify_source);
277 				dir->inotify_source = 0;
278 			}
279 # endif
280 #endif
281 			return;
282 		}
283 	}
284 
285 	g_warning("dir_detach: Callback/data pair not attached!\n");
286 }
287 
dir_update(Directory * dir,gchar * pathname)288 void dir_update(Directory *dir, gchar *pathname)
289 {
290 	update(dir, pathname, NULL);
291 }
292 
293 /* Rescan this directory */
refresh_dirs(const char * path)294 void refresh_dirs(const char *path)
295 {
296 	g_fscache_update(dir_cache, path);
297 }
298 
299 /* When something has happened to a particular object, call this
300  * and all appropriate changes will be made.
301  */
dir_check_this(const guchar * path)302 void dir_check_this(const guchar *path)
303 {
304 	guchar	*real_path;
305 	guchar	*dir_path;
306 	Directory *dir;
307 
308 	dir_path = g_path_get_dirname(path);
309 	real_path = pathdup(dir_path);
310 	g_free(dir_path);
311 
312 	dir = g_fscache_lookup_full(dir_cache, real_path,
313 					FSCACHE_LOOKUP_PEEK, NULL);
314 	if (dir)
315 	{
316 		dir_recheck(dir, real_path, g_basename(path));
317 		g_object_unref(dir);
318 	}
319 
320 	g_free(real_path);
321 }
322 
323 #ifdef USE_NOTIFY
drop_notify(gpointer key,gpointer value,gpointer data)324 static void drop_notify(gpointer key, gpointer value, gpointer data)
325 {
326 #ifdef USE_INOTIFY
327         inotify_rm_watch(inotify_fd, GPOINTER_TO_INT(key));
328 #endif
329 #ifdef USE_DNOTIFY
330 	close(GPOINTER_TO_INT(key));
331 #endif
332 }
333 #endif
334 
335 /* Used when we fork an action child, otherwise we can't delete or unmount
336  * any directory which we're watching via dnotify!  inotify does not have
337  * this problem
338  */
dir_drop_all_notifies(void)339 void dir_drop_all_notifies(void)
340 {
341 #ifdef USE_DNOTIFY
342 	g_hash_table_foreach(notify_fd_to_dir, drop_notify, NULL);
343 #endif
344 }
345 
346 /* Tell watchers that this item has changed, but don't rescan.
347  * (used when thumbnail has been created for an item)
348  */
dir_force_update_path(const gchar * path)349 void dir_force_update_path(const gchar *path)
350 {
351 	gchar	*dir_path;
352 	Directory *dir;
353 
354 	g_return_if_fail(path[0] == '/');
355 
356 	dir_path = g_path_get_dirname(path);
357 
358 	dir = g_fscache_lookup_full(dir_cache, dir_path, FSCACHE_LOOKUP_PEEK,
359 			NULL);
360 	if (dir)
361 	{
362 		dir_force_update_item(dir, g_basename(path));
363 		g_object_unref(dir);
364 	}
365 
366 	g_free(dir_path);
367 }
368 
369 /* Ensure that 'leafname' is up-to-date. Returns the new/updated
370  * DirItem, or NULL if the file no longer exists.
371  */
dir_update_item(Directory * dir,const gchar * leafname)372 DirItem *dir_update_item(Directory *dir, const gchar *leafname)
373 {
374 	DirItem *item;
375 
376 	time(&diritem_recent_time);
377 	item = insert_item(dir, leafname);
378 	dir_merge_new(dir);
379 
380 	return item;
381 }
382 
383 /* Add item to the recheck_list if it's marked as needing it.
384  * Item must have ITEM_FLAG_NEED_RESCAN_QUEUE.
385  * Items on the list will get checked later in an idle callback.
386  */
dir_queue_recheck(Directory * dir,DirItem * item)387 void dir_queue_recheck(Directory *dir, DirItem *item)
388 {
389 	g_return_if_fail(dir != NULL);
390 	g_return_if_fail(item != NULL);
391 	g_return_if_fail(item->flags & ITEM_FLAG_NEED_RESCAN_QUEUE);
392 
393 	dir->recheck_list = g_list_prepend(dir->recheck_list,
394 			g_strdup(item->leafname));
395 	item->flags &= ~ITEM_FLAG_NEED_RESCAN_QUEUE;
396 }
397 
free_recheck_list(Directory * dir)398 static void free_recheck_list(Directory *dir)
399 {
400 	destroy_glist(&dir->recheck_list);
401 }
402 
403 /* If scanning state has changed then notify all filer windows */
dir_set_scanning(Directory * dir,gboolean scanning)404 static void dir_set_scanning(Directory *dir, gboolean scanning)
405 {
406 	GList	*next;
407 
408 	if (scanning == dir->scanning)
409 		return;
410 
411 	in_callback++;
412 
413 	dir->scanning = scanning;
414 
415 	for (next = dir->users; next; next = next->next)
416 	{
417 		DirUser *user = (DirUser *) next->data;
418 
419 		user->callback(dir,
420 				scanning ? DIR_START_SCAN : DIR_END_SCAN,
421 				NULL, user->data);
422 	}
423 
424 #if 0
425 	/* Useful for profiling */
426 	if (!scanning)
427 	{
428 		g_print("Done\n");
429 		exit(0);
430 	}
431 #endif
432 
433 	in_callback--;
434 }
435 
436 /* Notify everyone that the error status of the directory has changed */
dir_error_changed(Directory * dir)437 static void dir_error_changed(Directory *dir)
438 {
439 	GList	*next;
440 
441 	in_callback++;
442 
443 	for (next = dir->users; next; next = next->next)
444 	{
445 		DirUser *user = (DirUser *) next->data;
446 
447 		user->callback(dir, DIR_ERROR_CHANGED, NULL, user->data);
448 	}
449 
450 	in_callback--;
451 }
452 
453 /* This is called in the background when there are items on the
454  * dir->recheck_list to process.
455  */
recheck_callback(gpointer data)456 static gboolean recheck_callback(gpointer data)
457 {
458 	Directory *dir = (Directory *) data;
459 	GList	*next;
460 	guchar	*leaf;
461 
462 	g_return_val_if_fail(dir != NULL, FALSE);
463 	g_return_val_if_fail(dir->recheck_list != NULL, FALSE);
464 
465 	/* Remove the first name from the list */
466 	next = dir->recheck_list;
467 	dir->recheck_list = g_list_remove_link(dir->recheck_list, next);
468 	leaf = (guchar *) next->data;
469 	g_list_free_1(next);
470 
471 	/* usleep(800); */
472 
473 	insert_item(dir, leaf);
474 
475 	g_free(leaf);
476 
477 	if (dir->recheck_list)
478 		return TRUE;	/* Call again */
479 
480 	/* The recheck_list list empty. Stop scanning, unless
481 	 * needs_update, in which case we start scanning again.
482 	 */
483 
484 	dir_merge_new(dir);
485 
486 	dir->have_scanned = TRUE;
487 	dir_set_scanning(dir, FALSE);
488 	g_source_remove(dir->idle_callback);
489 	dir->idle_callback = 0;
490 
491 	if (dir->needs_update)
492 		dir_rescan(dir);
493 
494 	return FALSE;
495 }
496 
497 /* Add all the new items to the items array.
498  * Notify everyone who is watching us.
499  */
dir_merge_new(Directory * dir)500 void dir_merge_new(Directory *dir)
501 {
502 	GPtrArray *new = dir->new_items;
503 	GPtrArray *up = dir->up_items;
504 	GPtrArray *gone = dir->gone_items;
505 	GList	  *list;
506 	guint	  i;
507 
508 	in_callback++;
509 
510 	for (list = dir->users; list; list = list->next)
511 	{
512 		DirUser *user = (DirUser *) list->data;
513 
514 		if (new->len)
515 			user->callback(dir, DIR_ADD, new, user->data);
516 		if (up->len)
517 			user->callback(dir, DIR_UPDATE, up, user->data);
518 		if (gone->len)
519 			user->callback(dir, DIR_REMOVE, gone, user->data);
520 	}
521 
522 	in_callback--;
523 
524 	for (i = 0; i < new->len; i++)
525 	{
526 		DirItem *item = (DirItem *) new->pdata[i];
527 
528 		g_hash_table_insert(dir->known_items, item->leafname, item);
529 	}
530 
531 	for (i = 0; i < gone->len; i++)
532 	{
533 		DirItem	*item = (DirItem *) gone->pdata[i];
534 
535 		diritem_free(item);
536 	}
537 
538 	g_ptr_array_set_size(gone, 0);
539 	g_ptr_array_set_size(new, 0);
540 	g_ptr_array_set_size(up, 0);
541 }
542 
543 #ifdef USE_DNOTIFY
544 /* Called from the mainloop shortly after dnotify_handler */
dnotify_wakeup(void)545 void dnotify_wakeup(void)
546 {
547 	Directory *dir;
548 
549 	dnotify_wakeup_flag = FALSE;
550 
551 	dir = g_hash_table_lookup(notify_fd_to_dir,
552 				  GINT_TO_POINTER(dnotify_last_fd));
553 
554 	if (dir)
555 		dir_rescan_soon(dir);
556 }
557 #endif
558 
559 /****************************************************************
560  *			INTERNAL FUNCTIONS			*
561  ****************************************************************/
562 
563 #ifdef USE_NOTIFY
rescan_soon_timeout(gpointer data)564 static gint rescan_soon_timeout(gpointer data)
565 {
566 	Directory *dir = (Directory *) data;
567 
568 	dir->rescan_timeout = -1;
569 	if (dir->scanning)
570 		dir->needs_update = TRUE;
571 	else
572 		dir_rescan(dir);
573 	return FALSE;
574 }
575 
576 /* Wait a fraction of a second and then rescan. If already waiting,
577  * this function does nothing.
578  */
dir_rescan_soon(Directory * dir)579 static void dir_rescan_soon(Directory *dir)
580 {
581 	if (dir->rescan_timeout != -1)
582 		return;
583 	dir->rescan_timeout = g_timeout_add(500, rescan_soon_timeout, dir);
584 }
585 #endif
586 
free_items_array(GPtrArray * array)587 static void free_items_array(GPtrArray *array)
588 {
589 	guint	i;
590 
591 	for (i = 0; i < array->len; i++)
592 	{
593 		DirItem	*item = (DirItem *) array->pdata[i];
594 
595 		diritem_free(item);
596 	}
597 
598 	g_ptr_array_free(array, TRUE);
599 }
600 
601 /* Tell everyone watching that these items have gone */
notify_deleted(Directory * dir,GPtrArray * deleted)602 static void notify_deleted(Directory *dir, GPtrArray *deleted)
603 {
604 	GList	*next;
605 
606 	if (!deleted->len)
607 		return;
608 
609 	in_callback++;
610 
611 	for (next = dir->users; next; next = next->next)
612 	{
613 		DirUser *user = (DirUser *) next->data;
614 
615 		user->callback(dir, DIR_REMOVE, deleted, user->data);
616 	}
617 
618 	in_callback--;
619 }
620 
mark_unused(gpointer key,gpointer value,gpointer data)621 static void mark_unused(gpointer key, gpointer value, gpointer data)
622 {
623 	DirItem	*item = (DirItem *) value;
624 
625 	item->may_delete = TRUE;
626 }
627 
keep_deleted(gpointer key,gpointer value,gpointer data)628 static void keep_deleted(gpointer key, gpointer value, gpointer data)
629 {
630 	DirItem	*item = (DirItem *) value;
631 	GPtrArray *deleted = (GPtrArray *) data;
632 
633 	if (item->may_delete)
634 		g_ptr_array_add(deleted, item);
635 }
636 
check_unused(gpointer key,gpointer value,gpointer data)637 static gboolean check_unused(gpointer key, gpointer value, gpointer data)
638 {
639 	DirItem	*item = (DirItem *) value;
640 
641 	return item->may_delete;
642 }
643 
644 /* Remove all the old items that have gone.
645  * Notify everyone who is watching us of the removed items.
646  */
remove_missing(Directory * dir,GPtrArray * keep)647 static void remove_missing(Directory *dir, GPtrArray *keep)
648 {
649 	GPtrArray	*deleted;
650 	guint		i;
651 
652 	deleted = g_ptr_array_new();
653 
654 	/* Mark all current items as may_delete */
655 	g_hash_table_foreach(dir->known_items, mark_unused, NULL);
656 
657 	/* Unmark all items also in 'keep' */
658 	for (i = 0; i < keep->len; i++)
659 	{
660 		guchar	*leaf = (guchar *) keep->pdata[i];
661 		DirItem *item;
662 
663 		item = g_hash_table_lookup(dir->known_items, leaf);
664 
665 		if (item)
666 			item->may_delete = FALSE;
667 	}
668 
669 	/* Add each item still marked to 'deleted' */
670 	g_hash_table_foreach(dir->known_items, keep_deleted, deleted);
671 
672 	/* Remove all items still marked */
673 	g_hash_table_foreach_remove(dir->known_items, check_unused, NULL);
674 
675 	notify_deleted(dir, deleted);
676 
677 	free_items_array(deleted);
678 }
679 
notify_timeout(gpointer data)680 static gint notify_timeout(gpointer data)
681 {
682 	Directory	*dir = (Directory *) data;
683 
684 	g_return_val_if_fail(dir->notify_active == TRUE, FALSE);
685 
686 	dir_merge_new(dir);
687 
688 	dir->notify_active = FALSE;
689 	g_object_unref(dir);
690 
691 	return FALSE;
692 }
693 
694 /* Call dir_merge_new() after a while. */
delayed_notify(Directory * dir)695 static void delayed_notify(Directory *dir)
696 {
697 	if (dir->notify_active)
698 		return;
699 	g_object_ref(dir);
700 	g_timeout_add(1500, notify_timeout, dir);
701 	dir->notify_active = TRUE;
702 }
703 
704 /* Stat this item and add, update or remove it.
705  * Returns the new/updated item, if any.
706  * (leafname may be from the current DirItem item)
707  * Ensure diritem_recent_time is reasonably up-to-date before calling this.
708  */
insert_item(Directory * dir,const guchar * leafname)709 static DirItem *insert_item(Directory *dir, const guchar *leafname)
710 {
711 	const gchar  	*full_path;
712 	DirItem		*item;
713 	DirItem		old;
714 	gboolean	do_compare = FALSE;	/* (old is filled in) */
715 
716 	if (leafname[0] == '.' && (leafname[1] == '\n' ||
717 			(leafname[1] == '.' && leafname[2] == '\n')))
718 		return NULL;		/* Ignore '.' and '..' */
719 
720 	full_path = make_path(dir->pathname, leafname);
721 	item = g_hash_table_lookup(dir->known_items, leafname);
722 
723 	if (item)
724 	{
725 		if (item->base_type != TYPE_UNKNOWN)
726 		{
727 			/* Preserve the old details so we can compare */
728 			old = *item;
729 			if (old._image)
730 				g_object_ref(old._image);
731 			do_compare = TRUE;
732 		}
733 		diritem_restat(full_path, item, &dir->stat_info);
734 	}
735 	else
736 	{
737 		/* Item isn't already here. This won't normally happen,
738 		 * because blank items are added when scanning, before
739 		 * we get here.
740 		 */
741 		item = diritem_new(leafname);
742 		diritem_restat(full_path, item, &dir->stat_info);
743 		if (item->base_type == TYPE_ERROR &&
744 				item->lstat_errno == ENOENT)
745 		{
746 			diritem_free(item);
747 			return NULL;
748 		}
749 		g_ptr_array_add(dir->new_items, item);
750 
751 	}
752 
753 	/* No need to queue the item for scanning. If we got here because
754 	 * the item was queued, this flag will normally already be clear.
755 	 */
756 	item->flags &= ~ITEM_FLAG_NEED_RESCAN_QUEUE;
757 
758 	if (item->base_type == TYPE_ERROR && item->lstat_errno == ENOENT)
759 	{
760 		/* Item has been deleted */
761 		g_hash_table_remove(dir->known_items, item->leafname);
762 		g_ptr_array_add(dir->gone_items, item);
763 		if (do_compare && old._image)
764 			g_object_unref(old._image);
765 		delayed_notify(dir);
766 		return NULL;
767 	}
768 
769 	if (do_compare)
770 	{
771 		/* It's a bit inefficient that we force the image to be
772 		 * loaded here, if we had an old image.
773 		 */
774 		if (item->lstat_errno == old.lstat_errno
775 		 && item->base_type == old.base_type
776 		 && item->flags == old.flags
777 		 && item->size == old.size
778 		 && item->mode == old.mode
779 		 && item->atime == old.atime
780 		 && item->ctime == old.ctime
781 		 && item->mtime == old.mtime
782 		 && item->uid == old.uid
783 		 && item->gid == old.gid
784 		 && item->mime_type == old.mime_type
785 		 && (old._image == NULL || di_image(item) == old._image))
786 		{
787 			if (old._image)
788 				g_object_unref(old._image);
789 			return item;
790 		}
791 		if (old._image)
792 			g_object_unref(old._image);
793 	}
794 
795 	g_ptr_array_add(dir->up_items, item);
796 	delayed_notify(dir);
797 
798 	return item;
799 }
800 
update(Directory * dir,gchar * pathname,gpointer data)801 static void update(Directory *dir, gchar *pathname, gpointer data)
802 {
803 	g_free(dir->pathname);
804 	dir->pathname = pathdup(pathname);
805 
806 	if (dir->scanning)
807 		dir->needs_update = TRUE;
808 	else
809 		dir_rescan(dir);
810 }
811 
812 /* If there is work to do, set the idle callback.
813  * Otherwise, stop scanning and unset the idle callback.
814  */
set_idle_callback(Directory * dir)815 static void set_idle_callback(Directory *dir)
816 {
817 	if (dir->recheck_list && dir->users)
818 	{
819 		/* Work to do, and someone's watching */
820 		dir_set_scanning(dir, TRUE);
821 		if (dir->idle_callback)
822 			return;
823 		time(&diritem_recent_time);
824 		dir->idle_callback = g_idle_add(recheck_callback, dir);
825 		/* Do the first call now (will remove the callback itself) */
826 		recheck_callback(dir);
827 	}
828 	else
829 	{
830 		dir_set_scanning(dir, FALSE);
831 		if (dir->idle_callback)
832 		{
833 			g_source_remove(dir->idle_callback);
834 			dir->idle_callback = 0;
835 		}
836 	}
837 }
838 
839 /* See dir_force_update_path() */
dir_force_update_item(Directory * dir,const gchar * leaf)840 static void dir_force_update_item(Directory *dir, const gchar *leaf)
841 {
842 	GList *list;
843 	GPtrArray *items;
844 	DirItem *item;
845 
846 	items = g_ptr_array_new();
847 
848 	item = g_hash_table_lookup(dir->known_items, leaf);
849 	if (!item)
850 		goto out;
851 
852 	g_ptr_array_add(items, item);
853 
854 	in_callback++;
855 
856 	for (list = dir->users; list; list = list->next)
857 	{
858 		DirUser *user = (DirUser *) list->data;
859 
860 		user->callback(dir, DIR_UPDATE, items, user->data);
861 	}
862 
863 	in_callback--;
864 
865 out:
866 	g_ptr_array_free(items, TRUE);
867 }
868 
dir_recheck(Directory * dir,const guchar * path,const guchar * leafname)869 static void dir_recheck(Directory *dir,
870 			const guchar *path, const guchar *leafname)
871 {
872 	guchar *old = dir->pathname;
873 
874 	dir->pathname = g_strdup(path);
875 	g_free(old);
876 
877 	time(&diritem_recent_time);
878 	insert_item(dir, leafname);
879 }
880 
to_array(gpointer key,gpointer value,gpointer data)881 static void to_array(gpointer key, gpointer value, gpointer data)
882 {
883 	GPtrArray *array = (GPtrArray *) data;
884 
885 	g_ptr_array_add(array, value);
886 }
887 
888 /* Convert a hash table to an unsorted GPtrArray.
889  * g_ptr_array_free() the result.
890  */
hash_to_array(GHashTable * hash)891 static GPtrArray *hash_to_array(GHashTable *hash)
892 {
893 	GPtrArray *array;
894 
895 	array = g_ptr_array_new();
896 
897 	g_hash_table_foreach(hash, to_array, array);
898 
899 	return array;
900 }
901 
902 static gpointer parent_class;
903 
904 /* Note: dir_cache is never purged, so this shouldn't get called */
dir_finialize(GObject * object)905 static void dir_finialize(GObject *object)
906 {
907 	GPtrArray *items;
908 	Directory *dir = (Directory *) object;
909 
910 	g_return_if_fail(dir->users == NULL);
911 
912 	g_print("[ dir finalize ]\n");
913 
914 	free_recheck_list(dir);
915 	set_idle_callback(dir);
916 	if (dir->rescan_timeout != -1)
917 		g_source_remove(dir->rescan_timeout);
918 
919 	dir_merge_new(dir);	/* Ensures new, up and gone are empty */
920 
921 	g_ptr_array_free(dir->up_items, TRUE);
922 	g_ptr_array_free(dir->new_items, TRUE);
923 	g_ptr_array_free(dir->gone_items, TRUE);
924 
925 	items = hash_to_array(dir->known_items);
926 	free_items_array(items);
927 	g_hash_table_destroy(dir->known_items);
928 
929 	g_free(dir->error);
930 	g_free(dir->pathname);
931 
932 	G_OBJECT_CLASS(parent_class)->finalize(object);
933 }
934 
directory_class_init(gpointer gclass,gpointer data)935 static void directory_class_init(gpointer gclass, gpointer data)
936 {
937 	GObjectClass *object = (GObjectClass *) gclass;
938 
939 	parent_class = g_type_class_peek_parent(gclass);
940 
941 	object->finalize = dir_finialize;
942 }
943 
directory_init(GTypeInstance * object,gpointer gclass)944 static void directory_init(GTypeInstance *object, gpointer gclass)
945 {
946 	Directory *dir = (Directory *) object;
947 
948 	dir->known_items = g_hash_table_new(g_str_hash, g_str_equal);
949 	dir->recheck_list = NULL;
950 	dir->idle_callback = 0;
951 	dir->scanning = FALSE;
952 	dir->have_scanned = FALSE;
953 
954 	dir->users = NULL;
955 	dir->needs_update = TRUE;
956 	dir->notify_active = FALSE;
957 	dir->pathname = NULL;
958 	dir->error = NULL;
959 	dir->rescan_timeout = -1;
960 #ifdef USE_NOTIFY
961 	dir->notify_fd = -1;
962 #endif
963 #ifdef USE_INOTIFY
964 	dir->inotify_source = 0;
965 #endif
966 
967 	dir->new_items = g_ptr_array_new();
968 	dir->up_items = g_ptr_array_new();
969 	dir->gone_items = g_ptr_array_new();
970 }
971 
dir_get_type(void)972 static GType dir_get_type(void)
973 {
974 	static GType type = 0;
975 
976 	if (!type)
977 	{
978 		static const GTypeInfo info =
979 		{
980 			sizeof (DirectoryClass),
981 			NULL,			/* base_init */
982 			NULL,			/* base_finalise */
983 			directory_class_init,
984 			NULL,			/* class_finalise */
985 			NULL,			/* class_data */
986 			sizeof(Directory),
987 			0,			/* n_preallocs */
988 			directory_init
989 		};
990 
991 		type = g_type_register_static(G_TYPE_OBJECT, "Directory",
992 					      &info, 0);
993 	}
994 
995 	return type;
996 }
997 
dir_new(const char * pathname)998 static Directory *dir_new(const char *pathname)
999 {
1000 	Directory *dir;
1001 
1002 	dir = g_object_new(dir_get_type(), NULL);
1003 
1004 	dir->pathname = g_strdup(pathname);
1005 
1006 	return dir;
1007 }
1008 
1009 /* Get the names of all files in the directory.
1010  * Remove any DirItems that are no longer listed.
1011  * Replace the recheck_list with the items found.
1012  */
dir_rescan(Directory * dir)1013 static void dir_rescan(Directory *dir)
1014 {
1015 	GPtrArray	*names;
1016 	DIR		*d;
1017 	struct dirent	*ent;
1018 	guint		i;
1019 	const char	*pathname;
1020 	GList		*next;
1021 
1022 	g_return_if_fail(dir != NULL);
1023 
1024 	pathname = dir->pathname;
1025 
1026 	dir->needs_update = FALSE;
1027 
1028 	names = g_ptr_array_new();
1029 
1030 	read_globicons();
1031 	mount_update(FALSE);
1032 	if (dir->error)
1033 	{
1034 		null_g_free(&dir->error);
1035 		dir_error_changed(dir);
1036 	}
1037 
1038 	/* Saves statting the parent for each item... */
1039 	if (mc_stat(pathname, &dir->stat_info))
1040 	{
1041 		dir->error = g_strdup_printf(_("Can't stat directory: %s"),
1042 				g_strerror(errno));
1043 		dir_error_changed(dir);
1044 		return;		/* Report on attach */
1045 	}
1046 
1047 	d = mc_opendir(pathname);
1048 	if (!d)
1049 	{
1050 		dir->error = g_strdup_printf(_("Can't open directory: %s"),
1051 				g_strerror(errno));
1052 		dir_error_changed(dir);
1053 		return;		/* Report on attach */
1054 	}
1055 
1056 	dir_set_scanning(dir, TRUE);
1057 	dir_merge_new(dir);
1058 	gdk_flush();
1059 
1060 	/* Make a list of all the names in the directory */
1061 	while ((ent = mc_readdir(d)))
1062 	{
1063 		if (ent->d_name[0] == '.')
1064 		{
1065 			if (ent->d_name[1] == '\0')
1066 				continue;		/* Ignore '.' */
1067 			if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
1068 				continue;		/* Ignore '..' */
1069 		}
1070 
1071 		g_ptr_array_add(names, g_strdup(ent->d_name));
1072 	}
1073 	mc_closedir(d);
1074 
1075 	/* Compare the list with the current DirItems, removing
1076 	 * any that are missing.
1077 	 */
1078 	remove_missing(dir, names);
1079 
1080 	free_recheck_list(dir);
1081 
1082 	/* For each name found, mark it as needing to be put on the rescan
1083 	 * list at some point in the future.
1084 	 * If the item is new, put a blank place-holder item in the directory.
1085 	 */
1086 	for (i = 0; i < names->len; i++)
1087 	{
1088 		DirItem *old;
1089 		guchar *name = names->pdata[i];
1090 
1091 		old = g_hash_table_lookup(dir->known_items, name);
1092 		if (old)
1093 		{
1094 			/* This flag is cleared when the item is added
1095 			 * to the rescan list.
1096 			 */
1097 			old->flags |= ITEM_FLAG_NEED_RESCAN_QUEUE;
1098 		}
1099 		else
1100 		{
1101 			DirItem *new;
1102 
1103 			new = diritem_new(name);
1104 			g_ptr_array_add(dir->new_items, new);
1105 		}
1106 
1107 	}
1108 
1109 	dir_merge_new(dir);
1110 
1111 	/* Ask everyone which items they need to display, and add them to
1112 	 * the recheck list. Typically, this means we don't waste time
1113 	 * scanning hidden items.
1114 	 */
1115 	in_callback++;
1116 	for (next = dir->users; next; next = next->next)
1117 	{
1118 		DirUser *user = (DirUser *) next->data;
1119 		user->callback(dir,
1120 				DIR_QUEUE_INTERESTING,
1121 				NULL, user->data);
1122 	}
1123 	in_callback--;
1124 
1125 	g_ptr_array_free(names, TRUE);
1126 
1127 	set_idle_callback(dir);
1128 	dir_merge_new(dir);
1129 }
1130 
1131 #ifdef USE_DNOTIFY
1132 /* Signal handler - don't do anything dangerous here */
dnotify_handler(int sig,siginfo_t * si,void * data)1133 static void dnotify_handler(int sig, siginfo_t *si, void *data)
1134 {
1135 	/* Note: there is currently only one place to store the fd,
1136 	 * so we'll miss updates to several directories if they happen
1137 	 * close together.
1138 	 */
1139 	dnotify_last_fd = si->si_fd;
1140 	dnotify_wakeup_flag = TRUE;
1141 	write(to_wakeup_pipe, "\0", 1);	/* Wake up! */
1142 }
1143 #endif
1144 
1145 #ifdef USE_INOTIFY
inotify_handler(GIOChannel * source,GIOCondition condition,gpointer udata)1146 static gboolean inotify_handler(GIOChannel *source, GIOCondition condition,
1147 				gpointer udata)
1148 {
1149 	int fd = g_io_channel_unix_get_fd(source);
1150 	Directory *dir;
1151 	char buf[sizeof(struct inotify_event)+1024];
1152 	int len, i = 0;
1153 
1154 	len = read(fd, buf, sizeof(buf));
1155 	if (len<0)
1156 	{
1157 		if (errno != EINTR)
1158 			perror("read");
1159 		return TRUE;
1160 	}
1161 	else if (!len)
1162 		return TRUE;
1163 
1164 	while (i<len)
1165 	{
1166 		struct inotify_event *event=(struct inotify_event *) (buf+i);
1167 
1168 		dir = g_hash_table_lookup(notify_fd_to_dir,
1169 					  GINT_TO_POINTER(event->wd));
1170 		if (dir)
1171 			dir_rescan_soon(dir);
1172 
1173 		i += sizeof(*event)+event->len;
1174 	}
1175 
1176 
1177 	return TRUE;
1178 }
1179 #endif
1180