r246: Added support for i18n. No translations yet, though!
[rox-filer/ma.git] / ROX-Filer / src / dir.c
blob4b46506a083f7c0d566aa431a11c1b5b23b35a19
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("ROX-Filer", 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.
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 user->callback(dir, DIR_END_SCAN, NULL, user->data);
211 list = list->next;
214 free_items_array(old);
218 /* Add all the new items to the items array.
219 * Notify everyone who is watching us.
221 static void merge_new(Directory *dir)
223 GList *list = dir->users;
224 GPtrArray *new = dir->new_items;
225 GPtrArray *up = dir->up_items;
226 int i;
228 while (list)
230 DirUser *user = (DirUser *) list->data;
232 if (new->len)
233 user->callback(dir, DIR_ADD, new, user->data);
234 if (up->len)
235 user->callback(dir, DIR_UPDATE, up, user->data);
237 list = list->next;
241 for (i = 0; i < new->len; i++)
242 g_ptr_array_add(dir->items, new->pdata[i]);
243 qsort(dir->items->pdata, dir->items->len, sizeof(DirItem *),
244 dir_item_cmp);
246 g_ptr_array_set_size(new, 0);
247 g_ptr_array_set_size(up, 0);
250 static void init_for_scan(Directory *dir)
252 int i;
254 dir->needs_update = FALSE;
255 dir->done_some_scanning = FALSE;
257 for (i = 0; i < dir->items->len; i++)
258 ((DirItem *) dir->items->pdata[i])->may_delete = TRUE;
261 static void start_scanning(Directory *dir, char *pathname)
263 GList *next;
265 if (dir->dir_handle)
267 /* We are already scanning */
268 if (dir->done_some_scanning)
269 dir->needs_update = TRUE;
270 return;
273 mount_update(FALSE);
275 if (dir->error)
277 g_free(dir->error);
278 dir->error = NULL;
281 dir->dir_handle = mc_opendir(pathname);
283 if (!dir->dir_handle)
285 dir->error = g_strdup_printf("Can't open directory: %s",
286 g_strerror(errno));
287 return; /* Report on attach */
290 dir->dir_start = telldir(dir->dir_handle);
292 init_for_scan(dir);
294 dir->idle = gtk_idle_add((GtkFunction) idle_callback, dir);
296 /* Let all the filer windows know we're scanning */
297 for (next = dir->users; next; next = next->next)
299 DirUser *user = (DirUser *) next->data;
301 user->callback(dir, DIR_START_SCAN, NULL, user->data);
305 static gint notify_timeout(gpointer data)
307 Directory *dir = (Directory *) data;
309 g_return_val_if_fail(dir->notify_active == TRUE, FALSE);
311 merge_new(dir);
313 dir->notify_active = FALSE;
314 unref(dir, NULL);
316 return FALSE;
319 /* Call merge_new() after a while. */
320 static void delayed_notify(Directory *dir)
322 if (dir->notify_active)
323 return;
324 ref(dir, NULL);
325 gtk_timeout_add(500, notify_timeout, dir);
326 dir->notify_active = TRUE;
329 static void insert_item(Directory *dir, struct dirent *ent)
331 static GString *tmp = NULL;
333 GdkFont *font;
334 GPtrArray *array = dir->items;
335 DirItem *item;
336 int i;
337 struct stat info;
338 DirItem new;
339 gboolean is_new = FALSE;
341 new.flags = 0;
342 new.mime_type = NULL;
343 new.image = NULL;
345 tmp = make_path(dir->pathname, ent->d_name);
347 if (mc_lstat(tmp->str, &info) == -1)
349 new.lstat_errno = errno;
350 new.base_type = TYPE_ERROR;
351 new.size = 0;
352 new.mode = 0;
353 new.mtime = 0;
354 new.uid = (uid_t) -1;
355 new.gid = (gid_t) -1;
357 else
359 new.lstat_errno = 0;
360 new.size = info.st_size;
361 new.mode = info.st_mode;
362 new.mtime = info.st_mtime;
363 new.uid = info.st_uid;
364 new.gid = info.st_gid;
365 if (S_ISREG(info.st_mode))
366 new.base_type = TYPE_FILE;
367 else if (S_ISDIR(info.st_mode))
369 new.base_type = TYPE_DIRECTORY;
371 if (g_hash_table_lookup(mtab_mounts, tmp->str))
372 new.flags |= ITEM_FLAG_MOUNT_POINT
373 | ITEM_FLAG_MOUNTED;
374 else if (g_hash_table_lookup(fstab_mounts, tmp->str))
375 new.flags |= ITEM_FLAG_MOUNT_POINT;
377 else if (S_ISBLK(info.st_mode))
378 new.base_type = TYPE_BLOCK_DEVICE;
379 else if (S_ISCHR(info.st_mode))
380 new.base_type = TYPE_CHAR_DEVICE;
381 else if (S_ISFIFO(info.st_mode))
382 new.base_type = TYPE_PIPE;
383 else if (S_ISSOCK(info.st_mode))
384 new.base_type = TYPE_SOCKET;
385 else if (S_ISLNK(info.st_mode))
387 if (mc_stat(tmp->str, &info))
388 new.base_type = TYPE_ERROR;
389 else
391 if (S_ISREG(info.st_mode))
392 new.base_type = TYPE_FILE;
393 else if (S_ISDIR(info.st_mode))
394 new.base_type = TYPE_DIRECTORY;
395 else if (S_ISBLK(info.st_mode))
396 new.base_type = TYPE_BLOCK_DEVICE;
397 else if (S_ISCHR(info.st_mode))
398 new.base_type = TYPE_CHAR_DEVICE;
399 else if (S_ISFIFO(info.st_mode))
400 new.base_type = TYPE_PIPE;
401 else if (S_ISSOCK(info.st_mode))
402 new.base_type = TYPE_SOCKET;
403 else
404 new.base_type = TYPE_UNKNOWN;
407 new.flags |= ITEM_FLAG_SYMLINK;
409 else
410 new.base_type = TYPE_UNKNOWN;
412 if (new.base_type == TYPE_DIRECTORY &&
413 !(new.flags & ITEM_FLAG_MOUNT_POINT))
415 uid_t uid = info.st_uid;
417 /* Might be an application directory - better check...
418 * AppRun must have the same owner as the directory
419 * (to stop people putting an AppRun in, eg, /tmp)
421 g_string_append(tmp, "/AppRun");
422 if (!mc_stat(tmp->str, &info) && info.st_uid == uid)
424 MaskedPixmap *app_icon;
426 new.flags |= ITEM_FLAG_APPDIR;
428 g_string_truncate(tmp, tmp->len - 3);
429 g_string_append(tmp, "Icon.xpm");
430 app_icon = g_fscache_lookup(pixmap_cache, tmp->str);
431 if (app_icon)
433 new.image = app_icon;
434 new.flags |= ITEM_FLAG_TEMP_ICON;
436 else
437 new.image = default_pixmap[TYPE_APPDIR];
439 else
440 new.image = default_pixmap[TYPE_DIRECTORY];
442 else if (new.base_type == TYPE_FILE)
444 /* Note: for symlinks we use need the mode of the target */
445 if (info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
447 new.image = default_pixmap[TYPE_EXEC_FILE];
448 new.flags |= ITEM_FLAG_EXEC_FILE;
450 else
452 new.mime_type = type_from_path(tmp->str);
453 new.image = type_to_icon(new.mime_type);
454 new.flags |= ITEM_FLAG_TEMP_ICON;
457 else
458 new.image = default_pixmap[new.base_type];
460 for (i = 0; i < array->len; i++)
462 item = (DirItem *) array->pdata[i];
464 if (strcmp(item->leafname, ent->d_name) == 0)
465 goto update;
468 item = g_new(DirItem, 1);
469 item->leafname = g_strdup(ent->d_name);
470 g_ptr_array_add(dir->new_items, item);
471 delayed_notify(dir);
472 is_new = TRUE;
473 update:
474 item->may_delete = FALSE;
476 font = gtk_widget_get_default_style()->font;
477 new.name_width = gdk_string_width(font, item->leafname);
478 new.leafname = item->leafname;
479 new.details_width = gdk_string_width(fixed_font, details(&new));
481 if (is_new == FALSE)
483 if (item->flags & ITEM_FLAG_TEMP_ICON)
484 pixmap_unref(item->image);
485 if (item->lstat_errno == new.lstat_errno
486 && item->base_type == new.base_type
487 && item->flags == new.flags
488 && item->size == new.size
489 && item->mode == new.mode
490 && item->mtime == new.mtime
491 && item->uid == new.uid
492 && item->gid == new.gid
493 && item->image == new.image
494 && item->mime_type == new.mime_type
495 && item->name_width == new.name_width
496 && item->details_width == new.details_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;
511 item->details_width = new.details_width;
513 if (!is_new)
515 g_ptr_array_add(dir->up_items, item);
516 delayed_notify(dir);
520 static gint idle_callback(Directory *dir)
522 struct dirent *ent;
524 dir->done_some_scanning = TRUE;
528 ent = mc_readdir(dir->dir_handle);
530 if (!ent)
532 merge_new(dir);
533 sweep_deleted(dir);
535 if (dir->needs_update)
537 mc_seekdir(dir->dir_handle, dir->dir_start);
538 init_for_scan(dir);
539 return TRUE;
542 mc_closedir(dir->dir_handle);
543 dir->dir_handle = NULL;
545 return FALSE; /* Stop */
548 insert_item(dir, ent);
550 } while (!gtk_events_pending());
552 return TRUE;
555 static Directory *load(char *pathname, gpointer data)
557 Directory *dir;
559 dir = g_new(Directory, 1);
560 dir->ref = 1;
561 dir->items = g_ptr_array_new();
562 dir->new_items = g_ptr_array_new();
563 dir->up_items = g_ptr_array_new();
564 dir->users = NULL;
565 dir->dir_handle = NULL;
566 dir->needs_update = TRUE;
567 dir->notify_active = FALSE;
568 dir->pathname = g_strdup(pathname);
569 dir->error = NULL;
571 return dir;
574 static void destroy(Directory *dir)
576 g_return_if_fail(dir->users == NULL);
578 g_print("[ destroy %p ]\n", dir);
579 if (dir->dir_handle)
581 mc_closedir(dir->dir_handle);
582 gtk_idle_remove(dir->idle);
584 g_ptr_array_free(dir->up_items, TRUE);
585 free_items_array(dir->items);
586 free_items_array(dir->new_items);
587 g_free(dir->error);
588 g_free(dir->pathname);
589 g_free(dir);
592 static void ref(Directory *dir, gpointer data)
594 dir->ref++;
597 static void unref(Directory *dir, gpointer data)
599 if (--dir->ref == 0)
600 destroy(dir);
603 static int getref(Directory *dir, gpointer data)
605 return dir->ref;
608 static void update(Directory *dir, gchar *pathname, gpointer data)
610 g_free(dir->pathname);
611 dir->pathname = g_strdup(pathname);
613 start_scanning(dir, pathname);
615 if (dir->error)
616 delayed_error("ROX-Filer", dir->error);