1 
2 /**
3    \file lib/gis/ls.c
4 
5    \brief Functions to list the files in a directory.
6 
7    \author Paul Kelly
8 
9    (C) 2007, 2008 by the GRASS Development Team
10 
11    This program is free software under the GNU General Public
12    License (>=v2). Read the file COPYING that comes with GRASS
13    for details.
14 */
15 
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/types.h>
20 #include <dirent.h>
21 #include <unistd.h>
22 
23 #include <grass/gis.h>
24 #include <grass/config.h>
25 #include <grass/glocale.h>
26 
27 #ifdef HAVE_TERMIOS_H
28 #  include <termios.h>
29 #endif
30 
31 #ifdef HAVE_SYS_IOCTL_H
32 #  include <sys/ioctl.h>
33 #endif
34 
35 typedef int ls_filter_func(const char * /*filename */ , void * /*closure */ );
36 
37 static struct state {
38     ls_filter_func *ls_filter;
39     void *ls_closure;
40     ls_filter_func *ls_ex_filter;
41     void *ls_ex_closure;
42 } state;
43 
44 static struct state *st = &state;
45 
cmp_names(const void * aa,const void * bb)46 static int cmp_names(const void *aa, const void *bb)
47 {
48     char *const *a = (char *const *)aa;
49     char *const *b = (char *const *)bb;
50 
51     return strcmp(*a, *b);
52 }
53 
54 /**
55  * \brief Sets a function and its complementary data for G_ls2 filtering.
56  *
57  * Defines a filter function and its rule data that allow G_ls2 to filter out
58  * unwanted file names.  Call this function before G_ls2.
59  *
60  * \param func      Filter callback function to compare a file name and closure
61  * 		    pattern (if NULL, no filter will be used).
62  * 		    func(filename, closure) should return 1 on success, 0 on
63  * 		    failure.
64  * \param closure   Data used to determine if a file name matches the rule.
65  **/
66 
G_set_ls_filter(ls_filter_func * func,void * closure)67 void G_set_ls_filter(ls_filter_func *func, void *closure)
68 {
69     st->ls_filter = func;
70     st->ls_closure = closure;
71 }
72 
G_set_ls_exclude_filter(ls_filter_func * func,void * closure)73 void G_set_ls_exclude_filter(ls_filter_func *func, void *closure)
74 {
75     st->ls_ex_filter = func;
76     st->ls_ex_closure = closure;
77 }
78 
79 /**
80  * \brief Stores a sorted directory listing in an array
81  *
82  * The filenames in the specified directory are stored in an array of
83  * strings, then sorted alphabetically. Each filename has space allocated
84  * using G_store(), which can be freed using G_free() if necessary. The
85  * same goes for the array itself.
86  *
87  *
88  * \param dir       Directory to list
89  * \param num_files Pointer to an integer in which the total number of
90  *                  files listed will be stored
91  *
92  * \return          Pointer to array of strings containing the listing
93  **/
94 
G_ls2(const char * dir,int * num_files)95 char **G_ls2(const char *dir, int *num_files)
96 {
97     struct dirent *dp;
98     DIR *dfd;
99     char **dir_listing = NULL;
100     int n = 0;
101 
102     if ((dfd = opendir(dir)) == NULL)
103 	G_fatal_error(_("Unable to open directory %s"), dir);
104 
105     while ((dp = readdir(dfd)) != NULL) {
106 	if (dp->d_name[0] == '.')	/* Don't list hidden files */
107 	    continue;
108 	if (st->ls_filter && !(*st->ls_filter)(dp->d_name, st->ls_closure))
109 	    continue;
110 	if (st->ls_ex_filter && (*st->ls_ex_filter)(dp->d_name, st->ls_ex_closure))
111 	    continue;
112 	dir_listing = (char **)G_realloc(dir_listing, (1 + n) * sizeof(char *));
113 	dir_listing[n] = G_store(dp->d_name);
114 	n++;
115     }
116     closedir(dfd);
117 
118     /* Sort list of filenames alphabetically */
119     qsort(dir_listing, n, sizeof(char *), cmp_names);
120 
121     *num_files = n;
122     return dir_listing;
123 }
124 
125 /**
126  * \brief Prints a directory listing to a stream, in prettified column format
127  *
128  * A replacement for system("ls -C"). Lists the contents of the directory
129  * specified to the given stream, e.g. stderr. Tries to determine an
130  * appropriate column width to keep the number of lines used to a minimum
131  * and look pretty on the screen.
132  *
133  * \param dir    Directory to list
134  * \param stream Stream to print listing to
135  **/
136 
G_ls(const char * dir,FILE * stream)137 void G_ls(const char *dir, FILE * stream)
138 {
139     int i, n;
140     char **dir_listing = G_ls2(dir, &n);
141 
142     G_ls_format(dir_listing, n, 0, stream);
143 
144     for (i = 0; i < n; i++)
145 	G_free(dir_listing[i]);
146 
147     G_free(dir_listing);
148 }
149 
150 /**
151  * \brief Prints a listing of items to a stream, in prettified column format
152  *
153  * Lists the contents of the array passed to the given stream, e.g. stderr.
154  * Prints the number of items specified by "perline" to each line, unless
155  * perline is given as 0 in which case the function tries to determine an
156  * appropriate column width to keep the number of lines used to a minimum
157  * and look pretty on the screen.
158  *
159  * \param list      Array of strings containing items to be printed
160  * \param num_items Number of items in the array
161  * \param perline   Number of items to print per line, 0 for autodetect
162  * \param stream    Stream to print listing to
163  **/
164 
G_ls_format(char ** list,int num_items,int perline,FILE * stream)165 void G_ls_format(char **list, int num_items, int perline, FILE * stream)
166 {
167     int i;
168 
169     int field_width, column_height;
170     int screen_width = 80;	/* Default width of 80 columns */
171 
172     if (num_items < 1)
173 	return;			/* Nothing to print */
174 
175 #ifdef TIOCGWINSZ
176     /* Determine screen_width if possible */
177     {
178 	struct winsize size;
179 
180 	if (ioctl(fileno(stream), TIOCGWINSZ, (char *)&size) == 0)
181 	    screen_width = size.ws_col;
182     }
183 #endif
184 
185     if (perline == 0) {
186 	int max_len = 0;
187 
188 	for (i = 0; i < num_items; i++) {
189 	    /* Find maximum filename length */
190 	    if (strlen(list[i]) > max_len)
191 		max_len = strlen(list[i]);
192 	}
193 	/* Auto-fit the number of items that will
194 	 * fit per line (+1 because of space after item) */
195 	perline = screen_width / (max_len + 1);
196 	if (perline < 1)
197 	    perline = 1;
198     }
199 
200     /* Field width to accommodate longest filename */
201     field_width = screen_width / perline;
202     /* Longest column height (i.e. num_items <= perline * column_height) */
203     column_height = (num_items / perline) + ((num_items % perline) > 0);
204 
205     {
206 	const int max
207 	    = num_items + column_height - (num_items % column_height);
208 	char **next;
209 
210 	for (i = 1, next = list; i <= num_items; i++) {
211 	    char **cur = next;
212 
213 	    next += column_height;
214 	    if (next >= list + num_items) {
215 		/* the next item has to be on the other line */
216 		next -= (max - 1 - (next < list + max ? column_height : 0));
217 		fprintf(stream, "%s\n", *cur);
218 	    }
219 	    else {
220 		fprintf(stream, "%-*s", field_width, *cur);
221 	    }
222 	}
223     }
224 }
225