1 /* Lips of Suna
2  * Copyright© 2007-2009 Lips of Suna development team.
3  *
4  * Lips of Suna is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as
6  * published by the Free Software Foundation, either version 3 of the
7  * License, or (at your option) any later version.
8  *
9  * Lips of Suna is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with Lips of Suna. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /**
19  * \addtogroup lisys System
20  * @{
21  * \addtogroup lisysDir Directory
22  * @{
23  */
24 
25 #include <assert.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <dirent.h>
32 #include <unistd.h>
33 #include "system-directory.h"
34 
35 #ifdef LI_ENABLE_ERROR
36 #include "system-error.h"
37 #define error_memory() lisys_error_set (ENOMEM, NULL)
38 #define error_open(fmt, file) lisys_error_set (EIO, fmt, file)
39 #else
40 #define error_memory()
41 #define error_open(fmt, file)
42 #endif
43 
44 struct _lisysDir
45 {
46 	void* dir;
47 	char* path;
48 	struct
49 	{
50 		lisysDirFilter filter;
51 		lisysDirSorter sorter;
52 	} calls;
53 	struct
54 	{
55 		int count;
56 		char** array;
57 	} names;
58 };
59 
60 static char*
61 private_concat_paths (const char* a,
62                       const char* b);
63 
64 /*****************************************************************************/
65 
66 /**
67  * \brief Opens a directory.
68  *
69  * \param path Path to directory.
70  * \return Directory or NULL.
71  */
72 lisysDir*
lisys_dir_open(const char * path)73 lisys_dir_open (const char* path)
74 {
75 	lisysDir* self;
76 
77 	/* Allocate self. */
78 	self = calloc (1, sizeof (lisysDir));
79 	if (self == NULL)
80 	{
81 		error_memory ();
82 		return NULL;
83 	}
84 
85 	/* Store path. */
86 	self->path = strdup (path);
87 	if (path == NULL)
88 	{
89 		error_memory ();
90 		free (self);
91 		return NULL;
92 	}
93 
94 	/* Open the directory. */
95 	self->dir = opendir (path);
96 	if (self->dir == NULL)
97 	{
98 		error_open ("cannot open directory `%s'", path);
99 		free (self->path);
100 		free (self);
101 		return NULL;
102 	}
103 
104 	return self;
105 }
106 
107 /**
108  * \brief Frees a directory.
109  *
110  * \param self Directory.
111  */
112 void
lisys_dir_free(lisysDir * self)113 lisys_dir_free (lisysDir* self)
114 {
115 	int i;
116 
117 	closedir (self->dir);
118 	for (i = 0 ; i < self->names.count ; i++)
119 		free (self->names.array[i]);
120 	free (self->names.array);
121 	free (self->path);
122 	free (self);
123 }
124 
125 /**
126  * \brief Scans a directory.
127  *
128  * Scans the directory for files and filters and sorts them according to the
129  * current filter and sorter rules. The results are stored to the names array.
130  *
131  * \param self Directory.
132  * \return Nonzero on success.
133  */
134 int
lisys_dir_scan(lisysDir * self)135 lisys_dir_scan (lisysDir* self)
136 {
137 	int i;
138 	int num = 0;
139 	int cap = 16;
140 	char** tmp;
141 	char** list;
142 	struct dirent* ent;
143 
144 	/* Allocate the name list. */
145 	list = malloc (cap * sizeof (char*));
146 	if (list == NULL)
147 	{
148 		error_memory ();
149 		return 0;
150 	}
151 
152 	/* Read names. */
153 	rewinddir (self->dir);
154 	while ((ent = readdir (self->dir)))
155 	{
156 		/* Filter. */
157 		if (self->calls.filter != NULL)
158 		{
159 			if (!self->calls.filter (self->path, ent->d_name))
160 				continue;
161 		}
162 
163 		/* Resize the list. */
164 		if (num == cap)
165 		{
166 			tmp = realloc (list, (cap << 1) * sizeof (char*));
167 			if (tmp == NULL)
168 			{
169 				error_memory ();
170 				goto error;
171 			}
172 			cap <<= 1;
173 			list = tmp;
174 		}
175 
176 		/* Append to the list. */
177 		list[num] = strdup (ent->d_name);
178 		if (list[num] == NULL)
179 		{
180 			error_memory ();
181 			goto error;
182 		}
183 		num++;
184 	}
185 
186 	/* Shrink. */
187 	if (num != 0)
188 	{
189 		tmp = realloc (list, num * sizeof (char*));
190 		if (tmp != NULL)
191 			list = tmp;
192 	}
193 	else
194 	{
195 		free (list);
196 		list = NULL;
197 	}
198 
199 	/* Sort names. */
200 	if (self->calls.sorter != NULL)
201 		qsort (list, num, sizeof (char*), (int(*)(const void*, const void*)) self->calls.sorter);
202 
203 	/* Replace old names. */
204 	for (i = 0 ; i < self->names.count ; i++)
205 		free (self->names.array[i]);
206 	free (self->names.array);
207 	self->names.count = num;
208 	self->names.array = list;
209 
210 	return 1;
211 
212 error:
213 	for (i = 0 ; i < num ; i++)
214 		free (list[i]);
215 	free (list);
216 	return 0;
217 }
218 
219 /**
220  * \brief Gets the number of files in the directory.
221  *
222  * \param self Directory.
223  * \return Number of files.
224  */
225 int
lisys_dir_get_count(const lisysDir * self)226 lisys_dir_get_count (const lisysDir* self)
227 {
228 	return self->names.count;
229 }
230 
231 /**
232  * \brief Gets the name of one of the files in the name array.
233  *
234  * \param self Directory.
235  * \param i Index in the name array.
236  * \return String owned by the directory.
237  */
238 const char*
lisys_dir_get_name(const lisysDir * self,int i)239 lisys_dir_get_name (const lisysDir* self,
240                     int             i)
241 {
242 	assert (i >= 0);
243 	assert (i < self->names.count);
244 
245 	return self->names.array[i];
246 }
247 
248 /**
249  * \brief Gets the path to one of the files in the name array.
250  *
251  * \param self Directory.
252  * \param i Index in the name array.
253  * \return New string or NULL.
254  */
255 char*
lisys_dir_get_path(const lisysDir * self,int i)256 lisys_dir_get_path (const lisysDir* self,
257                     int             i)
258 {
259 	assert (i >= 0);
260 	assert (i < self->names.count);
261 
262 	return private_concat_paths (self->path, self->names.array[i]);
263 }
264 
265 /**
266  * \brief Set the current filter rule.
267  *
268  * \param self Directory.
269  * \param filter Fiulter function or NULL.
270  */
271 void
lisys_dir_set_filter(lisysDir * self,lisysDirFilter filter)272 lisys_dir_set_filter (lisysDir*      self,
273                       lisysDirFilter filter)
274 {
275 	self->calls.filter = filter;
276 }
277 
278 /**
279  * \brief Set the current sorter rule.
280  *
281  * \param self Directory.
282  * \param sorter Sorter function or NULL.
283  */
284 void
lisys_dir_set_sorter(lisysDir * self,lisysDirSorter sorter)285 lisys_dir_set_sorter (lisysDir*      self,
286                       lisysDirSorter sorter)
287 {
288 	self->calls.sorter = sorter;
289 }
290 
291 /*****************************************************************************/
292 
293 /**
294  * \brief Lets everything except directories through.
295  */
296 int
LISYS_DIR_FILTER_FILES(const char * dir,const char * name)297 LISYS_DIR_FILTER_FILES (const char* dir,
298                         const char* name)
299 {
300 	char* path;
301 	struct stat st;
302 
303 	path = private_concat_paths (dir, name);
304 	if (path == NULL)
305 		return 1;
306 	if (stat (path, &st) < 0)
307 	{
308 		free (path);
309 		return 0;
310 	}
311 	if (!S_ISDIR (st.st_mode))
312 	{
313 		free (path);
314 		return 1;
315 	}
316 
317 	free (path);
318 	return 0;
319 }
320 
321 /**
322  * \brief Only lets directories through.
323  */
324 int
LISYS_DIR_FILTER_DIRS(const char * dir,const char * name)325 LISYS_DIR_FILTER_DIRS (const char* dir,
326                        const char* name)
327 {
328 	char* path;
329 	struct stat st;
330 
331 	path = private_concat_paths (dir, name);
332 	if (path == NULL)
333 		return 1;
334 	if (stat (path, &st) < 0)
335 	{
336 		free (path);
337 		return 0;
338 	}
339 	if (S_ISDIR (st.st_mode))
340 	{
341 		free (path);
342 		return 1;
343 	}
344 
345 	free (path);
346 	return 0;
347 }
348 
349 /**
350  * \brief Only lets hidden files through.
351  */
352 int
LISYS_DIR_FILTER_HIDDEN(const char * dir,const char * name)353 LISYS_DIR_FILTER_HIDDEN (const char* dir,
354                          const char* name)
355 {
356 	if (name[0] == '.')
357 		return 1;
358 	return 0;
359 }
360 
361 /**
362  * \brief Only lets non-hidden files through.
363  */
364 int
LISYS_DIR_FILTER_VISIBLE(const char * dir,const char * name)365 LISYS_DIR_FILTER_VISIBLE (const char* dir,
366                           const char* name)
367 {
368 	if (name[0] == '.')
369 		return 0;
370 	return 1;
371 }
372 
373 /**
374  * \brief Sorts entries alphabetically.
375  */
376 int
LISYS_DIR_SORTER_ALPHA(const char ** name0,const char ** name1)377 LISYS_DIR_SORTER_ALPHA (const char** name0,
378                         const char** name1)
379 {
380 	return strcmp (*name0, *name1);
381 }
382 
383 /*****************************************************************************/
384 
385 static char*
private_concat_paths(const char * a,const char * b)386 private_concat_paths (const char* a,
387                       const char* b)
388 {
389 	int len0;
390 	int len1;
391 	char* ret;
392 
393 	len0 = strlen (a);
394 	len1 = strlen (b);
395 	ret = malloc (len0 + len1 + 2);
396 	if (ret == NULL)
397 	{
398 		error_memory ();
399 		return NULL;
400 	}
401 	strcpy (ret, a);
402 	if (len0 && a[len0] != '/')
403 		ret[len0++] = '/';
404 	strcpy (ret + len0, b);
405 	ret[len0 + len1] = '\0';
406 
407 	return ret;
408 }
409 
410 /** @} */
411 /** @} */
412