r210: Added 'Permissions' (chmod) feature.
[rox-filer.git] / ROX-Filer / src / dir.c
blobb80e4691e4fa24a60c561af2d6bf0652910f54b5
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 <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"
32 GFSCache *dir_cache = NULL;
34 /* Static prototypes */
35 static Directory *load(char *pathname, gpointer data);
36 static void ref(Directory *dir, gpointer data);
37 static void unref(Directory *dir, gpointer data);
38 static int getref(Directory *dir, gpointer data);
39 static void update(Directory *dir, gchar *pathname, gpointer data);
40 static void destroy(Directory *dir);
41 static void start_scanning(Directory *dir, char *pathname);
42 static gint idle_callback(Directory *dir);
43 static void init_for_scan(Directory *dir);
44 static void merge_new(Directory *dir);
47 /****************************************************************
48 * EXTERNAL INTERFACE *
49 ****************************************************************/
51 void dir_init(void)
53 dir_cache = g_fscache_new((GFSLoadFunc) load,
54 (GFSRefFunc) ref,
55 (GFSRefFunc) unref,
56 (GFSGetRefFunc) getref,
57 (GFSUpdateFunc) update, NULL);
60 /* Periodically calls callback to notify about changes to the contents
61 * of the directory.
62 * Before this function returns, it calls the callback once to add all
63 * the items currently in the directory (unless the dir is empty).
64 * If we are not scanning, it also calls update(DIR_END_SCAN).
66 void dir_attach(Directory *dir, DirCallback callback, gpointer data)
68 DirUser *user;
70 g_return_if_fail(dir != NULL);
71 g_return_if_fail(callback != NULL);
73 user = g_new(DirUser, 1);
74 user->callback = callback;
75 user->data = data;
77 dir->users = g_list_prepend(dir->users, user);
79 ref(dir, NULL);
81 if (dir->items->len)
82 callback(dir, DIR_ADD, dir->items, data);
84 if (dir->needs_update)
85 start_scanning(dir, dir->pathname);
87 if (dir->error)
88 delayed_error("ROX-Filer", dir->error);
90 if (!dir->dir_handle)
91 callback(dir, DIR_END_SCAN, NULL, data);
94 /* Undo the effect of dir_attach */
95 void dir_detach(Directory *dir, DirCallback callback, gpointer data)
97 DirUser *user;
98 GList *list = dir->users;
100 g_return_if_fail(dir != NULL);
101 g_return_if_fail(callback != NULL);
103 while (list)
105 user = (DirUser *) list->data;
106 if (user->callback == callback && user->data == data)
108 g_free(user);
109 dir->users = g_list_remove(dir->users, user);
110 unref(dir, NULL);
111 if (!dir->users)
113 if (dir->dir_handle)
115 closedir(dir->dir_handle);
116 dir->dir_handle = NULL;
117 gtk_idle_remove(dir->idle);
118 merge_new(dir);
119 dir->needs_update = TRUE;
122 return;
124 list = list->next;
127 g_warning("dir_detach: Callback/data pair not attached!\n");
130 void dir_update(Directory *dir, gchar *pathname)
132 update(dir, pathname, NULL);
135 int dir_item_cmp(const void *a, const void *b)
137 DirItem *aa = *((DirItem **) a);
138 DirItem *bb = *((DirItem **) b);
140 return strcmp(aa->leafname, bb->leafname);
143 void refresh_dirs(char *path)
145 g_fscache_update(dir_cache, path);
148 /****************************************************************
149 * INTERNAL FUNCTIONS *
150 ****************************************************************/
152 static void free_items_array(GPtrArray *array)
154 int i;
156 for (i = 0; i < array->len; i++)
158 DirItem *item = (DirItem *) array->pdata[i];
160 g_free(item->leafname);
161 g_free(item);
164 g_ptr_array_free(array, TRUE);
167 /* Scanning has finished. Remove all the old items that have gone.
168 * Notify everyone who is watching us of the removed items and tell
169 * them that the scan is over.
171 static void sweep_deleted(Directory *dir)
173 GPtrArray *array = dir->items;
174 int items = array->len;
175 int new_items = 0;
176 GPtrArray *old;
177 DirItem **from = (DirItem **) array->pdata;
178 DirItem **to = (DirItem **) array->pdata;
179 GList *list = dir->users;
181 old = g_ptr_array_new();
183 while (items--)
185 if (from[0]->may_delete)
187 g_ptr_array_add(old, *from);
188 from++;
189 continue;
192 if (from > to)
193 *to = *from;
194 to++;
195 from++;
196 new_items++;
199 g_ptr_array_set_size(array, new_items);
201 while (list)
203 DirUser *user = (DirUser *) list->data;
205 if (old->len)
206 user->callback(dir, DIR_REMOVE, old, user->data);
207 user->callback(dir, DIR_END_SCAN, NULL, user->data);
209 list = list->next;
212 free_items_array(old);
216 /* Add all the new items to the items array.
217 * Notify everyone who is watching us.
219 static void merge_new(Directory *dir)
221 GList *list = dir->users;
222 GPtrArray *new = dir->new_items;
223 GPtrArray *up = dir->up_items;
224 int i;
226 while (list)
228 DirUser *user = (DirUser *) list->data;
230 if (new->len)
231 user->callback(dir, DIR_ADD, new, user->data);
232 if (up->len)
233 user->callback(dir, DIR_UPDATE, up, user->data);
235 list = list->next;
239 for (i = 0; i < new->len; i++)
240 g_ptr_array_add(dir->items, new->pdata[i]);
241 qsort(dir->items->pdata, dir->items->len, sizeof(DirItem *),
242 dir_item_cmp);
244 g_ptr_array_set_size(new, 0);
245 g_ptr_array_set_size(up, 0);
248 static void init_for_scan(Directory *dir)
250 int i;
252 dir->needs_update = FALSE;
253 dir->done_some_scanning = 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 GList *next;
263 if (dir->dir_handle)
265 /* We are already scanning */
266 if (dir->done_some_scanning)
267 dir->needs_update = TRUE;
268 return;
271 mount_update(FALSE);
273 if (dir->error)
275 g_free(dir->error);
276 dir->error = NULL;
279 dir->dir_handle = opendir(pathname);
281 if (!dir->dir_handle)
283 dir->error = g_strdup_printf("Can't open directory: %s",
284 g_strerror(errno));
285 return; /* Report on attach */
288 init_for_scan(dir);
290 dir->idle = gtk_idle_add((GtkFunction) idle_callback, dir);
292 /* Let all the filer windows know we're scanning */
293 for (next = dir->users; next; next = next->next)
295 DirUser *user = (DirUser *) next->data;
297 user->callback(dir, DIR_START_SCAN, NULL, user->data);
301 static gint notify_timeout(gpointer data)
303 Directory *dir = (Directory *) data;
305 g_return_val_if_fail(dir->notify_active == TRUE, FALSE);
307 merge_new(dir);
309 dir->notify_active = FALSE;
310 unref(dir, NULL);
312 return FALSE;
315 /* Call merge_new() after a while. */
316 static void delayed_notify(Directory *dir)
318 if (dir->notify_active)
319 return;
320 ref(dir, NULL);
321 gtk_timeout_add(500, notify_timeout, dir);
322 dir->notify_active = TRUE;
325 static void insert_item(Directory *dir, struct dirent *ent)
327 static GString *tmp = NULL;
329 GdkFont *font;
330 GPtrArray *array = dir->items;
331 DirItem *item;
332 int i;
333 struct stat info;
334 DirItem new;
335 gboolean is_new = FALSE;
337 new.flags = 0;
338 new.mime_type = NULL;
339 new.image = NULL;
340 new.size = 0;
341 new.mode = 0;
342 new.mtime = 0;
344 tmp = make_path(dir->pathname, ent->d_name);
346 if (lstat(tmp->str, &info) == -1)
347 new.base_type = TYPE_ERROR;
348 else
350 new.size = info.st_size;
351 new.mode = info.st_mode;
352 new.mtime = info.st_mtime;
353 new.uid = info.st_uid;
354 new.gid = info.st_gid;
355 if (S_ISREG(info.st_mode))
356 new.base_type = TYPE_FILE;
357 else if (S_ISDIR(info.st_mode))
359 new.base_type = TYPE_DIRECTORY;
361 if (g_hash_table_lookup(mtab_mounts, tmp->str))
362 new.flags |= ITEM_FLAG_MOUNT_POINT
363 | ITEM_FLAG_MOUNTED;
364 else if (g_hash_table_lookup(fstab_mounts, tmp->str))
365 new.flags |= ITEM_FLAG_MOUNT_POINT;
367 else if (S_ISBLK(info.st_mode))
368 new.base_type = TYPE_BLOCK_DEVICE;
369 else if (S_ISCHR(info.st_mode))
370 new.base_type = TYPE_CHAR_DEVICE;
371 else if (S_ISFIFO(info.st_mode))
372 new.base_type = TYPE_PIPE;
373 else if (S_ISSOCK(info.st_mode))
374 new.base_type = TYPE_SOCKET;
375 else if (S_ISLNK(info.st_mode))
377 if (stat(tmp->str, &info))
378 new.base_type = TYPE_ERROR;
379 else
381 if (S_ISREG(info.st_mode))
382 new.base_type = TYPE_FILE;
383 else if (S_ISDIR(info.st_mode))
384 new.base_type = TYPE_DIRECTORY;
385 else if (S_ISBLK(info.st_mode))
386 new.base_type = TYPE_BLOCK_DEVICE;
387 else if (S_ISCHR(info.st_mode))
388 new.base_type = TYPE_CHAR_DEVICE;
389 else if (S_ISFIFO(info.st_mode))
390 new.base_type = TYPE_PIPE;
391 else if (S_ISSOCK(info.st_mode))
392 new.base_type = TYPE_SOCKET;
393 else
394 new.base_type = TYPE_UNKNOWN;
397 new.flags |= ITEM_FLAG_SYMLINK;
399 else
400 new.base_type = TYPE_UNKNOWN;
402 if (new.base_type == TYPE_DIRECTORY &&
403 !(new.flags & ITEM_FLAG_MOUNT_POINT))
405 uid_t uid = info.st_uid;
407 /* Might be an application directory - better check...
408 * AppRun must have the same owner as the directory
409 * (to stop people putting an AppRun in, eg, /tmp)
411 g_string_append(tmp, "/AppRun");
412 if (!stat(tmp->str, &info) && info.st_uid == uid)
414 MaskedPixmap *app_icon;
416 new.flags |= ITEM_FLAG_APPDIR;
418 g_string_truncate(tmp, tmp->len - 3);
419 g_string_append(tmp, "Icon.xpm");
420 app_icon = g_fscache_lookup(pixmap_cache, tmp->str);
421 if (app_icon)
423 new.image = app_icon;
424 new.flags |= ITEM_FLAG_TEMP_ICON;
426 else
427 new.image = default_pixmap + TYPE_APPDIR;
429 else
430 new.image = default_pixmap + TYPE_DIRECTORY;
432 else if (new.base_type == TYPE_FILE)
434 /* Note: for symlinks we use need the mode of the target */
435 if (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
437 new.image = default_pixmap + TYPE_EXEC_FILE;
438 new.flags |= ITEM_FLAG_EXEC_FILE;
440 else
442 new.mime_type = type_from_path(tmp->str);
443 new.image = type_to_icon(new.mime_type);
444 new.flags |= ITEM_FLAG_TEMP_ICON;
447 else
448 new.image = default_pixmap + new.base_type;
450 for (i = 0; i < array->len; i++)
452 item = (DirItem *) array->pdata[i];
454 if (strcmp(item->leafname, ent->d_name) == 0)
455 goto update;
458 item = g_new(DirItem, 1);
459 item->leafname = g_strdup(ent->d_name);
460 g_ptr_array_add(dir->new_items, item);
461 delayed_notify(dir);
462 is_new = TRUE;
463 update:
464 item->may_delete = FALSE;
466 font = gtk_widget_get_default_style()->font;
467 new.name_width = gdk_string_width(font, item->leafname);
468 new.leafname = item->leafname;
469 new.details_width = gdk_string_width(fixed_font, details(&new));
471 if (is_new == FALSE
472 && item->base_type == new.base_type
473 && item->flags == new.flags
474 && item->size == new.size
475 && item->mode == new.mode
476 && item->mtime == new.mtime
477 && item->uid == new.uid
478 && item->gid == new.gid
479 && item->image == new.image
480 && item->mime_type == new.mime_type
481 && item->name_width == new.name_width
482 && item->details_width == new.details_width)
483 return;
485 item->base_type = new.base_type;
486 item->flags = new.flags;
487 item->size = new.size;
488 item->mode = new.mode;
489 item->uid = new.uid;
490 item->gid = new.gid;
491 item->mtime = new.mtime;
492 item->image = new.image;
493 item->mime_type = new.mime_type;
494 item->name_width = new.name_width;
495 item->details_width = new.details_width;
497 if (!is_new)
499 g_ptr_array_add(dir->up_items, item);
500 delayed_notify(dir);
504 static gint idle_callback(Directory *dir)
506 struct dirent *ent;
508 dir->done_some_scanning = TRUE;
512 ent = readdir(dir->dir_handle);
514 if (!ent)
516 merge_new(dir);
517 sweep_deleted(dir);
519 if (dir->needs_update)
521 rewinddir(dir->dir_handle);
522 init_for_scan(dir);
523 return TRUE;
526 closedir(dir->dir_handle);
527 dir->dir_handle = NULL;
529 return FALSE; /* Stop */
532 insert_item(dir, ent);
534 } while (!gtk_events_pending());
536 return TRUE;
539 static Directory *load(char *pathname, gpointer data)
541 Directory *dir;
543 dir = g_new(Directory, 1);
544 dir->ref = 1;
545 dir->items = g_ptr_array_new();
546 dir->new_items = g_ptr_array_new();
547 dir->up_items = g_ptr_array_new();
548 dir->users = NULL;
549 dir->dir_handle = NULL;
550 dir->needs_update = TRUE;
551 dir->notify_active = FALSE;
552 dir->pathname = g_strdup(pathname);
553 dir->error = NULL;
555 return dir;
558 static void destroy(Directory *dir)
560 g_return_if_fail(dir->users == NULL);
562 g_print("[ destroy %p ]\n", dir);
563 if (dir->dir_handle)
565 closedir(dir->dir_handle);
566 gtk_idle_remove(dir->idle);
568 g_ptr_array_free(dir->up_items, TRUE);
569 free_items_array(dir->items);
570 free_items_array(dir->new_items);
571 g_free(dir->error);
572 g_free(dir->pathname);
573 g_free(dir);
576 static void ref(Directory *dir, gpointer data)
578 dir->ref++;
581 static void unref(Directory *dir, gpointer data)
583 if (--dir->ref == 0)
584 destroy(dir);
587 static int getref(Directory *dir, gpointer data)
589 return dir->ref;
592 static void update(Directory *dir, gchar *pathname, gpointer data)
594 g_free(dir->pathname);
595 dir->pathname = g_strdup(pathname);
597 start_scanning(dir, pathname);
599 if (dir->error)
600 delayed_error("ROX-Filer", dir->error);