1 /*
2 * Copyright 2008-2013 Various Authors
3 * Copyright 2004-2005 Timo Hirvonen
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "browser.h"
20 #include "load_dir.h"
21 #include "cmus.h"
22 #include "xmalloc.h"
23 #include "ui_curses.h"
24 #include "file.h"
25 #include "misc.h"
26 #include "options.h"
27 #include "uchar.h"
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <dirent.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <sys/mman.h>
36
37 struct window *browser_win;
38 struct searchable *browser_searchable;
39 char *browser_dir;
40
41 static LIST_HEAD(browser_head);
42
browser_entry_to_iter(struct browser_entry * e,struct iter * iter)43 static inline void browser_entry_to_iter(struct browser_entry *e, struct iter *iter)
44 {
45 iter->data0 = &browser_head;
46 iter->data1 = e;
47 iter->data2 = NULL;
48 }
49
50 /* filter out names starting with '.' except '..' */
normal_filter(const char * name,const struct stat * s)51 static int normal_filter(const char *name, const struct stat *s)
52 {
53 if (name[0] == '.') {
54 if (name[1] == '.' && name[2] == 0)
55 return 1;
56 return 0;
57 }
58 if (S_ISDIR(s->st_mode))
59 return 1;
60 return cmus_is_supported(name);
61 }
62
63 /* filter out '.' */
hidden_filter(const char * name,const struct stat * s)64 static int hidden_filter(const char *name, const struct stat *s)
65 {
66 if (name[0] == '.' && name[1] == 0)
67 return 0;
68 return 1;
69 }
70
71 /* only works for BROWSER_ENTRY_DIR and BROWSER_ENTRY_FILE */
entry_cmp(const struct browser_entry * a,const struct browser_entry * b)72 static int entry_cmp(const struct browser_entry *a, const struct browser_entry *b)
73 {
74 if (a->type == BROWSER_ENTRY_DIR) {
75 if (b->type == BROWSER_ENTRY_FILE)
76 return -1;
77 if (!strcmp(a->name, "../"))
78 return -1;
79 if (!strcmp(b->name, "../"))
80 return 1;
81 return strcmp(a->name, b->name);
82 }
83 if (b->type == BROWSER_ENTRY_DIR)
84 return 1;
85 return strcmp(a->name, b->name);
86 }
87
fullname(const char * path,const char * name)88 static char *fullname(const char *path, const char *name)
89 {
90 int l1, l2;
91 char *full;
92
93 l1 = strlen(path);
94 l2 = strlen(name);
95 if (path[l1 - 1] == '/')
96 l1--;
97 full = xnew(char, l1 + 1 + l2 + 1);
98 memcpy(full, path, l1);
99 full[l1] = '/';
100 memcpy(full + l1 + 1, name, l2 + 1);
101 return full;
102 }
103
free_browser_list(void)104 static void free_browser_list(void)
105 {
106 struct list_head *item;
107
108 item = browser_head.next;
109 while (item != &browser_head) {
110 struct list_head *next = item->next;
111 struct browser_entry *entry;
112
113 entry = list_entry(item, struct browser_entry, node);
114 free(entry);
115 item = next;
116 }
117 list_init(&browser_head);
118 }
119
add_pl_line(void * data,const char * line)120 static int add_pl_line(void *data, const char *line)
121 {
122 struct browser_entry *e;
123 int name_size = strlen(line) + 1;
124
125 e = xmalloc(sizeof(struct browser_entry) + name_size);
126 memcpy(e->name, line, name_size);
127 e->type = BROWSER_ENTRY_PLLINE;
128 list_add_tail(&e->node, &browser_head);
129 return 0;
130 }
131
do_browser_load(const char * name)132 static int do_browser_load(const char *name)
133 {
134 struct stat st;
135
136 if (stat(name, &st))
137 return -1;
138
139 if (S_ISREG(st.st_mode) && cmus_is_playlist(name)) {
140 char *buf;
141 ssize_t size;
142
143 buf = mmap_file(name, &size);
144 if (size == -1)
145 return -1;
146
147 free_browser_list();
148
149 if (buf) {
150 struct browser_entry *parent_dir_e = xmalloc(sizeof(struct browser_entry) + 4);
151 strcpy(parent_dir_e->name, "../");
152 parent_dir_e->type = BROWSER_ENTRY_DIR;
153 list_add_tail(&parent_dir_e->node, &browser_head);
154
155 cmus_playlist_for_each(buf, size, 0, add_pl_line, NULL);
156 munmap(buf, size);
157 }
158 } else if (S_ISDIR(st.st_mode)) {
159 int (*filter)(const char *, const struct stat *) = normal_filter;
160 struct directory dir;
161 const char *str;
162 int root = !strcmp(name, "/");
163
164 if (show_hidden)
165 filter = hidden_filter;
166
167 if (dir_open(&dir, name))
168 return -1;
169
170 free_browser_list();
171 while ((str = dir_read(&dir))) {
172 struct browser_entry *e;
173 struct list_head *item;
174 int len;
175
176 if (!filter(str, &dir.st))
177 continue;
178
179 /* ignore .. if we are in the root dir */
180 if (root && !strcmp(str, ".."))
181 continue;
182
183 len = strlen(str);
184 e = xmalloc(sizeof(struct browser_entry) + len + 2);
185 e->type = BROWSER_ENTRY_FILE;
186 memcpy(e->name, str, len);
187 if (S_ISDIR(dir.st.st_mode)) {
188 e->type = BROWSER_ENTRY_DIR;
189 e->name[len++] = '/';
190 }
191 e->name[len] = 0;
192
193 item = browser_head.prev;
194 while (item != &browser_head) {
195 struct browser_entry *other;
196
197 other = container_of(item, struct browser_entry, node);
198 if (entry_cmp(e, other) >= 0)
199 break;
200 item = item->prev;
201 }
202 /* add after item */
203 list_add(&e->node, item);
204 }
205 dir_close(&dir);
206
207 /* try to update currect working directory */
208 if (chdir(name))
209 return -1;
210 } else {
211 errno = ENOTDIR;
212 return -1;
213 }
214 return 0;
215 }
216
browser_load(const char * name)217 static int browser_load(const char *name)
218 {
219 int rc;
220
221 rc = do_browser_load(name);
222 if (rc)
223 return rc;
224
225 window_set_contents(browser_win, &browser_head);
226 free(browser_dir);
227 browser_dir = xstrdup(name);
228 return 0;
229 }
230
GENERIC_ITER_PREV(browser_get_prev,struct browser_entry,node)231 static GENERIC_ITER_PREV(browser_get_prev, struct browser_entry, node)
232 static GENERIC_ITER_NEXT(browser_get_next, struct browser_entry, node)
233
234 static int browser_search_get_current(void *data, struct iter *iter)
235 {
236 return window_get_sel(browser_win, iter);
237 }
238
browser_search_matches(void * data,struct iter * iter,const char * text)239 static int browser_search_matches(void *data, struct iter *iter, const char *text)
240 {
241 char **words = get_words(text);
242 int matched = 0;
243
244 if (words[0] != NULL) {
245 struct browser_entry *e;
246 int i;
247
248 e = iter_to_browser_entry(iter);
249 for (i = 0; ; i++) {
250 if (words[i] == NULL) {
251 window_set_sel(browser_win, iter);
252 matched = 1;
253 break;
254 }
255 if (u_strcasestr_filename(e->name, words[i]) == NULL)
256 break;
257 }
258 }
259 free_str_array(words);
260 return matched;
261 }
262
263 static const struct searchable_ops browser_search_ops = {
264 .get_prev = browser_get_prev,
265 .get_next = browser_get_next,
266 .get_current = browser_search_get_current,
267 .matches = browser_search_matches
268 };
269
browser_init(void)270 void browser_init(void)
271 {
272 struct iter iter;
273 char cwd[1024];
274 char *dir;
275
276 if (getcwd(cwd, sizeof(cwd)) == NULL) {
277 dir = xstrdup("/");
278 } else {
279 dir = xstrdup(cwd);
280 }
281 if (do_browser_load(dir)) {
282 free(dir);
283 do_browser_load("/");
284 browser_dir = xstrdup("/");
285 } else {
286 browser_dir = dir;
287 }
288
289 browser_win = window_new(browser_get_prev, browser_get_next);
290 window_set_contents(browser_win, &browser_head);
291 window_changed(browser_win);
292
293 iter.data0 = &browser_head;
294 iter.data1 = NULL;
295 iter.data2 = NULL;
296 browser_searchable = searchable_new(NULL, &iter, &browser_search_ops);
297 }
298
browser_exit(void)299 void browser_exit(void)
300 {
301 searchable_free(browser_searchable);
302 free_browser_list();
303 window_free(browser_win);
304 free(browser_dir);
305 }
306
browser_chdir(const char * dir)307 int browser_chdir(const char *dir)
308 {
309 if (browser_load(dir)) {
310 }
311 return 0;
312 }
313
browser_up(void)314 void browser_up(void)
315 {
316 char *new, *ptr, *pos;
317 struct browser_entry *e;
318 int len;
319
320 if (strcmp(browser_dir, "/") == 0)
321 return;
322
323 ptr = strrchr(browser_dir, '/');
324 if (ptr == browser_dir) {
325 new = xstrdup("/");
326 } else {
327 new = xstrndup(browser_dir, ptr - browser_dir);
328 }
329
330 /* remember old position */
331 ptr++;
332 len = strlen(ptr);
333 pos = xstrdup(ptr);
334
335 errno = 0;
336 if (browser_load(new)) {
337 if (errno == ENOENT) {
338 free(pos);
339 free(browser_dir);
340 browser_dir = new;
341 browser_up();
342 return;
343 }
344 error_msg("could not open directory '%s': %s\n", new, strerror(errno));
345 free(new);
346 free(pos);
347 return;
348 }
349 free(new);
350
351 /* select old position */
352 list_for_each_entry(e, &browser_head, node) {
353 if (strncmp(e->name, pos, len) == 0 &&
354 (e->name[len] == '/' || e->name[len] == '\0')) {
355 struct iter iter;
356
357 browser_entry_to_iter(e, &iter);
358 window_set_sel(browser_win, &iter);
359 break;
360 }
361 }
362 free(pos);
363 }
364
browser_cd(const char * dir)365 static void browser_cd(const char *dir)
366 {
367 char *new;
368 int len;
369
370 if (strcmp(dir, "../") == 0) {
371 browser_up();
372 return;
373 }
374
375 new = fullname(browser_dir, dir);
376 len = strlen(new);
377 if (new[len - 1] == '/')
378 new[len - 1] = 0;
379 if (browser_load(new))
380 error_msg("could not open directory '%s': %s\n", dir, strerror(errno));
381 free(new);
382 }
383
browser_cd_playlist(const char * filename)384 static void browser_cd_playlist(const char *filename)
385 {
386 if (browser_load(filename))
387 error_msg("could not read playlist '%s': %s\n", filename, strerror(errno));
388 }
389
browser_enter(void)390 void browser_enter(void)
391 {
392 struct browser_entry *e;
393 struct iter sel;
394 int len;
395
396 if (!window_get_sel(browser_win, &sel))
397 return;
398 e = iter_to_browser_entry(&sel);
399 len = strlen(e->name);
400 if (len == 0)
401 return;
402 if (e->type == BROWSER_ENTRY_DIR) {
403 browser_cd(e->name);
404 } else {
405 if (e->type == BROWSER_ENTRY_PLLINE) {
406 cmus_play_file(e->name);
407 } else {
408 char *filename;
409
410 filename = fullname(browser_dir, e->name);
411 if (cmus_is_playlist(filename)) {
412 browser_cd_playlist(filename);
413 } else {
414 cmus_play_file(filename);
415 }
416 free(filename);
417 }
418 }
419 }
420
browser_get_sel(void)421 char *browser_get_sel(void)
422 {
423 struct browser_entry *e;
424 struct iter sel;
425
426 if (!window_get_sel(browser_win, &sel))
427 return NULL;
428
429 e = iter_to_browser_entry(&sel);
430 if (e->type == BROWSER_ENTRY_PLLINE)
431 return xstrdup(e->name);
432
433 return fullname(browser_dir, e->name);
434 }
435
browser_delete(void)436 void browser_delete(void)
437 {
438 struct browser_entry *e;
439 struct iter sel;
440 int len;
441
442 if (!window_get_sel(browser_win, &sel))
443 return;
444 e = iter_to_browser_entry(&sel);
445 len = strlen(e->name);
446 if (len == 0)
447 return;
448 if (e->type == BROWSER_ENTRY_FILE) {
449 char *name;
450
451 name = fullname(browser_dir, e->name);
452 if (yes_no_query("Delete file '%s'? [y/N]", e->name) == UI_QUERY_ANSWER_YES) {
453 if (unlink(name) == -1) {
454 error_msg("deleting '%s': %s", e->name, strerror(errno));
455 } else {
456 window_row_vanishes(browser_win, &sel);
457 list_del(&e->node);
458 free(e);
459 }
460 }
461 free(name);
462 }
463 }
464
browser_reload(void)465 void browser_reload(void)
466 {
467 char *tmp = xstrdup(browser_dir);
468 char *sel = NULL;
469 struct iter iter;
470 struct browser_entry *e;
471
472 /* remember selection */
473 if (window_get_sel(browser_win, &iter)) {
474 e = iter_to_browser_entry(&iter);
475 sel = xstrdup(e->name);
476 }
477
478 /* have to use tmp */
479 if (browser_load(tmp)) {
480 error_msg("could not update contents '%s': %s\n", tmp, strerror(errno));
481 free(tmp);
482 free(sel);
483 return;
484 }
485
486 if (sel) {
487 /* set selection */
488 list_for_each_entry(e, &browser_head, node) {
489 if (strcmp(e->name, sel) == 0) {
490 browser_entry_to_iter(e, &iter);
491 window_set_sel(browser_win, &iter);
492 break;
493 }
494 }
495 }
496
497 free(tmp);
498 free(sel);
499 }
500