git subrepo pull --force deps/lightrec
[pcsx_rearmed.git] / deps / libretro-common / lists / dir_list.c
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 }