r287: Removed the DEFAULT_PIXMAP array - the new type system makes in unnecessary
[rox-filer.git] / ROX-Filer / src / dir.c
blob8b4ffef00df5aa694089735a6bd054a032b55b4d
1 /*
2 * $Id$
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)
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>
27 #include "support.h"
28 #include "gui_support.h"
29 #include "dir.h"
30 #include "fscache.h"
31 #include "mount.h"
32 #include "pixmaps.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 ****************************************************************/
53 void dir_init(void)
55 dir_cache = g_fscache_new((GFSLoadFunc) load,
56 (GFSRefFunc) ref,
57 (GFSRefFunc) unref,
58 (GFSGetRefFunc) getref,
59 (GFSUpdateFunc) update, NULL);
62 /* Periodically calls callback to notify about changes to the contents
63 * of the directory.
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)
70 DirUser *user;
72 g_return_if_fail(dir != NULL);
73 g_return_if_fail(callback != NULL);
75 user = g_new(DirUser, 1);
76 user->callback = callback;
77 user->data = data;
79 dir->users = g_list_prepend(dir->users, user);
81 ref(dir, NULL);
83 if (dir->items->len)
84 callback(dir, DIR_ADD, dir->items, data);
86 if (dir->needs_update)
87 start_scanning(dir, dir->pathname);
89 if (dir->error)
90 delayed_error(PROJECT, dir->error);
92 if (!dir->dir_handle)
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)
99 DirUser *user;
100 GList *list = dir->users;
102 g_return_if_fail(dir != NULL);
103 g_return_if_fail(callback != NULL);
105 while (list)
107 user = (DirUser *) list->data;
108 if (user->callback == callback && user->data == data)
110 g_free(user);
111 dir->users = g_list_remove(dir->users, user);
112 unref(dir, NULL);
113 if (!dir->users)
115 if (dir->dir_handle)
117 mc_closedir(dir->dir_handle);
118 dir->dir_handle = NULL;
119 gtk_idle_remove(dir->idle);
120 merge_new(dir);
121 dir->needs_update = TRUE;
124 return;
126 list = list->next;
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)
156 int i;
158 for (i = 0; i < array->len; i++)
160 DirItem *item = (DirItem *) array->pdata[i];
162 g_free(item->leafname);
163 g_free(item);
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;
177 int new_items = 0;
178 GPtrArray *old;
179 DirItem **from = (DirItem **) array->pdata;
180 DirItem **to = (DirItem **) array->pdata;
181 GList *list = dir->users;
183 old = g_ptr_array_new();
185 while (items--)
187 if (from[0]->may_delete)
189 g_ptr_array_add(old, *from);
190 from++;
191 continue;
194 if (from > to)
195 *to = *from;
196 to++;
197 from++;
198 new_items++;
201 g_ptr_array_set_size(array, new_items);
203 while (list)
205 DirUser *user = (DirUser *) list->data;
207 if (old->len)
208 user->callback(dir, DIR_REMOVE, old, user->data);
209 if (!dir->needs_update)
210 user->callback(dir, DIR_END_SCAN, NULL, user->data);
212 list = list->next;
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;
227 int i;
229 while (list)
231 DirUser *user = (DirUser *) list->data;
233 if (new->len)
234 user->callback(dir, DIR_ADD, new, user->data);
235 if (up->len)
236 user->callback(dir, DIR_UPDATE, up, user->data);
238 list = list->next;
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 *),
245 dir_item_cmp);
247 g_ptr_array_set_size(new, 0);
248 g_ptr_array_set_size(up, 0);
251 static void init_for_scan(Directory *dir)
253 int i;
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)
264 GList *next;
266 if (dir->dir_handle)
268 /* We are already scanning */
269 if (dir->done_some_scanning)
270 dir->needs_update = TRUE;
271 return;
274 mount_update(FALSE);
276 if (dir->error)
278 g_free(dir->error);
279 dir->error = NULL;
282 dir->dir_handle = mc_opendir(pathname);
284 if (!dir->dir_handle)
286 dir->error = g_strdup_printf(_("Can't open directory: %s"),
287 g_strerror(errno));
288 return; /* Report on attach */
291 dir->dir_start = mc_telldir(dir->dir_handle);
293 init_for_scan(dir);
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);
312 merge_new(dir);
314 dir->notify_active = FALSE;
315 unref(dir, NULL);
317 return FALSE;
320 /* Call merge_new() after a while. */
321 static void delayed_notify(Directory *dir)
323 if (dir->notify_active)
324 return;
325 ref(dir, NULL);
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;
334 GdkFont *font;
335 GPtrArray *array = dir->items;
336 DirItem *item;
337 int i;
338 struct stat info;
339 DirItem new;
340 gboolean is_new = FALSE;
342 new.flags = 0;
343 new.mime_type = NULL;
344 new.image = 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;
352 new.size = 0;
353 new.mode = 0;
354 new.mtime = 0;
355 new.uid = (uid_t) -1;
356 new.gid = (gid_t) -1;
358 else
360 new.lstat_errno = 0;
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
374 | ITEM_FLAG_MOUNTED;
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;
390 else
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;
404 else
405 new.base_type = TYPE_UNKNOWN;
408 new.flags |= ITEM_FLAG_SYMLINK;
410 else
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);
432 if (app_icon)
434 new.image = app_icon;
435 new.flags |= ITEM_FLAG_TEMP_ICON;
437 else
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;
449 else
451 new.mime_type = type_from_path(tmp->str);
452 new.flags |= ITEM_FLAG_TEMP_ICON;
456 if (!new.mime_type)
457 new.mime_type = mime_type_from_base_type(new.base_type);
459 if (!new.image)
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)
467 goto update;
470 item = g_new(DirItem, 1);
471 item->leafname = g_strdup(ent->d_name);
472 g_ptr_array_add(dir->new_items, item);
473 delayed_notify(dir);
474 is_new = TRUE;
475 update:
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));
483 if (is_new == FALSE)
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)
499 return;
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;
508 item->uid = new.uid;
509 item->gid = new.gid;
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;
515 if (!is_new)
517 g_ptr_array_add(dir->up_items, item);
518 delayed_notify(dir);
522 static gint idle_callback(Directory *dir)
524 struct dirent *ent;
526 dir->done_some_scanning = TRUE;
530 ent = mc_readdir(dir->dir_handle);
532 if (!ent)
534 merge_new(dir);
535 sweep_deleted(dir);
537 if (dir->needs_update)
539 mc_seekdir(dir->dir_handle, dir->dir_start);
540 init_for_scan(dir);
541 return TRUE;
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());
554 return TRUE;
557 static Directory *load(char *pathname, gpointer data)
559 Directory *dir;
561 dir = g_new(Directory, 1);
562 dir->ref = 1;
563 dir->items = g_ptr_array_new();
564 dir->new_items = g_ptr_array_new();
565 dir->up_items = g_ptr_array_new();
566 dir->users = NULL;
567 dir->dir_handle = NULL;
568 dir->needs_update = TRUE;
569 dir->notify_active = FALSE;
570 dir->pathname = g_strdup(pathname);
571 dir->error = NULL;
573 return dir;
576 static void destroy(Directory *dir)
578 g_return_if_fail(dir->users == NULL);
580 g_print("[ destroy %p ]\n", dir);
581 if (dir->dir_handle)
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);
589 g_free(dir->error);
590 g_free(dir->pathname);
591 g_free(dir);
594 static void ref(Directory *dir, gpointer data)
596 dir->ref++;
599 static void unref(Directory *dir, gpointer data)
601 if (--dir->ref == 0)
602 destroy(dir);
605 static int getref(Directory *dir, gpointer data)
607 return dir->ref;
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);
617 if (dir->error)
618 delayed_error(PROJECT, dir->error);