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