share/gui: don't use less of widget width for truncation than available
[neverball] / share / dir.c
1 #include <dirent.h>
2
3 #include <string.h>
4 #include <stdlib.h>
5 #include <assert.h>
6
7 #include "dir.h"
8 #include "common.h"
9
10 static char **get_dir_list(const char *path)
11 {
12     DIR *dir;
13     char **files = NULL;
14     int count = 0;
15
16     /*
17      * HACK: MinGW provides numerous POSIX extensions to MSVCRT,
18      * including dirent.h, so parasti ever so lazily has not bothered
19      * to port the code below to FindFirstFile et al.
20      */
21
22     if ((dir = opendir(path)))
23     {
24         struct dirent *ent;
25
26         files = malloc(sizeof (char *));
27
28         while ((ent = readdir(dir)))
29         {
30             if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
31                 continue;
32
33             files[count++] = strdup(ent->d_name);
34             files = realloc(files, (count + 1) * sizeof (char *));
35         }
36
37         files[count] = NULL;
38
39         closedir(dir);
40     }
41
42     return files;
43 }
44
45 static void free_dir_list(void *files)
46 {
47     if (files)
48     {
49         char **file;
50
51         /* Free file names. */
52         for (file = files; *file; free(*file++));
53
54         /* Free trailing NULL. */
55         free(*file);
56
57         /* Free pointer list. */
58         free(files);
59     }
60 }
61
62 static struct dir_item *add_item(Array items, const char *dir, const char *name)
63 {
64     struct dir_item *item = array_add(items);
65
66     item->path = *dir ? concat_string(dir, "/", name, NULL) : strdup(name);
67     item->data = NULL;
68
69     return item;
70 }
71
72 static void del_item(Array items)
73 {
74     struct dir_item *item = array_get(items, array_len(items) - 1);
75
76     free((void *) item->path);
77     assert(!item->data);
78
79     array_del(items);
80 }
81
82 /*
83  * Scan the directory PATH and return an array of dir_item structures
84  * for which FILTER evaluates to non-zero (or all, if FILTER is NULL).
85  * FILTER can associate data with a dir_item for later use by
86  * assigning it to the "data" member.  If GET_LIST is non-NULL, it is
87  * used to obtain a NULL-terminated list of names in PATH; this list
88  * is later freed with FREE_LIST.  If both are NULL, the default
89  * directory listing mechanism is used.
90  *
91  * (FIXME: GET_LIST was added to reduce code duplication when
92  * maintaining the existing "real file system" directory scanning
93  * functionality and at the same time supporting "virtual file system"
94  * directory scanning using custom routines.  The result of this is
95  * that dir_scan becomes a ridiculously general piece of code to
96  * "filter and turn an arbitrary list of strings into an array of
97  * dir_item structs".  This is too confusing; it's probably better to
98  * support VFS only and adapt accordingly.)
99  */
100 Array dir_scan(const char *path,
101                int    (*filter)   (struct dir_item *),
102                char **(*get_list) (const char *),
103                void   (*free_list)(void *))
104 {
105     char **list;
106     Array items = NULL;
107
108     assert((get_list && free_list) || (!get_list && !free_list));
109
110     if (!get_list)
111         get_list = get_dir_list;
112
113     if (!free_list)
114         free_list = free_dir_list;
115
116     if ((list = get_list(path)))
117     {
118         char **file = list;
119
120         items = array_new(sizeof (struct dir_item));
121
122         while (*file)
123         {
124             struct dir_item *item;
125
126             item = add_item(items, path, *file);
127
128             if (filter && !filter(item))
129                 del_item(items);
130
131             file++;
132         }
133
134         free_list(list);
135     }
136
137     return items;
138 }
139
140 void dir_free(Array items)
141 {
142     while (array_len(items))
143         del_item(items);
144
145     array_free(items);
146 }