1 /*
2 * Copyright 2017 LarsGit223
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program 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 General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #include <glib.h>
20 #include <glib/gstdio.h>
21
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #include "filelist.h"
27 #include <geanyplugin.h>
28
29 typedef struct
30 {
31 guint file_count;
32 guint folder_count;
33 GSList *filelist;
34 GSList *file_patterns;
35 GSList *ignored_dirs_list;
36 GSList *ignored_file_list;
37 GHashTable *visited_paths;
38 }
39 ScanDirParams;
40
41
42 typedef struct
43 {
44 GSList *filelist;
45 GHashTable *visited_paths;
46 void (*callback)(const gchar *path, gboolean *add, gboolean *enter, void *userdata);
47 void *userdata;
48 }
49 ScanDirParamsCallback;
50
51
52 /** Get precompiled patterns.
53 *
54 * The function builds the precompiled patterns for @a patterns and returns them
55 * as a list.
56 *
57 * @param patterns NULL terminated string array of patterns.
58 * @return Pointer to GSList of patterns or NULL if patterns == NULL
59 *
60 **/
filelist_get_precompiled_patterns(gchar ** patterns)61 GSList *filelist_get_precompiled_patterns(gchar **patterns)
62 {
63 guint i;
64 GSList *pattern_list = NULL;
65
66 if (!patterns)
67 return NULL;
68
69 for (i = 0; patterns[i] != NULL; i++)
70 {
71 GPatternSpec *pattern_spec = g_pattern_spec_new(patterns[i]);
72 pattern_list = g_slist_prepend(pattern_list, pattern_spec);
73 }
74 return pattern_list;
75 }
76
77
78 /** Check if a string matches a pattern.
79 *
80 * The function checks if @a str matches pattern @a patterns.
81 *
82 * @param patterns Pattern list.
83 * @param str String to check
84 * @return TRUE if str matches the pattern, FALSE otherwise
85 *
86 **/
filelist_patterns_match(GSList * patterns,const gchar * str)87 gboolean filelist_patterns_match(GSList *patterns, const gchar *str)
88 {
89 GSList *elem = NULL;
90 foreach_slist (elem, patterns)
91 {
92 GPatternSpec *pattern = elem->data;
93 if (g_pattern_match_string(pattern, str))
94 return TRUE;
95 }
96 return FALSE;
97 }
98
99
100 /* Scan directory searchdir. Input and output parameters come from/go to params. */
filelist_scan_directory_int(const gchar * searchdir,ScanDirParams * params,guint flags)101 static void filelist_scan_directory_int(const gchar *searchdir, ScanDirParams *params, guint flags)
102 {
103 GDir *dir;
104 gchar *locale_path = utils_get_locale_from_utf8(searchdir);
105 gchar *real_path = utils_get_real_path(locale_path);
106
107 dir = g_dir_open(locale_path, 0, NULL);
108 if (!dir || !real_path || g_hash_table_lookup(params->visited_paths, real_path))
109 {
110 if (dir != NULL)
111 {
112 g_dir_close(dir);
113 }
114 g_free(locale_path);
115 g_free(real_path);
116 return;
117 }
118
119 g_hash_table_insert(params->visited_paths, real_path, GINT_TO_POINTER(1));
120
121 while (TRUE)
122 {
123 const gchar *locale_name;
124 gchar *locale_filename, *utf8_filename, *utf8_name;
125
126 locale_name = g_dir_read_name(dir);
127 if (!locale_name)
128 {
129 break;
130 }
131
132 utf8_name = utils_get_utf8_from_locale(locale_name);
133 locale_filename = g_build_filename(locale_path, locale_name, NULL);
134 utf8_filename = utils_get_utf8_from_locale(locale_filename);
135
136 if (g_file_test(locale_filename, G_FILE_TEST_IS_DIR))
137 {
138 if (!filelist_patterns_match(params->ignored_dirs_list, utf8_name))
139 {
140 filelist_scan_directory_int(utf8_filename, params, flags);
141 params->folder_count++;
142 if (flags & FILELIST_FLAG_ADD_DIRS)
143 {
144 params->filelist = g_slist_prepend(params->filelist, g_strdup(utf8_filename));
145 }
146 }
147 }
148 else if (g_file_test(locale_filename, G_FILE_TEST_IS_REGULAR))
149 {
150 if (filelist_patterns_match(params->file_patterns, utf8_name) && !
151 filelist_patterns_match(params->ignored_file_list, utf8_name))
152 {
153 params->file_count++;
154 params->filelist = g_slist_prepend(params->filelist, g_strdup(utf8_filename));
155 }
156 }
157
158 g_free(utf8_filename);
159 g_free(locale_filename);
160 g_free(utf8_name);
161 }
162
163 g_dir_close(dir);
164 g_free(locale_path);
165 }
166
167
168 /** Scan a directory and return a list of files.
169 *
170 * The function scans directory searchdir and returns a list of files.
171 * The list will only include files which match the patterns in file_patterns.
172 * Directories or files matched by ignored_dirs_patterns or ignored_file_patterns
173 * will not be scanned or added to the list.
174 *
175 * @param files Can be optionally specified to return the number of matched/found
176 * files in *files.
177 * @param folders Can be optionally specified to return the number of matched/found
178 * folders/sub-directories in *folders.
179 * @param searchdir Directory which shall be scanned
180 * @param file_patterns
181 * File patterns for matching files (e.g. "*.c") or NULL
182 * for all files.
183 * @param ignored_dirs_patterns
184 * Patterns for ignored directories
185 * @param ignored_file_patterns
186 * Patterns for ignored files
187 * @return GSList of matched files
188 *
189 **/
gp_filelist_scan_directory(guint * files,guint * folders,const gchar * searchdir,gchar ** file_patterns,gchar ** ignored_dirs_patterns,gchar ** ignored_file_patterns)190 GSList *gp_filelist_scan_directory(guint *files, guint *folders, const gchar *searchdir, gchar **file_patterns,
191 gchar **ignored_dirs_patterns, gchar **ignored_file_patterns)
192 {
193 ScanDirParams params = { 0 };
194
195 if (!file_patterns || !file_patterns[0])
196 {
197 const gchar *all_pattern[] = { "*", NULL };
198 params.file_patterns = filelist_get_precompiled_patterns((gchar **)all_pattern);
199 }
200 else
201 {
202 params.file_patterns = filelist_get_precompiled_patterns(file_patterns);
203 }
204
205 params.ignored_dirs_list = filelist_get_precompiled_patterns(ignored_dirs_patterns);
206 params.ignored_file_list = filelist_get_precompiled_patterns(ignored_file_patterns);
207
208 params.visited_paths = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
209 filelist_scan_directory_int(searchdir, ¶ms, 0);
210 g_hash_table_destroy(params.visited_paths);
211
212 g_slist_foreach(params.file_patterns, (GFunc) g_pattern_spec_free, NULL);
213 g_slist_free(params.file_patterns);
214
215 g_slist_foreach(params.ignored_dirs_list, (GFunc) g_pattern_spec_free, NULL);
216 g_slist_free(params.ignored_dirs_list);
217
218 g_slist_foreach(params.ignored_file_list, (GFunc) g_pattern_spec_free, NULL);
219 g_slist_free(params.ignored_file_list);
220
221 if (files != NULL)
222 {
223 *files = params.file_count;
224 }
225 if (folders != NULL)
226 {
227 *folders = params.folder_count;
228 }
229
230 return params.filelist;
231 }
232
233
234 /** Scan a directory and return a list of files and directories.
235 *
236 * The function scans directory searchdir and returns a list of files.
237 * The list will only include files which match the patterns in file_patterns.
238 * Directories or files matched by ignored_dirs_patterns or ignored_file_patterns
239 * will not be scanned or added to the list.
240 *
241 * If flags is 0 then the result will be the same as for gp_filelist_scan_directory().
242 *
243 * @param files Can be optionally specified to return the number of matched/found
244 * files in *files.
245 * @param folders Can be optionally specified to return the number of matched/found
246 * folders/sub-directories in *folders.
247 * @param searchdir Directory which shall be scanned
248 * @param file_patterns
249 * File patterns for matching files (e.g. "*.c") or NULL
250 * for all files.
251 * @param ignored_dirs_patterns
252 * Patterns for ignored directories
253 * @param ignored_file_patterns
254 * Patterns for ignored files
255 * @param flags Flags which influence the returned list:
256 * - FILELIST_FLAG_ADD_DIRS: if set, directories will be added
257 * as own list entries. This also includes empty dirs.
258 * @return GSList of matched files
259 *
260 **/
gp_filelist_scan_directory_full(guint * files,guint * folders,const gchar * searchdir,gchar ** file_patterns,gchar ** ignored_dirs_patterns,gchar ** ignored_file_patterns,guint flags)261 GSList *gp_filelist_scan_directory_full(guint *files, guint *folders, const gchar *searchdir, gchar **file_patterns,
262 gchar **ignored_dirs_patterns, gchar **ignored_file_patterns, guint flags)
263 {
264 ScanDirParams params = { 0 };
265
266 if (!file_patterns || !file_patterns[0])
267 {
268 const gchar *all_pattern[] = { "*", NULL };
269 params.file_patterns = filelist_get_precompiled_patterns((gchar **)all_pattern);
270 }
271 else
272 {
273 params.file_patterns = filelist_get_precompiled_patterns(file_patterns);
274 }
275
276 params.ignored_dirs_list = filelist_get_precompiled_patterns(ignored_dirs_patterns);
277 params.ignored_file_list = filelist_get_precompiled_patterns(ignored_file_patterns);
278
279 params.visited_paths = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
280 filelist_scan_directory_int(searchdir, ¶ms, flags);
281 g_hash_table_destroy(params.visited_paths);
282
283 g_slist_foreach(params.file_patterns, (GFunc) g_pattern_spec_free, NULL);
284 g_slist_free(params.file_patterns);
285
286 g_slist_foreach(params.ignored_dirs_list, (GFunc) g_pattern_spec_free, NULL);
287 g_slist_free(params.ignored_dirs_list);
288
289 g_slist_foreach(params.ignored_file_list, (GFunc) g_pattern_spec_free, NULL);
290 g_slist_free(params.ignored_file_list);
291
292 if (files != NULL)
293 {
294 *files = params.file_count;
295 }
296 if (folders != NULL)
297 {
298 *folders = params.folder_count;
299 }
300
301 return params.filelist;
302 }
303
304
305 /** Check if a filepath matches the given patterns.
306 *
307 * If the filepath belongs to a regular file, then TRUE will be returned if
308 * the filepath matches the patterns in file_patterns but does not match the
309 * patterns in ignored_file_patterns.
310 *
311 * If the filepath belongs to a directory, then TRUE will be returned if it
312 * does not match the patterns in ignored_dirs_patterns.
313 *
314 * @param filepath The file or dorectory path to check
315 * @param file_patterns
316 * File patterns for matching files (e.g. "*.c") or NULL
317 * for all files.
318 * @param ignored_dirs_patterns
319 * Patterns for ignored directories
320 * @param ignored_file_patterns
321 * Patterns for ignored files
322 * @return gboolean
323 *
324 **/
gp_filelist_filepath_matches_patterns(const gchar * filepath,gchar ** file_patterns,gchar ** ignored_dirs_patterns,gchar ** ignored_file_patterns)325 gboolean gp_filelist_filepath_matches_patterns(const gchar *filepath, gchar **file_patterns,
326 gchar **ignored_dirs_patterns, gchar **ignored_file_patterns)
327 {
328 gboolean match = FALSE;
329 GSList *file_patterns_list;
330 GSList *ignored_dirs_list;
331 GSList *ignored_file_list;
332
333 if (!file_patterns || !file_patterns[0])
334 {
335 const gchar *all_pattern[] = { "*", NULL };
336 file_patterns_list = filelist_get_precompiled_patterns((gchar **)all_pattern);
337 }
338 else
339 {
340 file_patterns_list = filelist_get_precompiled_patterns(file_patterns);
341 }
342
343 ignored_dirs_list = filelist_get_precompiled_patterns(ignored_dirs_patterns);
344 ignored_file_list = filelist_get_precompiled_patterns(ignored_file_patterns);
345
346 if (g_file_test(filepath, G_FILE_TEST_IS_DIR))
347 {
348 if (!filelist_patterns_match(ignored_dirs_list, filepath))
349 {
350 match = TRUE;
351 }
352 }
353 else if (g_file_test(filepath, G_FILE_TEST_IS_REGULAR))
354 {
355 if (filelist_patterns_match(file_patterns_list, filepath) &&
356 !filelist_patterns_match(ignored_file_list, filepath))
357 {
358 match = TRUE;
359 }
360 }
361
362 g_slist_foreach(file_patterns_list, (GFunc) g_pattern_spec_free, NULL);
363 g_slist_free(file_patterns_list);
364
365 g_slist_foreach(ignored_dirs_list, (GFunc) g_pattern_spec_free, NULL);
366 g_slist_free(ignored_dirs_list);
367
368 g_slist_foreach(ignored_file_list, (GFunc) g_pattern_spec_free, NULL);
369 g_slist_free(ignored_file_list);
370
371 return match;
372 }
373
374
375 /* Scan directory searchdir. Let a callback-function decide which files
376 to add and which directories to enter/crawl. */
filelist_scan_directory_callback_int(const gchar * searchdir,ScanDirParamsCallback * params)377 static void filelist_scan_directory_callback_int(const gchar *searchdir, ScanDirParamsCallback *params)
378 {
379 GDir *dir;
380 gchar *locale_path = utils_get_locale_from_utf8(searchdir);
381 gchar *real_path = utils_get_real_path(locale_path);
382
383 dir = g_dir_open(locale_path, 0, NULL);
384 if (!dir || !real_path || g_hash_table_lookup(params->visited_paths, real_path))
385 {
386 if (dir != NULL)
387 {
388 g_dir_close(dir);
389 }
390 g_free(locale_path);
391 g_free(real_path);
392 return;
393 }
394
395 g_hash_table_insert(params->visited_paths, real_path, GINT_TO_POINTER(1));
396
397 while (TRUE)
398 {
399 const gchar *locale_name;
400 gchar *locale_filename, *utf8_filename, *utf8_name;
401 gboolean add, enter;
402
403 locale_name = g_dir_read_name(dir);
404 if (!locale_name)
405 {
406 break;
407 }
408
409 utf8_name = utils_get_utf8_from_locale(locale_name);
410 locale_filename = g_build_filename(locale_path, locale_name, NULL);
411 utf8_filename = utils_get_utf8_from_locale(locale_filename);
412
413 /* Call user callback. */
414 params->callback(locale_filename, &add, &enter, params->userdata);
415
416 /* Should the file/path be added to the list? */
417 if (add)
418 {
419 params->filelist = g_slist_prepend(params->filelist, g_strdup(utf8_filename));
420 }
421
422 /* If the path is a directory, should it be entered? */
423 if (enter && g_file_test(locale_filename, G_FILE_TEST_IS_DIR))
424 {
425 filelist_scan_directory_callback_int(utf8_filename, params);
426 }
427
428 g_free(utf8_filename);
429 g_free(locale_filename);
430 g_free(utf8_name);
431 }
432
433 g_dir_close(dir);
434 g_free(locale_path);
435 }
436
437
438 /** Scan a directory and return a list of files and directories.
439 *
440 * The function scans directory searchdir and returns a list of files.
441 * The list will only include files for which the callback function returned
442 * 'add == TRUE'. Sub-directories will only be scanned if the callback function
443 * returned 'enter == TRUE'.
444 *
445 * @param searchdir Directory which shall be scanned
446 * @param callback Function to be called to decide which files to add to
447 * the list and to decide which sub-directories to enter
448 * or to ignore.
449 * @param userdata A pointer which is transparently passed through
450 * to the callback function.
451 * @return GSList of matched files
452 *
453 **/
gp_filelist_scan_directory_callback(const gchar * searchdir,void (* callback)(const gchar * path,gboolean * add,gboolean * enter,void * userdata),void * userdata)454 GSList *gp_filelist_scan_directory_callback(const gchar *searchdir,
455 void (*callback)(const gchar *path, gboolean *add, gboolean *enter, void *userdata),
456 void *userdata)
457 {
458 ScanDirParamsCallback params = { 0 };
459
460 params.callback = callback;
461 params.userdata = userdata;
462 params.visited_paths = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
463 filelist_scan_directory_callback_int(searchdir, ¶ms);
464 g_hash_table_destroy(params.visited_paths);
465
466 return params.filelist;
467 }
468