r156: Fixed some compiler warnings for systems with signed char types.
[rox-filer/ma.git] / ROX-Filer / src / dir.c
blobfa416e4ac019ec9e6668ca5e0e4a1f4377c9d3dd
1 /*
2 * $Id$
4 * dir.c - Caches and updates directories
5 * Copyright (C) 1999, 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)
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 <gtk/gtk.h>
23 #include <errno.h>
25 #include "support.h"
26 #include "gui_support.h"
27 #include "dir.h"
28 #include "fscache.h"
29 #include "mount.h"
30 #include "pixmaps.h"
31 #include "filer.h"
33 GFSCache *dir_cache = NULL;
35 /* Static prototypes */
36 static Directory *load(char *pathname, gpointer data);
37 static void ref(Directory *dir, gpointer data);
38 static void unref(Directory *dir, gpointer data);
39 static int getref(Directory *dir, gpointer data);
40 static void update(Directory *dir, gchar *pathname, gpointer data);
41 static void destroy(Directory *dir);
42 static void start_scanning(Directory *dir, char *pathname);
43 static gint idle_callback(Directory *dir);
44 static void init_for_scan(Directory *dir);
45 static void merge_new(Directory *dir);
48 /****************************************************************
49 * EXTERNAL INTERFACE *
50 ****************************************************************/
52 void dir_init(void)
54 dir_cache = g_fscache_new((GFSLoadFunc) load,
55 (GFSRefFunc) ref,
56 (GFSRefFunc) unref,
57 (GFSGetRefFunc) getref,
58 (GFSUpdateFunc) update, NULL);
61 /* Periodically calls callback to notify about changes to the contents
62 * of the directory.
63 * Before this function returns, it calls the callback once to add all
64 * the items currently in the directory (unless the dir is empty).
65 * If we are not scanning, it also calls update(DIR_END_SCAN).
67 void dir_attach(Directory *dir, DirCallback callback, gpointer data)
69 DirUser *user;
71 g_return_if_fail(dir != NULL);
72 g_return_if_fail(callback != NULL);
74 user = g_new(DirUser, 1);
75 user->callback = callback;
76 user->data = data;
78 dir->users = g_list_prepend(dir->users, user);
80 ref(dir, NULL);
82 if (dir->items->len)
83 callback(dir, DIR_ADD, dir->items, data);
85 if (dir->needs_update)
86 start_scanning(dir, dir->pathname);
88 if (dir->error)
89 delayed_error("ROX-Filer", dir->error);
91 if (!dir->dir_handle)
92 callback(dir, DIR_END_SCAN, NULL, data);
95 /* Undo the effect of dir_attach */
96 void dir_detach(Directory *dir, DirCallback callback, gpointer data)
98 DirUser *user;
99 GList *list = dir->users;
101 g_return_if_fail(dir != NULL);
102 g_return_if_fail(callback != NULL);
104 while (list)
106 user = (DirUser *) list->data;
107 if (user->callback == callback && user->data == data)
109 g_free(user);
110 dir->users = g_list_remove(dir->users, user);
111 unref(dir, NULL);
112 if (!dir->users)
114 if (dir->dir_handle)
116 closedir(dir->dir_handle);
117 dir->dir_handle = NULL;
118 gtk_idle_remove(dir->idle);
119 merge_new(dir);
120 dir->needs_update = TRUE;
123 return;
125 list = list->next;
128 g_warning("dir_detach: Callback/data pair not attached!\n");
131 void dir_update(Directory *dir, gchar *pathname)
133 update(dir, pathname, NULL);
136 int dir_item_cmp(const void *a, const void *b)
138 DirItem *aa = *((DirItem **) a);
139 DirItem *bb = *((DirItem **) b);
141 return strcmp(aa->leafname, bb->leafname);
144 void refresh_dirs(char *path)
146 g_fscache_update(dir_cache, path);
149 /****************************************************************
150 * INTERNAL FUNCTIONS *
151 ****************************************************************/
153 static void free_items_array(GPtrArray *array)
155 int i;
157 for (i = 0; i < array->len; i++)
159 DirItem *item = (DirItem *) array->pdata[i];
161 g_free(item->leafname);
162 g_free(item);
165 g_ptr_array_free(array, TRUE);
168 /* Scanning has finished. Remove all the old items that have gone.
169 * Notify everyone who is watching us of the removed items and tell
170 * them that the scan is over.
172 static void sweep_deleted(Directory *dir)
174 GPtrArray *array = dir->items;
175 int items = array->len;
176 int new_items = 0;
177 GPtrArray *old;
178 DirItem **from = (DirItem **) array->pdata;
179 DirItem **to = (DirItem **) array->pdata;
180 GList *list = dir->users;
182 old = g_ptr_array_new();
184 while (items--)
186 if (from[0]->may_delete)
188 g_ptr_array_add(old, *from);
189 from++;
190 continue;
193 if (from > to)
194 *to = *from;
195 to++;
196 from++;
197 new_items++;
200 g_ptr_array_set_size(array, new_items);
202 while (list)
204 DirUser *user = (DirUser *) list->data;
206 if (old->len)
207 user->callback(dir, DIR_REMOVE, old, user->data);
208 user->callback(dir, DIR_END_SCAN, NULL, user->data);
210 list = list->next;
213 free_items_array(old);
217 /* Add all the new items to the items array.
218 * Notify everyone who is watching us.
220 static void merge_new(Directory *dir)
222 GList *list = dir->users;
223 GPtrArray *new = dir->new_items;
224 GPtrArray *up = dir->up_items;
225 int i;
227 while (list)
229 DirUser *user = (DirUser *) list->data;
231 if (new->len)
232 user->callback(dir, DIR_ADD, new, user->data);
233 if (up->len)
234 user->callback(dir, DIR_UPDATE, up, user->data);
236 list = list->next;
240 for (i = 0; i < new->len; i++)
241 g_ptr_array_add(dir->items, new->pdata[i]);
242 qsort(dir->items->pdata, dir->items->len, sizeof(DirItem *),
243 dir_item_cmp);
245 g_ptr_array_set_size(new, 0);
246 g_ptr_array_set_size(up, 0);
249 static void init_for_scan(Directory *dir)
251 int i;
253 dir->needs_update = FALSE;
255 for (i = 0; i < dir->items->len; i++)
256 ((DirItem *) dir->items->pdata[i])->may_delete = TRUE;
259 static void start_scanning(Directory *dir, char *pathname)
261 if (dir->dir_handle)
263 dir->needs_update = TRUE;
264 return;
267 mount_update(FALSE);
269 if (dir->error)
271 g_free(dir->error);
272 dir->error = NULL;
275 dir->dir_handle = opendir(pathname);
277 if (!dir->dir_handle)
279 dir->error = g_strdup_printf("Can't open directory: %s",
280 g_strerror(errno));
281 return; /* Report on attach */
284 init_for_scan(dir);
286 dir->idle = gtk_idle_add((GtkFunction) idle_callback, dir);
289 static gint notify_timeout(gpointer data)
291 Directory *dir = (Directory *) data;
293 g_return_val_if_fail(dir->notify_active == TRUE, FALSE);
295 merge_new(dir);
297 dir->notify_active = FALSE;
298 unref(dir, NULL);
300 return FALSE;
303 /* Call merge_new() after a while. */
304 static void delayed_notify(Directory *dir)
306 if (dir->notify_active)
307 return;
308 ref(dir, NULL);
309 gtk_timeout_add(500, notify_timeout, dir);
310 dir->notify_active = TRUE;
313 static void insert_item(Directory *dir, struct dirent *ent)
315 static GString *tmp = NULL;
317 GdkFont *font;
318 GPtrArray *array = dir->items;
319 DirItem *item;
320 int i;
321 struct stat info;
322 DirItem new;
323 gboolean is_new = FALSE;
325 new.flags = 0;
326 new.mime_type = NULL;
327 new.image = NULL;
328 new.size = 0;
329 new.mode = 0;
330 new.mtime = 0;
332 tmp = make_path(dir->pathname, ent->d_name);
334 if (lstat(tmp->str, &info) == -1)
335 new.base_type = TYPE_ERROR;
336 else
338 new.size = info.st_size;
339 new.mode = info.st_mode;
340 new.mtime = info.st_mtime;
341 if (S_ISREG(info.st_mode))
342 new.base_type = TYPE_FILE;
343 else if (S_ISDIR(info.st_mode))
345 new.base_type = TYPE_DIRECTORY;
347 if (g_hash_table_lookup(mtab_mounts, tmp->str))
348 new.flags |= ITEM_FLAG_MOUNT_POINT
349 | ITEM_FLAG_MOUNTED;
350 else if (g_hash_table_lookup(fstab_mounts, tmp->str))
351 new.flags |= ITEM_FLAG_MOUNT_POINT;
353 else if (S_ISBLK(info.st_mode))
354 new.base_type = TYPE_BLOCK_DEVICE;
355 else if (S_ISCHR(info.st_mode))
356 new.base_type = TYPE_CHAR_DEVICE;
357 else if (S_ISFIFO(info.st_mode))
358 new.base_type = TYPE_PIPE;
359 else if (S_ISSOCK(info.st_mode))
360 new.base_type = TYPE_SOCKET;
361 else if (S_ISLNK(info.st_mode))
363 if (stat(tmp->str, &info))
364 new.base_type = TYPE_ERROR;
365 else
367 if (S_ISREG(info.st_mode))
368 new.base_type = TYPE_FILE;
369 else if (S_ISDIR(info.st_mode))
370 new.base_type = TYPE_DIRECTORY;
371 else if (S_ISBLK(info.st_mode))
372 new.base_type = TYPE_BLOCK_DEVICE;
373 else if (S_ISCHR(info.st_mode))
374 new.base_type = TYPE_CHAR_DEVICE;
375 else if (S_ISFIFO(info.st_mode))
376 new.base_type = TYPE_PIPE;
377 else if (S_ISSOCK(info.st_mode))
378 new.base_type = TYPE_SOCKET;
379 else
380 new.base_type = TYPE_UNKNOWN;
383 new.flags |= ITEM_FLAG_SYMLINK;
385 else
386 new.base_type = TYPE_UNKNOWN;
388 if (new.base_type == TYPE_DIRECTORY &&
389 !(new.flags & ITEM_FLAG_MOUNT_POINT))
391 uid_t uid = info.st_uid;
393 /* Might be an application directory - better check...
394 * AppRun must have the same owner as the directory
395 * (to stop people putting an AppRun in, eg, /tmp)
397 g_string_append(tmp, "/AppRun");
398 if (!stat(tmp->str, &info) && info.st_uid == uid)
400 MaskedPixmap *app_icon;
402 new.flags |= ITEM_FLAG_APPDIR;
404 g_string_truncate(tmp, tmp->len - 3);
405 g_string_append(tmp, "Icon.xpm");
406 app_icon = g_fscache_lookup(pixmap_cache, tmp->str);
407 if (app_icon)
409 new.image = app_icon;
410 new.flags |= ITEM_FLAG_TEMP_ICON;
412 else
413 new.image = default_pixmap + TYPE_APPDIR;
415 else
416 new.image = default_pixmap + TYPE_DIRECTORY;
418 else if (new.base_type == TYPE_FILE)
420 /* Note: for symlinks we use need the mode of the target */
421 if (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
423 new.image = default_pixmap + TYPE_EXEC_FILE;
424 new.flags |= ITEM_FLAG_EXEC_FILE;
426 else
428 new.mime_type = type_from_path(tmp->str);
429 new.image = type_to_icon(new.mime_type);
430 new.flags |= ITEM_FLAG_TEMP_ICON;
433 else
434 new.image = default_pixmap + new.base_type;
436 for (i = 0; i < array->len; i++)
438 item = (DirItem *) array->pdata[i];
440 if (strcmp(item->leafname, ent->d_name) == 0)
441 goto update;
444 item = g_new(DirItem, 1);
445 item->leafname = g_strdup(ent->d_name);
446 g_ptr_array_add(dir->new_items, item);
447 delayed_notify(dir);
448 is_new = TRUE;
449 update:
450 item->may_delete = FALSE;
452 font = gtk_widget_get_default_style()->font;
453 new.name_width = gdk_string_width(font, item->leafname);
454 new.leafname = item->leafname;
455 new.details_width = gdk_string_width(fixed_font, details(&new));
457 if (is_new == FALSE
458 && item->base_type == new.base_type
459 && item->flags == new.flags
460 && item->size == new.size
461 && item->mode == new.mode
462 && item->mtime == new.mtime
463 && item->image == new.image
464 && item->mime_type == new.mime_type
465 && item->name_width == new.name_width
466 && item->details_width == new.details_width)
467 return;
469 item->base_type = new.base_type;
470 item->flags = new.flags;
471 item->size = new.size;
472 item->mode = new.mode;
473 item->mtime = new.mtime;
474 item->image = new.image;
475 item->mime_type = new.mime_type;
476 item->name_width = new.name_width;
477 item->details_width = new.details_width;
479 if (!is_new)
481 g_ptr_array_add(dir->up_items, item);
482 delayed_notify(dir);
486 static gint idle_callback(Directory *dir)
488 struct dirent *ent;
492 ent = readdir(dir->dir_handle);
494 if (!ent)
496 merge_new(dir);
497 sweep_deleted(dir);
499 if (dir->needs_update)
501 rewinddir(dir->dir_handle);
502 init_for_scan(dir);
503 return TRUE;
506 closedir(dir->dir_handle);
507 dir->dir_handle = NULL;
509 return FALSE; /* Stop */
512 insert_item(dir, ent);
514 } while (!gtk_events_pending());
516 return TRUE;
519 static Directory *load(char *pathname, gpointer data)
521 Directory *dir;
523 dir = g_new(Directory, 1);
524 dir->ref = 1;
525 dir->items = g_ptr_array_new();
526 dir->new_items = g_ptr_array_new();
527 dir->up_items = g_ptr_array_new();
528 dir->users = NULL;
529 dir->dir_handle = NULL;
530 dir->needs_update = TRUE;
531 dir->notify_active = FALSE;
532 dir->pathname = g_strdup(pathname);
533 dir->error = NULL;
535 return dir;
538 static void destroy(Directory *dir)
540 g_return_if_fail(dir->users == NULL);
542 g_print("[ destroy %p ]\n", dir);
543 if (dir->dir_handle)
545 closedir(dir->dir_handle);
546 gtk_idle_remove(dir->idle);
548 g_ptr_array_free(dir->up_items, TRUE);
549 free_items_array(dir->items);
550 free_items_array(dir->new_items);
551 g_free(dir->error);
552 g_free(dir->pathname);
553 g_free(dir);
556 static void ref(Directory *dir, gpointer data)
558 dir->ref++;
561 static void unref(Directory *dir, gpointer data)
563 if (--dir->ref == 0)
564 destroy(dir);
567 static int getref(Directory *dir, gpointer data)
569 return dir->ref;
572 static void update(Directory *dir, gchar *pathname, gpointer data)
574 g_free(dir->pathname);
575 dir->pathname = g_strdup(pathname);
577 start_scanning(dir, pathname);
579 if (dir->error)
580 delayed_error("ROX-Filer", dir->error);