Commit | Line | Data |
---|---|---|
3719602c PC |
1 | /* Copyright (C) 2010-2020 The RetroArch team |
2 | * | |
3 | * --------------------------------------------------------------------------------------- | |
4 | * The following license statement only applies to this file (dir_list.c). | |
5 | * --------------------------------------------------------------------------------------- | |
6 | * | |
7 | * Permission is hereby granted, free of charge, | |
8 | * to any person obtaining a copy of this software and associated documentation files (the "Software"), | |
9 | * to deal in the Software without restriction, including without limitation the rights to | |
10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, | |
11 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
19 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
21 | */ | |
22 | ||
23 | #include <stdlib.h> | |
24 | ||
25 | #if defined(_WIN32) && defined(_XBOX) | |
26 | #include <xtl.h> | |
27 | #elif defined(_WIN32) | |
28 | #include <windows.h> | |
29 | #endif | |
30 | ||
31 | #include <lists/dir_list.h> | |
32 | #include <lists/string_list.h> | |
33 | #include <file/file_path.h> | |
34 | ||
35 | #include <compat/strl.h> | |
36 | #include <retro_dirent.h> | |
37 | ||
38 | #include <string/stdstring.h> | |
39 | #include <retro_miscellaneous.h> | |
40 | ||
41 | static int qstrcmp_plain(const void *a_, const void *b_) | |
42 | { | |
43 | const struct string_list_elem *a = (const struct string_list_elem*)a_; | |
44 | const struct string_list_elem *b = (const struct string_list_elem*)b_; | |
45 | ||
46 | return strcasecmp(a->data, b->data); | |
47 | } | |
48 | ||
49 | static int qstrcmp_dir(const void *a_, const void *b_) | |
50 | { | |
51 | const struct string_list_elem *a = (const struct string_list_elem*)a_; | |
52 | const struct string_list_elem *b = (const struct string_list_elem*)b_; | |
53 | int a_type = a->attr.i; | |
54 | int b_type = b->attr.i; | |
55 | ||
56 | /* Sort directories before files. */ | |
57 | if (a_type != b_type) | |
58 | return b_type - a_type; | |
59 | return strcasecmp(a->data, b->data); | |
60 | } | |
61 | ||
62 | /** | |
63 | * dir_list_sort: | |
64 | * @list : pointer to the directory listing. | |
65 | * @dir_first : move the directories in the listing to the top? | |
66 | * | |
67 | * Sorts a directory listing. | |
68 | **/ | |
69 | void dir_list_sort(struct string_list *list, bool dir_first) | |
70 | { | |
71 | if (list) | |
72 | qsort(list->elems, list->size, sizeof(struct string_list_elem), | |
73 | dir_first ? qstrcmp_dir : qstrcmp_plain); | |
74 | } | |
75 | ||
76 | /** | |
77 | * dir_list_free: | |
78 | * @list : pointer to the directory listing | |
79 | * | |
80 | * Frees a directory listing. | |
81 | **/ | |
82 | void dir_list_free(struct string_list *list) | |
83 | { | |
84 | string_list_free(list); | |
85 | } | |
86 | ||
87 | bool dir_list_deinitialize(struct string_list *list) | |
88 | { | |
89 | if (!list) | |
90 | return false; | |
91 | return string_list_deinitialize(list); | |
92 | } | |
93 | ||
94 | /** | |
95 | * dir_list_read: | |
96 | * @dir : directory path. | |
97 | * @list : the string list to add files to | |
98 | * @ext_list : the string list of extensions to include | |
99 | * @include_dirs : include directories as part of the finished directory listing? | |
100 | * @include_hidden : include hidden files and directories as part of the finished directory listing? | |
101 | * @include_compressed : Only include files which match ext. Do not try to match compressed files, etc. | |
102 | * @recursive : list directory contents recursively | |
103 | * | |
104 | * Add files within a directory to an existing string list | |
105 | * | |
106 | * @return -1 on error, 0 on success. | |
107 | **/ | |
108 | static int dir_list_read(const char *dir, | |
109 | struct string_list *list, struct string_list *ext_list, | |
110 | bool include_dirs, bool include_hidden, | |
111 | bool include_compressed, bool recursive) | |
112 | { | |
113 | struct RDIR *entry = retro_opendir_include_hidden(dir, include_hidden); | |
114 | ||
115 | if (!entry || retro_dirent_error(entry)) | |
116 | goto error; | |
117 | ||
118 | while (retro_readdir(entry)) | |
119 | { | |
120 | union string_list_elem_attr attr; | |
121 | char file_path[PATH_MAX_LENGTH]; | |
122 | const char *name = retro_dirent_get_name(entry); | |
123 | ||
124 | if (name[0] == '.') | |
125 | { | |
126 | /* Do not include hidden files and directories */ | |
127 | if (!include_hidden) | |
128 | continue; | |
129 | ||
130 | /* char-wise comparisons to avoid string comparison */ | |
131 | ||
132 | /* Do not include current dir */ | |
133 | if (name[1] == '\0') | |
134 | continue; | |
135 | /* Do not include parent dir */ | |
136 | if (name[1] == '.' && name[2] == '\0') | |
137 | continue; | |
138 | } | |
139 | ||
140 | fill_pathname_join_special(file_path, dir, name, sizeof(file_path)); | |
141 | ||
142 | if (retro_dirent_is_dir(entry, NULL)) | |
143 | { | |
144 | if (recursive) | |
145 | dir_list_read(file_path, list, ext_list, include_dirs, | |
146 | include_hidden, include_compressed, recursive); | |
147 | ||
148 | if (!include_dirs) | |
149 | continue; | |
150 | attr.i = RARCH_DIRECTORY; | |
151 | } | |
152 | else | |
153 | { | |
154 | const char *file_ext = path_get_extension(name); | |
155 | ||
156 | attr.i = RARCH_FILETYPE_UNSET; | |
157 | ||
158 | /* | |
159 | * If the file format is explicitly supported by the libretro-core, we | |
160 | * need to immediately load it and not designate it as a compressed file. | |
161 | * | |
162 | * Example: .zip could be supported as a image by the core and as a | |
163 | * compressed_file. In that case, we have to interpret it as a image. | |
164 | * | |
165 | * */ | |
166 | if (string_list_find_elem_prefix(ext_list, ".", file_ext)) | |
167 | attr.i = RARCH_PLAIN_FILE; | |
168 | else | |
169 | { | |
170 | bool is_compressed_file; | |
171 | if ((is_compressed_file = path_is_compressed_file(file_path))) | |
172 | attr.i = RARCH_COMPRESSED_ARCHIVE; | |
173 | ||
174 | if (ext_list && | |
175 | (!is_compressed_file || !include_compressed)) | |
176 | continue; | |
177 | } | |
178 | } | |
179 | ||
180 | if (!string_list_append(list, file_path, attr)) | |
181 | goto error; | |
182 | } | |
183 | ||
184 | retro_closedir(entry); | |
185 | ||
186 | return 0; | |
187 | ||
188 | error: | |
189 | if (entry) | |
190 | retro_closedir(entry); | |
191 | return -1; | |
192 | } | |
193 | ||
194 | /** | |
195 | * dir_list_append: | |
196 | * @list : existing list to append to. | |
197 | * @dir : directory path. | |
198 | * @ext : allowed extensions of file directory entries to include. | |
199 | * @include_dirs : include directories as part of the finished directory listing? | |
200 | * @include_hidden : include hidden files and directories as part of the finished directory listing? | |
201 | * @include_compressed : Only include files which match ext. Do not try to match compressed files, etc. | |
202 | * @recursive : list directory contents recursively | |
203 | * | |
204 | * Create a directory listing, appending to an existing list | |
205 | * | |
206 | * @return Returns true on success, otherwise false. | |
207 | **/ | |
208 | bool dir_list_append(struct string_list *list, | |
209 | const char *dir, | |
210 | const char *ext, bool include_dirs, | |
211 | bool include_hidden, bool include_compressed, | |
212 | bool recursive) | |
213 | { | |
214 | bool ret = false; | |
215 | struct string_list ext_list = {0}; | |
216 | struct string_list *ext_list_ptr = NULL; | |
217 | ||
218 | if (ext) | |
219 | { | |
220 | string_list_initialize(&ext_list); | |
221 | string_split_noalloc(&ext_list, ext, "|"); | |
222 | ext_list_ptr = &ext_list; | |
223 | } | |
224 | ret = dir_list_read(dir, list, ext_list_ptr, | |
225 | include_dirs, include_hidden, include_compressed, recursive) != -1; | |
226 | string_list_deinitialize(&ext_list); | |
227 | return ret; | |
228 | } | |
229 | ||
230 | /** | |
231 | * dir_list_new: | |
232 | * @dir : directory path. | |
233 | * @ext : allowed extensions of file directory entries to include. | |
234 | * @include_dirs : include directories as part of the finished directory listing? | |
235 | * @include_hidden : include hidden files and directories as part of the finished directory listing? | |
236 | * @include_compressed : Only include files which match ext. Do not try to match compressed files, etc. | |
237 | * @recursive : list directory contents recursively | |
238 | * | |
239 | * Create a directory listing. | |
240 | * | |
241 | * @return pointer to a directory listing of type 'struct string_list *' on success, | |
242 | * NULL in case of error. Has to be freed manually. | |
243 | **/ | |
244 | struct string_list *dir_list_new(const char *dir, | |
245 | const char *ext, bool include_dirs, | |
246 | bool include_hidden, bool include_compressed, | |
247 | bool recursive) | |
248 | { | |
249 | struct string_list *list = string_list_new(); | |
250 | ||
251 | if (!list) | |
252 | return NULL; | |
253 | ||
254 | if (!dir_list_append(list, dir, ext, include_dirs, | |
255 | include_hidden, include_compressed, recursive)) | |
256 | { | |
257 | string_list_free(list); | |
258 | return NULL; | |
259 | } | |
260 | ||
261 | return list; | |
262 | } | |
263 | ||
264 | /** | |
265 | * dir_list_initialize: | |
266 | * | |
267 | * NOTE: @list must zero initialised before | |
268 | * calling this function, otherwise UB. | |
269 | **/ | |
270 | bool dir_list_initialize(struct string_list *list, | |
271 | const char *dir, | |
272 | const char *ext, bool include_dirs, | |
273 | bool include_hidden, bool include_compressed, | |
274 | bool recursive) | |
275 | { | |
276 | if (list && string_list_initialize(list)) | |
277 | return dir_list_append(list, dir, ext, include_dirs, | |
278 | include_hidden, include_compressed, recursive); | |
279 | return false; | |
280 | } |