Handle streams separately in tree_add_track()
[cmus.git] / browser.c
blobd552b43fc41d3ba63c21b917f38912b0fbe557c3
1 /*
2 * Copyright 2004-2005 Timo Hirvonen
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
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., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
20 #include "browser.h"
21 #include "load_dir.h"
22 #include "cmus.h"
23 #include "xmalloc.h"
24 #include "xstrjoin.h"
25 #include "ui_curses.h"
26 #include "file.h"
27 #include "misc.h"
28 #include "options.h"
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <dirent.h>
34 #include <string.h>
35 #include <errno.h>
37 struct window *browser_win;
38 struct searchable *browser_searchable;
39 char *browser_dir;
41 static LIST_HEAD(browser_head);
43 static inline void browser_entry_to_iter(struct browser_entry *e, struct iter *iter)
45 iter->data0 = &browser_head;
46 iter->data1 = e;
47 iter->data2 = NULL;
50 /* filter out names starting with '.' except '..' */
51 static int normal_filter(const char *name, const struct stat *s)
53 if (name[0] == '.') {
54 if (name[1] == '.' && name[2] == 0)
55 return 1;
56 return 0;
58 if (S_ISDIR(s->st_mode))
59 return 1;
60 return cmus_is_supported(name);
63 /* filter out '.' */
64 static int hidden_filter(const char *name, const struct stat *s)
66 if (name[0] == '.' && name[1] == 0)
67 return 0;
68 return 1;
71 /* only works for BROWSER_ENTRY_DIR and BROWSER_ENTRY_FILE */
72 static int entry_cmp(const struct browser_entry *a, const struct browser_entry *b)
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);
83 if (b->type == BROWSER_ENTRY_DIR)
84 return 1;
85 return strcmp(a->name, b->name);
88 static char *fullname(const char *path, const char *name)
90 int l1, l2;
91 char *full;
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;
104 static void free_browser_list(void)
106 struct list_head *item;
108 item = browser_head.next;
109 while (item != &browser_head) {
110 struct list_head *next = item->next;
111 struct browser_entry *entry;
113 entry = list_entry(item, struct browser_entry, node);
114 free(entry);
115 item = next;
117 list_init(&browser_head);
120 static int add_pl_line(void *data, const char *line)
122 struct browser_entry *e;
123 int name_size = strlen(line) + 1;
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;
132 static int do_browser_load(const char *name)
134 struct stat st;
136 if (stat(name, &st))
137 return -1;
139 if (S_ISREG(st.st_mode) && cmus_is_playlist(name)) {
140 char *buf;
141 int size;
143 buf = mmap_file(name, &size);
144 if (size == -1)
145 return -1;
147 free_browser_list();
149 if (buf) {
150 cmus_playlist_for_each(buf, size, 0, add_pl_line, NULL);
151 munmap(buf, size);
153 } else if (S_ISDIR(st.st_mode)) {
154 int (*filter)(const char *, const struct stat *) = normal_filter;
155 struct directory dir;
156 const char *str;
157 int root = !strcmp(name, "/");
159 if (show_hidden)
160 filter = hidden_filter;
162 if (dir_open(&dir, name))
163 return -1;
165 free_browser_list();
166 while ((str = dir_read(&dir))) {
167 struct browser_entry *e;
168 struct list_head *item;
169 int len;
171 if (!filter(str, &dir.st))
172 continue;
174 /* ignore .. if we are in the root dir */
175 if (root && !strcmp(str, ".."))
176 continue;
178 len = strlen(str);
179 e = xmalloc(sizeof(struct browser_entry) + len + 2);
180 e->type = BROWSER_ENTRY_FILE;
181 memcpy(e->name, str, len);
182 if (S_ISDIR(dir.st.st_mode)) {
183 e->type = BROWSER_ENTRY_DIR;
184 e->name[len++] = '/';
186 e->name[len] = 0;
188 item = browser_head.prev;
189 while (item != &browser_head) {
190 struct browser_entry *other;
192 other = container_of(item, struct browser_entry, node);
193 if (entry_cmp(e, other) >= 0)
194 break;
195 item = item->prev;
197 /* add after item */
198 list_add(&e->node, item);
200 dir_close(&dir);
202 /* try to update currect working directory */
203 chdir(name);
204 } else {
205 errno = ENOTDIR;
206 return -1;
208 return 0;
211 static int browser_load(const char *name)
213 int rc;
215 rc = do_browser_load(name);
216 if (rc)
217 return rc;
219 window_set_contents(browser_win, &browser_head);
220 free(browser_dir);
221 browser_dir = xstrdup(name);
222 return 0;
225 static GENERIC_ITER_PREV(browser_get_prev, struct browser_entry, node)
226 static GENERIC_ITER_NEXT(browser_get_next, struct browser_entry, node)
228 static int browser_search_get_current(void *data, struct iter *iter)
230 return window_get_sel(browser_win, iter);
233 static int browser_search_matches(void *data, struct iter *iter, const char *text)
235 char **words = get_words(text);
236 int matched = 0;
238 if (words[0] != NULL) {
239 struct browser_entry *e;
240 int i;
242 e = iter_to_browser_entry(iter);
243 for (i = 0; ; i++) {
244 if (words[i] == NULL) {
245 window_set_sel(browser_win, iter);
246 matched = 1;
247 break;
249 if (u_strcasestr_filename(e->name, words[i]) == NULL)
250 break;
253 free_str_array(words);
254 return matched;
257 static const struct searchable_ops browser_search_ops = {
258 .get_prev = browser_get_prev,
259 .get_next = browser_get_next,
260 .get_current = browser_search_get_current,
261 .matches = browser_search_matches
264 void browser_init(void)
266 struct iter iter;
267 char cwd[1024];
268 char *dir;
270 if (getcwd(cwd, sizeof(cwd)) == NULL) {
271 dir = xstrdup("/");
272 } else {
273 dir = xstrdup(cwd);
275 if (do_browser_load(dir)) {
276 free(dir);
277 do_browser_load("/");
278 browser_dir = xstrdup("/");
279 } else {
280 browser_dir = dir;
283 browser_win = window_new(browser_get_prev, browser_get_next);
284 window_set_contents(browser_win, &browser_head);
285 window_changed(browser_win);
287 iter.data0 = &browser_head;
288 iter.data1 = NULL;
289 iter.data2 = NULL;
290 browser_searchable = searchable_new(NULL, &iter, &browser_search_ops);
293 void browser_exit(void)
295 searchable_free(browser_searchable);
296 free_browser_list();
297 window_free(browser_win);
298 free(browser_dir);
301 int browser_chdir(const char *dir)
303 if (browser_load(dir)) {
305 return 0;
308 void browser_up(void)
310 char *new, *ptr, *pos;
311 struct browser_entry *e;
312 int len;
314 if (strcmp(browser_dir, "/") == 0)
315 return;
317 ptr = strrchr(browser_dir, '/');
318 if (ptr == browser_dir) {
319 new = xstrdup("/");
320 } else {
321 new = xstrndup(browser_dir, ptr - browser_dir);
324 /* remember last position */
325 ptr++;
326 len = strlen(ptr);
327 pos = xnew(char, len + 2);
328 memcpy(pos, ptr, len);
329 pos[len] = '/';
330 pos[len + 1] = 0;
332 if (browser_load(new)) {
333 error_msg("could not open directory '%s': %s\n", new, strerror(errno));
334 free(new);
335 return;
337 free(new);
339 /* select */
340 list_for_each_entry(e, &browser_head, node) {
341 if (strcmp(e->name, pos) == 0) {
342 struct iter iter;
344 browser_entry_to_iter(e, &iter);
345 window_set_sel(browser_win, &iter);
346 break;
349 free(pos);
352 static void browser_cd(const char *dir)
354 char *new;
355 int len;
357 if (strcmp(dir, "../") == 0) {
358 browser_up();
359 return;
362 new = fullname(browser_dir, dir);
363 len = strlen(new);
364 if (new[len - 1] == '/')
365 new[len - 1] = 0;
366 if (browser_load(new))
367 error_msg("could not open directory '%s': %s\n", dir, strerror(errno));
368 free(new);
371 static void browser_cd_playlist(const char *filename)
373 if (browser_load(filename))
374 error_msg("could not read playlist '%s': %s\n", filename, strerror(errno));
377 void browser_enter(void)
379 struct browser_entry *e;
380 struct iter sel;
381 int len;
383 if (!window_get_sel(browser_win, &sel))
384 return;
385 e = iter_to_browser_entry(&sel);
386 len = strlen(e->name);
387 if (len == 0)
388 return;
389 if (e->type == BROWSER_ENTRY_DIR) {
390 browser_cd(e->name);
391 } else {
392 if (e->type == BROWSER_ENTRY_PLLINE) {
393 cmus_play_file(e->name);
394 } else {
395 char *filename;
397 filename = fullname(browser_dir, e->name);
398 if (cmus_is_playlist(filename)) {
399 browser_cd_playlist(filename);
400 } else {
401 cmus_play_file(filename);
403 free(filename);
408 char *browser_get_sel(void)
410 struct browser_entry *e;
411 struct iter sel;
413 if (!window_get_sel(browser_win, &sel))
414 return NULL;
416 e = iter_to_browser_entry(&sel);
417 if (e->type == BROWSER_ENTRY_PLLINE)
418 return xstrdup(e->name);
420 return fullname(browser_dir, e->name);
423 void browser_delete(void)
425 struct browser_entry *e;
426 struct iter sel;
427 int len;
429 if (!window_get_sel(browser_win, &sel))
430 return;
431 e = iter_to_browser_entry(&sel);
432 len = strlen(e->name);
433 if (len == 0)
434 return;
435 if (e->type == BROWSER_ENTRY_FILE) {
436 char *name;
438 name = fullname(browser_dir, e->name);
439 if (yes_no_query("Delete file '%s'? [y/N]", e->name)) {
440 if (unlink(name) == -1) {
441 error_msg("deleting '%s': %s", e->name, strerror(errno));
442 } else {
443 window_row_vanishes(browser_win, &sel);
444 list_del(&e->node);
445 free(e);
448 free(name);
452 void browser_reload(void)
454 char *tmp = xstrdup(browser_dir);
455 char *sel = NULL;
456 struct iter iter;
457 struct browser_entry *e;
459 /* remember selection */
460 if (window_get_sel(browser_win, &iter)) {
461 e = iter_to_browser_entry(&iter);
462 sel = xstrdup(e->name);
465 /* have to use tmp */
466 if (browser_load(tmp)) {
467 error_msg("could not update contents '%s': %s\n", tmp, strerror(errno));
468 free(tmp);
469 free(sel);
470 return;
473 if (sel) {
474 /* set selection */
475 list_for_each_entry(e, &browser_head, node) {
476 if (strcmp(e->name, sel) == 0) {
477 browser_entry_to_iter(e, &iter);
478 window_set_sel(browser_win, &iter);
479 break;
484 free(tmp);
485 free(sel);