r343: Updated the docs.
[rox-filer/ma.git] / ROX-Filer / src / dir.c
blob4b384a043722a702dd0bd03a084ce2732d13a88e
1 /*
2 * $Id$
4 * dir.c - Caches and updates directories
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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)
10 * any later version.
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
15 * more details.
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
22 #include "config.h"
24 #include <gtk/gtk.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <string.h>
29 #include "global.h"
31 #include "support.h"
32 #include "gui_support.h"
33 #include "dir.h"
34 #include "fscache.h"
35 #include "mount.h"
36 #include "pixmaps.h"
37 #include "type.h"
39 GFSCache *dir_cache = NULL;
41 /* Static prototypes */
42 static Directory *load(char *pathname, gpointer data);
43 static void ref(Directory *dir, gpointer data);
44 static void unref(Directory *dir, gpointer data);
45 static int getref(Directory *dir, gpointer data);
46 static void update(Directory *dir, gchar *pathname, gpointer data);
47 static void destroy(Directory *dir);
48 static void start_scanning(Directory *dir, char *pathname);
49 static gint idle_callback(Directory *dir);
50 static void init_for_scan(Directory *dir);
51 static void merge_new(Directory *dir);
54 /****************************************************************
55 * EXTERNAL INTERFACE *
56 ****************************************************************/
58 void dir_init(void)
60 dir_cache = g_fscache_new((GFSLoadFunc) load,
61 (GFSRefFunc) ref,
62 (GFSRefFunc) unref,
63 (GFSGetRefFunc) getref,
64 (GFSUpdateFunc) update, NULL);
67 /* Periodically calls callback to notify about changes to the contents
68 * of the directory.
69 * Before this function returns, it calls the callback once to add all
70 * the items currently in the directory (unless the dir is empty).
71 * If we are not scanning, it also calls update(DIR_END_SCAN).
73 void dir_attach(Directory *dir, DirCallback callback, gpointer data)
75 DirUser *user;
77 g_return_if_fail(dir != NULL);
78 g_return_if_fail(callback != NULL);
80 user = g_new(DirUser, 1);
81 user->callback = callback;
82 user->data = data;
84 dir->users = g_list_prepend(dir->users, user);
86 ref(dir, NULL);
88 if (dir->items->len)
89 callback(dir, DIR_ADD, dir->items, data);
91 if (dir->needs_update)
92 start_scanning(dir, dir->pathname);
94 if (dir->error)
95 delayed_error(PROJECT, dir->error);
97 if (!dir->dir_handle)
98 callback(dir, DIR_END_SCAN, NULL, data);
101 /* Undo the effect of dir_attach */
102 void dir_detach(Directory *dir, DirCallback callback, gpointer data)
104 DirUser *user;
105 GList *list = dir->users;
107 g_return_if_fail(dir != NULL);
108 g_return_if_fail(callback != NULL);
110 while (list)
112 user = (DirUser *) list->data;
113 if (user->callback == callback && user->data == data)
115 g_free(user);
116 dir->users = g_list_remove(dir->users, user);
117 unref(dir, NULL);
118 if (!dir->users)
120 if (dir->dir_handle)
122 mc_closedir(dir->dir_handle);
123 dir->dir_handle = NULL;
124 gtk_idle_remove(dir->idle);
125 merge_new(dir);
126 dir->needs_update = TRUE;
129 return;
131 list = list->next;
134 g_warning("dir_detach: Callback/data pair not attached!\n");
137 void dir_update(Directory *dir, gchar *pathname)
139 update(dir, pathname, NULL);
142 int dir_item_cmp(const void *a, const void *b)
144 DirItem *aa = *((DirItem **) a);
145 DirItem *bb = *((DirItem **) b);
147 return strcmp(aa->leafname, bb->leafname);
150 void refresh_dirs(char *path)
152 g_fscache_update(dir_cache, path);
155 /* Bring this item's structure uptodate */
156 void dir_restat(guchar *path, DirItem *item)
158 struct stat info;
160 if (item->image && item->flags & ITEM_FLAG_TEMP_ICON)
162 pixmap_unref(item->image);
163 item->image = NULL;
165 item->flags = 0;
166 item->mime_type = NULL;
168 if (mc_lstat(path, &info) == -1)
170 item->lstat_errno = errno;
171 item->base_type = TYPE_ERROR;
172 item->size = 0;
173 item->mode = 0;
174 item->mtime = 0;
175 item->uid = (uid_t) -1;
176 item->gid = (gid_t) -1;
178 else
180 item->lstat_errno = 0;
181 item->size = info.st_size;
182 item->mode = info.st_mode;
183 item->mtime = info.st_mtime;
184 item->uid = info.st_uid;
185 item->gid = info.st_gid;
187 if (S_ISLNK(info.st_mode))
189 if (mc_stat(path, &info))
190 item->base_type = TYPE_ERROR;
191 else
192 item->base_type =
193 mode_to_base_type(info.st_mode);
195 item->flags |= ITEM_FLAG_SYMLINK;
197 else
199 item->base_type = mode_to_base_type(info.st_mode);
201 if (item->base_type == TYPE_DIRECTORY)
203 if (g_hash_table_lookup(mtab_mounts, path))
204 item->flags |= ITEM_FLAG_MOUNT_POINT
205 | ITEM_FLAG_MOUNTED;
206 else if (g_hash_table_lookup(fstab_mounts,
207 path))
208 item->flags |= ITEM_FLAG_MOUNT_POINT;
213 if (item->base_type == TYPE_DIRECTORY &&
214 !(item->flags & ITEM_FLAG_MOUNT_POINT))
216 uid_t uid = info.st_uid;
217 int path_len;
218 guchar *tmp;
220 /* Might be an application directory - better check...
221 * AppRun must have the same owner as the directory
222 * (to stop people putting an AppRun in, eg, /tmp)
224 path_len = strlen(path);
225 /* (sizeof(string) includes \0) */
226 tmp = g_malloc(path_len + sizeof("/AppIcon.xpm"));
227 sprintf(tmp, "%s/%s", path, "AppRun");
228 if (!mc_stat(tmp, &info) && info.st_uid == uid)
230 MaskedPixmap *app_icon;
232 item->flags |= ITEM_FLAG_APPDIR;
234 strcpy(tmp + path_len + 4, "Icon.xpm");
235 app_icon = g_fscache_lookup(pixmap_cache, tmp);
236 if (app_icon)
238 item->image = app_icon;
239 item->flags |= ITEM_FLAG_TEMP_ICON;
241 else
242 item->image = im_appdir;
244 g_free(tmp);
246 else if (item->base_type == TYPE_FILE)
248 /* Note: for symlinks we use need the mode of the target */
249 if (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
251 item->image = im_exec_file;
252 item->flags |= ITEM_FLAG_EXEC_FILE;
254 else
256 item->mime_type = type_from_path(path);
257 item->flags |= ITEM_FLAG_TEMP_ICON;
261 if (!item->mime_type)
262 item->mime_type = mime_type_from_base_type(item->base_type);
264 if (!item->image)
265 item->image = type_to_icon(item->mime_type);
268 /* Fill in the item structure with the appropriate details.
269 * 'leafname' field is set to NULL; text_width is unset.
271 void dir_stat(guchar *path, DirItem *item)
273 item->leafname = NULL;
274 item->may_delete = FALSE;
275 item->image = NULL;
277 dir_restat(path, item);
280 /* Frees all fields in the icon, but does not free the icon structure
281 * itself (because it might be part of a larger structure).
283 void dir_item_clear(DirItem *item)
285 g_return_if_fail(item != NULL);
287 if (item->flags & ITEM_FLAG_TEMP_ICON)
288 pixmap_unref(item->image);
289 g_free(item->leafname);
292 /* When something has happened to a particular object, call this
293 * and all appropriate changes will be made.
294 * Currently, we just extract the dirname and rescan...
296 void dir_check_this(guchar *path)
298 guchar *slash;
299 guchar *real_path;
301 real_path = pathdup(path);
303 slash = strrchr(real_path, '/');
305 if (slash)
307 *slash = '\0';
308 refresh_dirs(real_path);
311 g_free(real_path);
314 /****************************************************************
315 * INTERNAL FUNCTIONS *
316 ****************************************************************/
318 static void free_items_array(GPtrArray *array)
320 int i;
322 for (i = 0; i < array->len; i++)
324 DirItem *item = (DirItem *) array->pdata[i];
326 dir_item_clear(item);
327 g_free(item);
330 g_ptr_array_free(array, TRUE);
333 /* Scanning has finished. Remove all the old items that have gone.
334 * Notify everyone who is watching us of the removed items and tell
335 * them that the scan is over, unless 'needs_update'.
337 static void sweep_deleted(Directory *dir)
339 GPtrArray *array = dir->items;
340 int items = array->len;
341 int new_items = 0;
342 GPtrArray *old;
343 DirItem **from = (DirItem **) array->pdata;
344 DirItem **to = (DirItem **) array->pdata;
345 GList *list = dir->users;
347 old = g_ptr_array_new();
349 while (items--)
351 if (from[0]->may_delete)
353 g_ptr_array_add(old, *from);
354 from++;
355 continue;
358 if (from > to)
359 *to = *from;
360 to++;
361 from++;
362 new_items++;
365 g_ptr_array_set_size(array, new_items);
367 while (list)
369 DirUser *user = (DirUser *) list->data;
371 if (old->len)
372 user->callback(dir, DIR_REMOVE, old, user->data);
373 if (!dir->needs_update)
374 user->callback(dir, DIR_END_SCAN, NULL, user->data);
376 list = list->next;
379 free_items_array(old);
383 /* Add all the new items to the items array.
384 * Notify everyone who is watching us.
386 static void merge_new(Directory *dir)
388 GList *list = dir->users;
389 GPtrArray *new = dir->new_items;
390 GPtrArray *up = dir->up_items;
391 int i;
393 while (list)
395 DirUser *user = (DirUser *) list->data;
397 if (new->len)
398 user->callback(dir, DIR_ADD, new, user->data);
399 if (up->len)
400 user->callback(dir, DIR_UPDATE, up, user->data);
402 list = list->next;
406 for (i = 0; i < new->len; i++)
407 g_ptr_array_add(dir->items, new->pdata[i]);
408 qsort(dir->items->pdata, dir->items->len, sizeof(DirItem *),
409 dir_item_cmp);
411 g_ptr_array_set_size(new, 0);
412 g_ptr_array_set_size(up, 0);
415 static void init_for_scan(Directory *dir)
417 int i;
419 dir->needs_update = FALSE;
420 dir->done_some_scanning = FALSE;
422 for (i = 0; i < dir->items->len; i++)
423 ((DirItem *) dir->items->pdata[i])->may_delete = TRUE;
426 static void start_scanning(Directory *dir, char *pathname)
428 GList *next;
430 if (dir->dir_handle)
432 /* We are already scanning */
433 if (dir->done_some_scanning)
434 dir->needs_update = TRUE;
435 return;
438 mount_update(FALSE);
440 if (dir->error)
442 g_free(dir->error);
443 dir->error = NULL;
446 dir->dir_handle = mc_opendir(pathname);
448 if (!dir->dir_handle)
450 dir->error = g_strdup_printf(_("Can't open directory: %s"),
451 g_strerror(errno));
452 return; /* Report on attach */
455 dir->dir_start = mc_telldir(dir->dir_handle);
457 init_for_scan(dir);
459 dir->idle = gtk_idle_add((GtkFunction) idle_callback, dir);
461 /* Let all the filer windows know we're scanning */
462 for (next = dir->users; next; next = next->next)
464 DirUser *user = (DirUser *) next->data;
466 user->callback(dir, DIR_START_SCAN, NULL, user->data);
470 static gint notify_timeout(gpointer data)
472 Directory *dir = (Directory *) data;
474 g_return_val_if_fail(dir->notify_active == TRUE, FALSE);
476 merge_new(dir);
478 dir->notify_active = FALSE;
479 unref(dir, NULL);
481 return FALSE;
484 /* Call merge_new() after a while. */
485 static void delayed_notify(Directory *dir)
487 if (dir->notify_active)
488 return;
489 ref(dir, NULL);
490 gtk_timeout_add(500, notify_timeout, dir);
491 dir->notify_active = TRUE;
494 static void insert_item(Directory *dir, struct dirent *ent)
496 static GString *tmp = NULL;
498 GdkFont *font;
499 GPtrArray *array = dir->items;
500 DirItem *item;
501 int i;
502 DirItem new;
503 gboolean is_new = FALSE;
505 tmp = make_path(dir->pathname, ent->d_name);
506 dir_stat(tmp->str, &new);
508 /* Is an item with this name already listed? */
509 for (i = 0; i < array->len; i++)
511 item = (DirItem *) array->pdata[i];
513 if (strcmp(item->leafname, ent->d_name) == 0)
514 goto update;
517 item = g_new(DirItem, 1);
518 item->leafname = g_strdup(ent->d_name);
519 g_ptr_array_add(dir->new_items, item);
520 delayed_notify(dir);
521 is_new = TRUE;
522 update:
523 item->may_delete = FALSE;
525 font = gtk_widget_get_default_style()->font;
526 new.name_width = gdk_string_width(font, item->leafname);
527 new.leafname = item->leafname;
529 if (is_new == FALSE)
531 if (item->flags & ITEM_FLAG_TEMP_ICON)
532 pixmap_unref(item->image);
533 if (item->lstat_errno == new.lstat_errno
534 && item->base_type == new.base_type
535 && item->flags == new.flags
536 && item->size == new.size
537 && item->mode == new.mode
538 && item->mtime == new.mtime
539 && item->uid == new.uid
540 && item->gid == new.gid
541 && item->image == new.image
542 && item->mime_type == new.mime_type
543 && item->name_width == new.name_width)
544 return;
547 item->image = new.image;
548 item->lstat_errno = new.lstat_errno;
549 item->base_type = new.base_type;
550 item->flags = new.flags;
551 item->size = new.size;
552 item->mode = new.mode;
553 item->uid = new.uid;
554 item->gid = new.gid;
555 item->mtime = new.mtime;
556 item->mime_type = new.mime_type;
557 item->name_width = new.name_width;
559 if (!is_new)
561 g_ptr_array_add(dir->up_items, item);
562 delayed_notify(dir);
566 static gint idle_callback(Directory *dir)
568 struct dirent *ent;
570 dir->done_some_scanning = TRUE;
574 ent = mc_readdir(dir->dir_handle);
576 if (!ent)
578 merge_new(dir);
579 sweep_deleted(dir);
581 if (dir->needs_update)
583 mc_seekdir(dir->dir_handle, dir->dir_start);
584 init_for_scan(dir);
585 return TRUE;
588 mc_closedir(dir->dir_handle);
589 dir->dir_handle = NULL;
591 return FALSE; /* Stop */
594 insert_item(dir, ent);
596 } while (!gtk_events_pending());
598 return TRUE;
601 static Directory *load(char *pathname, gpointer data)
603 Directory *dir;
605 dir = g_new(Directory, 1);
606 dir->ref = 1;
607 dir->items = g_ptr_array_new();
608 dir->new_items = g_ptr_array_new();
609 dir->up_items = g_ptr_array_new();
610 dir->users = NULL;
611 dir->dir_handle = NULL;
612 dir->needs_update = TRUE;
613 dir->notify_active = FALSE;
614 dir->pathname = g_strdup(pathname);
615 dir->error = NULL;
617 return dir;
620 static void destroy(Directory *dir)
622 g_return_if_fail(dir->users == NULL);
624 g_print("[ destroy %p ]\n", dir);
625 if (dir->dir_handle)
627 mc_closedir(dir->dir_handle);
628 gtk_idle_remove(dir->idle);
630 g_ptr_array_free(dir->up_items, TRUE);
631 free_items_array(dir->items);
632 free_items_array(dir->new_items);
633 g_free(dir->error);
634 g_free(dir->pathname);
635 g_free(dir);
638 static void ref(Directory *dir, gpointer data)
640 dir->ref++;
643 static void unref(Directory *dir, gpointer data)
645 if (--dir->ref == 0)
646 destroy(dir);
649 static int getref(Directory *dir, gpointer data)
651 return dir->ref;
654 static void update(Directory *dir, gchar *pathname, gpointer data)
656 g_free(dir->pathname);
657 dir->pathname = g_strdup(pathname);
659 start_scanning(dir, pathname);
661 if (dir->error)
662 delayed_error(PROJECT, dir->error);