r302: Added a choice of details (Summary and Sizes).
[rox-filer.git] / ROX-Filer / src / dir.c
blob933f3ebe7d918747220d19c325049d61567e1b52
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;
482 if (is_new == FALSE)
484 if (item->flags & ITEM_FLAG_TEMP_ICON)
485 pixmap_unref(item->image);
486 if (item->lstat_errno == new.lstat_errno
487 && item->base_type == new.base_type
488 && item->flags == new.flags
489 && item->size == new.size
490 && item->mode == new.mode
491 && item->mtime == new.mtime
492 && item->uid == new.uid
493 && item->gid == new.gid
494 && item->image == new.image
495 && item->mime_type == new.mime_type
496 && item->name_width == new.name_width)
497 return;
500 item->image = new.image;
501 item->lstat_errno = new.lstat_errno;
502 item->base_type = new.base_type;
503 item->flags = new.flags;
504 item->size = new.size;
505 item->mode = new.mode;
506 item->uid = new.uid;
507 item->gid = new.gid;
508 item->mtime = new.mtime;
509 item->mime_type = new.mime_type;
510 item->name_width = new.name_width;
512 if (!is_new)
514 g_ptr_array_add(dir->up_items, item);
515 delayed_notify(dir);
519 static gint idle_callback(Directory *dir)
521 struct dirent *ent;
523 dir->done_some_scanning = TRUE;
527 ent = mc_readdir(dir->dir_handle);
529 if (!ent)
531 merge_new(dir);
532 sweep_deleted(dir);
534 if (dir->needs_update)
536 mc_seekdir(dir->dir_handle, dir->dir_start);
537 init_for_scan(dir);
538 return TRUE;
541 mc_closedir(dir->dir_handle);
542 dir->dir_handle = NULL;
544 return FALSE; /* Stop */
547 insert_item(dir, ent);
549 } while (!gtk_events_pending());
551 return TRUE;
554 static Directory *load(char *pathname, gpointer data)
556 Directory *dir;
558 dir = g_new(Directory, 1);
559 dir->ref = 1;
560 dir->items = g_ptr_array_new();
561 dir->new_items = g_ptr_array_new();
562 dir->up_items = g_ptr_array_new();
563 dir->users = NULL;
564 dir->dir_handle = NULL;
565 dir->needs_update = TRUE;
566 dir->notify_active = FALSE;
567 dir->pathname = g_strdup(pathname);
568 dir->error = NULL;
570 return dir;
573 static void destroy(Directory *dir)
575 g_return_if_fail(dir->users == NULL);
577 g_print("[ destroy %p ]\n", dir);
578 if (dir->dir_handle)
580 mc_closedir(dir->dir_handle);
581 gtk_idle_remove(dir->idle);
583 g_ptr_array_free(dir->up_items, TRUE);
584 free_items_array(dir->items);
585 free_items_array(dir->new_items);
586 g_free(dir->error);
587 g_free(dir->pathname);
588 g_free(dir);
591 static void ref(Directory *dir, gpointer data)
593 dir->ref++;
596 static void unref(Directory *dir, gpointer data)
598 if (--dir->ref == 0)
599 destroy(dir);
602 static int getref(Directory *dir, gpointer data)
604 return dir->ref;
607 static void update(Directory *dir, gchar *pathname, gpointer data)
609 g_free(dir->pathname);
610 dir->pathname = g_strdup(pathname);
612 start_scanning(dir, pathname);
614 if (dir->error)
615 delayed_error(PROJECT, dir->error);