4 * dir.c - Caches and updates directories
5 * Copyright (C) 2000, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
28 #include "gui_support.h"
34 GFSCache
*dir_cache
= NULL
;
36 /* Static prototypes */
37 static Directory
*load(char *pathname
, gpointer data
);
38 static void ref(Directory
*dir
, gpointer data
);
39 static void unref(Directory
*dir
, gpointer data
);
40 static int getref(Directory
*dir
, gpointer data
);
41 static void update(Directory
*dir
, gchar
*pathname
, gpointer data
);
42 static void destroy(Directory
*dir
);
43 static void start_scanning(Directory
*dir
, char *pathname
);
44 static gint
idle_callback(Directory
*dir
);
45 static void init_for_scan(Directory
*dir
);
46 static void merge_new(Directory
*dir
);
49 /****************************************************************
50 * EXTERNAL INTERFACE *
51 ****************************************************************/
55 dir_cache
= g_fscache_new((GFSLoadFunc
) load
,
58 (GFSGetRefFunc
) getref
,
59 (GFSUpdateFunc
) update
, NULL
);
62 /* Periodically calls callback to notify about changes to the contents
64 * Before this function returns, it calls the callback once to add all
65 * the items currently in the directory (unless the dir is empty).
66 * If we are not scanning, it also calls update(DIR_END_SCAN).
68 void dir_attach(Directory
*dir
, DirCallback callback
, gpointer data
)
72 g_return_if_fail(dir
!= NULL
);
73 g_return_if_fail(callback
!= NULL
);
75 user
= g_new(DirUser
, 1);
76 user
->callback
= callback
;
79 dir
->users
= g_list_prepend(dir
->users
, user
);
84 callback(dir
, DIR_ADD
, dir
->items
, data
);
86 if (dir
->needs_update
)
87 start_scanning(dir
, dir
->pathname
);
90 delayed_error(PROJECT
, dir
->error
);
93 callback(dir
, DIR_END_SCAN
, NULL
, data
);
96 /* Undo the effect of dir_attach */
97 void dir_detach(Directory
*dir
, DirCallback callback
, gpointer data
)
100 GList
*list
= dir
->users
;
102 g_return_if_fail(dir
!= NULL
);
103 g_return_if_fail(callback
!= NULL
);
107 user
= (DirUser
*) list
->data
;
108 if (user
->callback
== callback
&& user
->data
== data
)
111 dir
->users
= g_list_remove(dir
->users
, user
);
117 mc_closedir(dir
->dir_handle
);
118 dir
->dir_handle
= NULL
;
119 gtk_idle_remove(dir
->idle
);
121 dir
->needs_update
= TRUE
;
129 g_warning("dir_detach: Callback/data pair not attached!\n");
132 void dir_update(Directory
*dir
, gchar
*pathname
)
134 update(dir
, pathname
, NULL
);
137 int dir_item_cmp(const void *a
, const void *b
)
139 DirItem
*aa
= *((DirItem
**) a
);
140 DirItem
*bb
= *((DirItem
**) b
);
142 return strcmp(aa
->leafname
, bb
->leafname
);
145 void refresh_dirs(char *path
)
147 g_fscache_update(dir_cache
, path
);
150 /****************************************************************
151 * INTERNAL FUNCTIONS *
152 ****************************************************************/
154 static void free_items_array(GPtrArray
*array
)
158 for (i
= 0; i
< array
->len
; i
++)
160 DirItem
*item
= (DirItem
*) array
->pdata
[i
];
162 g_free(item
->leafname
);
166 g_ptr_array_free(array
, TRUE
);
169 /* Scanning has finished. Remove all the old items that have gone.
170 * Notify everyone who is watching us of the removed items and tell
171 * them that the scan is over, unless 'needs_update'.
173 static void sweep_deleted(Directory
*dir
)
175 GPtrArray
*array
= dir
->items
;
176 int items
= array
->len
;
179 DirItem
**from
= (DirItem
**) array
->pdata
;
180 DirItem
**to
= (DirItem
**) array
->pdata
;
181 GList
*list
= dir
->users
;
183 old
= g_ptr_array_new();
187 if (from
[0]->may_delete
)
189 g_ptr_array_add(old
, *from
);
201 g_ptr_array_set_size(array
, new_items
);
205 DirUser
*user
= (DirUser
*) list
->data
;
208 user
->callback(dir
, DIR_REMOVE
, old
, user
->data
);
209 if (!dir
->needs_update
)
210 user
->callback(dir
, DIR_END_SCAN
, NULL
, user
->data
);
215 free_items_array(old
);
219 /* Add all the new items to the items array.
220 * Notify everyone who is watching us.
222 static void merge_new(Directory
*dir
)
224 GList
*list
= dir
->users
;
225 GPtrArray
*new = dir
->new_items
;
226 GPtrArray
*up
= dir
->up_items
;
231 DirUser
*user
= (DirUser
*) list
->data
;
234 user
->callback(dir
, DIR_ADD
, new, user
->data
);
236 user
->callback(dir
, DIR_UPDATE
, up
, user
->data
);
242 for (i
= 0; i
< new->len
; i
++)
243 g_ptr_array_add(dir
->items
, new->pdata
[i
]);
244 qsort(dir
->items
->pdata
, dir
->items
->len
, sizeof(DirItem
*),
247 g_ptr_array_set_size(new, 0);
248 g_ptr_array_set_size(up
, 0);
251 static void init_for_scan(Directory
*dir
)
255 dir
->needs_update
= FALSE
;
256 dir
->done_some_scanning
= FALSE
;
258 for (i
= 0; i
< dir
->items
->len
; i
++)
259 ((DirItem
*) dir
->items
->pdata
[i
])->may_delete
= TRUE
;
262 static void start_scanning(Directory
*dir
, char *pathname
)
268 /* We are already scanning */
269 if (dir
->done_some_scanning
)
270 dir
->needs_update
= TRUE
;
282 dir
->dir_handle
= mc_opendir(pathname
);
284 if (!dir
->dir_handle
)
286 dir
->error
= g_strdup_printf(_("Can't open directory: %s"),
288 return; /* Report on attach */
291 dir
->dir_start
= mc_telldir(dir
->dir_handle
);
295 dir
->idle
= gtk_idle_add((GtkFunction
) idle_callback
, dir
);
297 /* Let all the filer windows know we're scanning */
298 for (next
= dir
->users
; next
; next
= next
->next
)
300 DirUser
*user
= (DirUser
*) next
->data
;
302 user
->callback(dir
, DIR_START_SCAN
, NULL
, user
->data
);
306 static gint
notify_timeout(gpointer data
)
308 Directory
*dir
= (Directory
*) data
;
310 g_return_val_if_fail(dir
->notify_active
== TRUE
, FALSE
);
314 dir
->notify_active
= FALSE
;
320 /* Call merge_new() after a while. */
321 static void delayed_notify(Directory
*dir
)
323 if (dir
->notify_active
)
326 gtk_timeout_add(500, notify_timeout
, dir
);
327 dir
->notify_active
= TRUE
;
330 static void insert_item(Directory
*dir
, struct dirent
*ent
)
332 static GString
*tmp
= NULL
;
335 GPtrArray
*array
= dir
->items
;
340 gboolean is_new
= FALSE
;
343 new.mime_type
= NULL
;
346 tmp
= make_path(dir
->pathname
, ent
->d_name
);
348 if (mc_lstat(tmp
->str
, &info
) == -1)
350 new.lstat_errno
= errno
;
351 new.base_type
= TYPE_ERROR
;
355 new.uid
= (uid_t
) -1;
356 new.gid
= (gid_t
) -1;
361 new.size
= info
.st_size
;
362 new.mode
= info
.st_mode
;
363 new.mtime
= info
.st_mtime
;
364 new.uid
= info
.st_uid
;
365 new.gid
= info
.st_gid
;
366 if (S_ISREG(info
.st_mode
))
367 new.base_type
= TYPE_FILE
;
368 else if (S_ISDIR(info
.st_mode
))
370 new.base_type
= TYPE_DIRECTORY
;
372 if (g_hash_table_lookup(mtab_mounts
, tmp
->str
))
373 new.flags
|= ITEM_FLAG_MOUNT_POINT
375 else if (g_hash_table_lookup(fstab_mounts
, tmp
->str
))
376 new.flags
|= ITEM_FLAG_MOUNT_POINT
;
378 else if (S_ISBLK(info
.st_mode
))
379 new.base_type
= TYPE_BLOCK_DEVICE
;
380 else if (S_ISCHR(info
.st_mode
))
381 new.base_type
= TYPE_CHAR_DEVICE
;
382 else if (S_ISFIFO(info
.st_mode
))
383 new.base_type
= TYPE_PIPE
;
384 else if (S_ISSOCK(info
.st_mode
))
385 new.base_type
= TYPE_SOCKET
;
386 else if (S_ISLNK(info
.st_mode
))
388 if (mc_stat(tmp
->str
, &info
))
389 new.base_type
= TYPE_ERROR
;
392 if (S_ISREG(info
.st_mode
))
393 new.base_type
= TYPE_FILE
;
394 else if (S_ISDIR(info
.st_mode
))
395 new.base_type
= TYPE_DIRECTORY
;
396 else if (S_ISBLK(info
.st_mode
))
397 new.base_type
= TYPE_BLOCK_DEVICE
;
398 else if (S_ISCHR(info
.st_mode
))
399 new.base_type
= TYPE_CHAR_DEVICE
;
400 else if (S_ISFIFO(info
.st_mode
))
401 new.base_type
= TYPE_PIPE
;
402 else if (S_ISSOCK(info
.st_mode
))
403 new.base_type
= TYPE_SOCKET
;
405 new.base_type
= TYPE_UNKNOWN
;
408 new.flags
|= ITEM_FLAG_SYMLINK
;
411 new.base_type
= TYPE_UNKNOWN
;
413 if (new.base_type
== TYPE_DIRECTORY
&&
414 !(new.flags
& ITEM_FLAG_MOUNT_POINT
))
416 uid_t uid
= info
.st_uid
;
418 /* Might be an application directory - better check...
419 * AppRun must have the same owner as the directory
420 * (to stop people putting an AppRun in, eg, /tmp)
422 g_string_append(tmp
, "/AppRun");
423 if (!mc_stat(tmp
->str
, &info
) && info
.st_uid
== uid
)
425 MaskedPixmap
*app_icon
;
427 new.flags
|= ITEM_FLAG_APPDIR
;
429 g_string_truncate(tmp
, tmp
->len
- 3);
430 g_string_append(tmp
, "Icon.xpm");
431 app_icon
= g_fscache_lookup(pixmap_cache
, tmp
->str
);
434 new.image
= app_icon
;
435 new.flags
|= ITEM_FLAG_TEMP_ICON
;
438 new.image
= im_appdir
;
441 else if (new.base_type
== TYPE_FILE
)
443 /* Note: for symlinks we use need the mode of the target */
444 if (info
.st_mode
& (S_IXUSR
| S_IXGRP
| S_IXOTH
))
446 new.image
= im_exec_file
;
447 new.flags
|= ITEM_FLAG_EXEC_FILE
;
451 new.mime_type
= type_from_path(tmp
->str
);
452 new.flags
|= ITEM_FLAG_TEMP_ICON
;
457 new.mime_type
= mime_type_from_base_type(new.base_type
);
460 new.image
= type_to_icon(new.mime_type
);
462 for (i
= 0; i
< array
->len
; i
++)
464 item
= (DirItem
*) array
->pdata
[i
];
466 if (strcmp(item
->leafname
, ent
->d_name
) == 0)
470 item
= g_new(DirItem
, 1);
471 item
->leafname
= g_strdup(ent
->d_name
);
472 g_ptr_array_add(dir
->new_items
, item
);
476 item
->may_delete
= FALSE
;
478 font
= gtk_widget_get_default_style()->font
;
479 new.name_width
= gdk_string_width(font
, item
->leafname
);
480 new.leafname
= item
->leafname
;
481 new.details_width
= gdk_string_width(fixed_font
, details(&new));
485 if (item
->flags
& ITEM_FLAG_TEMP_ICON
)
486 pixmap_unref(item
->image
);
487 if (item
->lstat_errno
== new.lstat_errno
488 && item
->base_type
== new.base_type
489 && item
->flags
== new.flags
490 && item
->size
== new.size
491 && item
->mode
== new.mode
492 && item
->mtime
== new.mtime
493 && item
->uid
== new.uid
494 && item
->gid
== new.gid
495 && item
->image
== new.image
496 && item
->mime_type
== new.mime_type
497 && item
->name_width
== new.name_width
498 && item
->details_width
== new.details_width
)
502 item
->image
= new.image
;
503 item
->lstat_errno
= new.lstat_errno
;
504 item
->base_type
= new.base_type
;
505 item
->flags
= new.flags
;
506 item
->size
= new.size
;
507 item
->mode
= new.mode
;
510 item
->mtime
= new.mtime
;
511 item
->mime_type
= new.mime_type
;
512 item
->name_width
= new.name_width
;
513 item
->details_width
= new.details_width
;
517 g_ptr_array_add(dir
->up_items
, item
);
522 static gint
idle_callback(Directory
*dir
)
526 dir
->done_some_scanning
= TRUE
;
530 ent
= mc_readdir(dir
->dir_handle
);
537 if (dir
->needs_update
)
539 mc_seekdir(dir
->dir_handle
, dir
->dir_start
);
544 mc_closedir(dir
->dir_handle
);
545 dir
->dir_handle
= NULL
;
547 return FALSE
; /* Stop */
550 insert_item(dir
, ent
);
552 } while (!gtk_events_pending());
557 static Directory
*load(char *pathname
, gpointer data
)
561 dir
= g_new(Directory
, 1);
563 dir
->items
= g_ptr_array_new();
564 dir
->new_items
= g_ptr_array_new();
565 dir
->up_items
= g_ptr_array_new();
567 dir
->dir_handle
= NULL
;
568 dir
->needs_update
= TRUE
;
569 dir
->notify_active
= FALSE
;
570 dir
->pathname
= g_strdup(pathname
);
576 static void destroy(Directory
*dir
)
578 g_return_if_fail(dir
->users
== NULL
);
580 g_print("[ destroy %p ]\n", dir
);
583 mc_closedir(dir
->dir_handle
);
584 gtk_idle_remove(dir
->idle
);
586 g_ptr_array_free(dir
->up_items
, TRUE
);
587 free_items_array(dir
->items
);
588 free_items_array(dir
->new_items
);
590 g_free(dir
->pathname
);
594 static void ref(Directory
*dir
, gpointer data
)
599 static void unref(Directory
*dir
, gpointer data
)
605 static int getref(Directory
*dir
, gpointer data
)
610 static void update(Directory
*dir
, gchar
*pathname
, gpointer data
)
612 g_free(dir
->pathname
);
613 dir
->pathname
= g_strdup(pathname
);
615 start_scanning(dir
, pathname
);
618 delayed_error(PROJECT
, dir
->error
);