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