r216: Check for copying a directory onto itself, as well as into itself.
[rox-filer.git] / ROX-Filer / src / dir.c
blob0c6767a37e03c3b08000bfb15866416e86aaf66a
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 "main.h"
28 #include "support.h"
29 #include "gui_support.h"
30 #include "dir.h"
31 #include "fscache.h"
32 #include "mount.h"
33 #include "pixmaps.h"
35 GFSCache *dir_cache = NULL;
37 /* Static prototypes */
38 static Directory *load(char *pathname, gpointer data);
39 static void ref(Directory *dir, gpointer data);
40 static void unref(Directory *dir, gpointer data);
41 static int getref(Directory *dir, gpointer data);
42 static void update(Directory *dir, gchar *pathname, gpointer data);
43 static void destroy(Directory *dir);
44 static void start_scanning(Directory *dir, char *pathname);
45 static gint idle_callback(Directory *dir);
46 static void init_for_scan(Directory *dir);
47 static void merge_new(Directory *dir);
50 /****************************************************************
51 * EXTERNAL INTERFACE *
52 ****************************************************************/
54 void dir_init(void)
56 dir_cache = g_fscache_new((GFSLoadFunc) load,
57 (GFSRefFunc) ref,
58 (GFSRefFunc) unref,
59 (GFSGetRefFunc) getref,
60 (GFSUpdateFunc) update, NULL);
63 /* Periodically calls callback to notify about changes to the contents
64 * of the directory.
65 * Before this function returns, it calls the callback once to add all
66 * the items currently in the directory (unless the dir is empty).
67 * If we are not scanning, it also calls update(DIR_END_SCAN).
69 void dir_attach(Directory *dir, DirCallback callback, gpointer data)
71 DirUser *user;
73 g_return_if_fail(dir != NULL);
74 g_return_if_fail(callback != NULL);
76 user = g_new(DirUser, 1);
77 user->callback = callback;
78 user->data = data;
80 dir->users = g_list_prepend(dir->users, user);
82 ref(dir, NULL);
84 if (dir->items->len)
85 callback(dir, DIR_ADD, dir->items, data);
87 if (dir->needs_update)
88 start_scanning(dir, dir->pathname);
90 if (dir->error)
91 delayed_error("ROX-Filer", dir->error);
93 if (!dir->dir_handle)
94 callback(dir, DIR_END_SCAN, NULL, data);
97 /* Undo the effect of dir_attach */
98 void dir_detach(Directory *dir, DirCallback callback, gpointer data)
100 DirUser *user;
101 GList *list = dir->users;
103 g_return_if_fail(dir != NULL);
104 g_return_if_fail(callback != NULL);
106 while (list)
108 user = (DirUser *) list->data;
109 if (user->callback == callback && user->data == data)
111 g_free(user);
112 dir->users = g_list_remove(dir->users, user);
113 unref(dir, NULL);
114 if (!dir->users)
116 if (dir->dir_handle)
118 mc_closedir(dir->dir_handle);
119 dir->dir_handle = NULL;
120 gtk_idle_remove(dir->idle);
121 merge_new(dir);
122 dir->needs_update = TRUE;
125 return;
127 list = list->next;
130 g_warning("dir_detach: Callback/data pair not attached!\n");
133 void dir_update(Directory *dir, gchar *pathname)
135 update(dir, pathname, NULL);
138 int dir_item_cmp(const void *a, const void *b)
140 DirItem *aa = *((DirItem **) a);
141 DirItem *bb = *((DirItem **) b);
143 return strcmp(aa->leafname, bb->leafname);
146 void refresh_dirs(char *path)
148 g_fscache_update(dir_cache, path);
151 /****************************************************************
152 * INTERNAL FUNCTIONS *
153 ****************************************************************/
155 static void free_items_array(GPtrArray *array)
157 int i;
159 for (i = 0; i < array->len; i++)
161 DirItem *item = (DirItem *) array->pdata[i];
163 g_free(item->leafname);
164 g_free(item);
167 g_ptr_array_free(array, TRUE);
170 /* Scanning has finished. Remove all the old items that have gone.
171 * Notify everyone who is watching us of the removed items and tell
172 * them that the scan is over.
174 static void sweep_deleted(Directory *dir)
176 GPtrArray *array = dir->items;
177 int items = array->len;
178 int new_items = 0;
179 GPtrArray *old;
180 DirItem **from = (DirItem **) array->pdata;
181 DirItem **to = (DirItem **) array->pdata;
182 GList *list = dir->users;
184 old = g_ptr_array_new();
186 while (items--)
188 if (from[0]->may_delete)
190 g_ptr_array_add(old, *from);
191 from++;
192 continue;
195 if (from > to)
196 *to = *from;
197 to++;
198 from++;
199 new_items++;
202 g_ptr_array_set_size(array, new_items);
204 while (list)
206 DirUser *user = (DirUser *) list->data;
208 if (old->len)
209 user->callback(dir, DIR_REMOVE, old, user->data);
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 = 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.base_type = TYPE_ERROR;
351 new.size = 0;
352 new.mode = 0;
353 new.mtime = 0;
354 new.uid = noaccess_uid;
355 new.gid = noaccess_gid;
357 else
359 new.size = info.st_size;
360 new.mode = info.st_mode;
361 new.mtime = info.st_mtime;
362 new.uid = info.st_uid;
363 new.gid = info.st_gid;
364 if (S_ISREG(info.st_mode))
365 new.base_type = TYPE_FILE;
366 else if (S_ISDIR(info.st_mode))
368 new.base_type = TYPE_DIRECTORY;
370 if (g_hash_table_lookup(mtab_mounts, tmp->str))
371 new.flags |= ITEM_FLAG_MOUNT_POINT
372 | ITEM_FLAG_MOUNTED;
373 else if (g_hash_table_lookup(fstab_mounts, tmp->str))
374 new.flags |= ITEM_FLAG_MOUNT_POINT;
376 else if (S_ISBLK(info.st_mode))
377 new.base_type = TYPE_BLOCK_DEVICE;
378 else if (S_ISCHR(info.st_mode))
379 new.base_type = TYPE_CHAR_DEVICE;
380 else if (S_ISFIFO(info.st_mode))
381 new.base_type = TYPE_PIPE;
382 else if (S_ISSOCK(info.st_mode))
383 new.base_type = TYPE_SOCKET;
384 else if (S_ISLNK(info.st_mode))
386 if (mc_stat(tmp->str, &info))
387 new.base_type = TYPE_ERROR;
388 else
390 if (S_ISREG(info.st_mode))
391 new.base_type = TYPE_FILE;
392 else if (S_ISDIR(info.st_mode))
393 new.base_type = TYPE_DIRECTORY;
394 else if (S_ISBLK(info.st_mode))
395 new.base_type = TYPE_BLOCK_DEVICE;
396 else if (S_ISCHR(info.st_mode))
397 new.base_type = TYPE_CHAR_DEVICE;
398 else if (S_ISFIFO(info.st_mode))
399 new.base_type = TYPE_PIPE;
400 else if (S_ISSOCK(info.st_mode))
401 new.base_type = TYPE_SOCKET;
402 else
403 new.base_type = TYPE_UNKNOWN;
406 new.flags |= ITEM_FLAG_SYMLINK;
408 else
409 new.base_type = TYPE_UNKNOWN;
411 if (new.base_type == TYPE_DIRECTORY &&
412 !(new.flags & ITEM_FLAG_MOUNT_POINT))
414 uid_t uid = info.st_uid;
416 /* Might be an application directory - better check...
417 * AppRun must have the same owner as the directory
418 * (to stop people putting an AppRun in, eg, /tmp)
420 g_string_append(tmp, "/AppRun");
421 if (!mc_stat(tmp->str, &info) && info.st_uid == uid)
423 MaskedPixmap *app_icon;
425 new.flags |= ITEM_FLAG_APPDIR;
427 g_string_truncate(tmp, tmp->len - 3);
428 g_string_append(tmp, "Icon.xpm");
429 app_icon = g_fscache_lookup(pixmap_cache, tmp->str);
430 if (app_icon)
432 new.image = app_icon;
433 new.flags |= ITEM_FLAG_TEMP_ICON;
435 else
436 new.image = default_pixmap + TYPE_APPDIR;
438 else
439 new.image = default_pixmap + TYPE_DIRECTORY;
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 = default_pixmap + TYPE_EXEC_FILE;
447 new.flags |= ITEM_FLAG_EXEC_FILE;
449 else
451 new.mime_type = type_from_path(tmp->str);
452 new.image = type_to_icon(new.mime_type);
453 new.flags |= ITEM_FLAG_TEMP_ICON;
456 else
457 new.image = default_pixmap + new.base_type;
459 for (i = 0; i < array->len; i++)
461 item = (DirItem *) array->pdata[i];
463 if (strcmp(item->leafname, ent->d_name) == 0)
464 goto update;
467 item = g_new(DirItem, 1);
468 item->leafname = g_strdup(ent->d_name);
469 g_ptr_array_add(dir->new_items, item);
470 delayed_notify(dir);
471 is_new = TRUE;
472 update:
473 item->may_delete = FALSE;
475 font = gtk_widget_get_default_style()->font;
476 new.name_width = gdk_string_width(font, item->leafname);
477 new.leafname = item->leafname;
478 new.details_width = gdk_string_width(fixed_font, details(&new));
480 if (is_new == FALSE
481 && item->base_type == new.base_type
482 && item->flags == new.flags
483 && item->size == new.size
484 && item->mode == new.mode
485 && item->mtime == new.mtime
486 && item->uid == new.uid
487 && item->gid == new.gid
488 && item->image == new.image
489 && item->mime_type == new.mime_type
490 && item->name_width == new.name_width
491 && item->details_width == new.details_width)
492 return;
494 item->base_type = new.base_type;
495 item->flags = new.flags;
496 item->size = new.size;
497 item->mode = new.mode;
498 item->uid = new.uid;
499 item->gid = new.gid;
500 item->mtime = new.mtime;
501 item->image = new.image;
502 item->mime_type = new.mime_type;
503 item->name_width = new.name_width;
504 item->details_width = new.details_width;
506 if (!is_new)
508 g_ptr_array_add(dir->up_items, item);
509 delayed_notify(dir);
513 static gint idle_callback(Directory *dir)
515 struct dirent *ent;
517 dir->done_some_scanning = TRUE;
521 ent = mc_readdir(dir->dir_handle);
523 if (!ent)
525 merge_new(dir);
526 sweep_deleted(dir);
528 if (dir->needs_update)
530 mc_seekdir(dir->dir_handle, dir->dir_start);
531 init_for_scan(dir);
532 return TRUE;
535 mc_closedir(dir->dir_handle);
536 dir->dir_handle = NULL;
538 return FALSE; /* Stop */
541 insert_item(dir, ent);
543 } while (!gtk_events_pending());
545 return TRUE;
548 static Directory *load(char *pathname, gpointer data)
550 Directory *dir;
552 dir = g_new(Directory, 1);
553 dir->ref = 1;
554 dir->items = g_ptr_array_new();
555 dir->new_items = g_ptr_array_new();
556 dir->up_items = g_ptr_array_new();
557 dir->users = NULL;
558 dir->dir_handle = NULL;
559 dir->needs_update = TRUE;
560 dir->notify_active = FALSE;
561 dir->pathname = g_strdup(pathname);
562 dir->error = NULL;
564 return dir;
567 static void destroy(Directory *dir)
569 g_return_if_fail(dir->users == NULL);
571 g_print("[ destroy %p ]\n", dir);
572 if (dir->dir_handle)
574 mc_closedir(dir->dir_handle);
575 gtk_idle_remove(dir->idle);
577 g_ptr_array_free(dir->up_items, TRUE);
578 free_items_array(dir->items);
579 free_items_array(dir->new_items);
580 g_free(dir->error);
581 g_free(dir->pathname);
582 g_free(dir);
585 static void ref(Directory *dir, gpointer data)
587 dir->ref++;
590 static void unref(Directory *dir, gpointer data)
592 if (--dir->ref == 0)
593 destroy(dir);
596 static int getref(Directory *dir, gpointer data)
598 return dir->ref;
601 static void update(Directory *dir, gchar *pathname, gpointer data)
603 g_free(dir->pathname);
604 dir->pathname = g_strdup(pathname);
606 start_scanning(dir, pathname);
608 if (dir->error)
609 delayed_error("ROX-Filer", dir->error);