/* * Copyright 2008-2013 Various Authors * Copyright 2004-2005 Timo Hirvonen * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "browser.h" #include "load_dir.h" #include "cmus.h" #include "xmalloc.h" #include "ui_curses.h" #include "file.h" #include "misc.h" #include "options.h" #include "uchar.h" #include #include #include #include #include #include #include struct window *browser_win; struct searchable *browser_searchable; char *browser_dir; static LIST_HEAD(browser_head); static inline void browser_entry_to_iter(struct browser_entry *e, struct iter *iter) { iter->data0 = &browser_head; iter->data1 = e; iter->data2 = NULL; } /* filter out names starting with '.' except '..' */ static int normal_filter(const char *name, const struct stat *s) { if (name[0] == '.') { if (name[1] == '.' && name[2] == 0) return 1; return 0; } if (S_ISDIR(s->st_mode)) return 1; return cmus_is_supported(name); } /* filter out '.' */ static int hidden_filter(const char *name, const struct stat *s) { if (name[0] == '.' && name[1] == 0) return 0; return 1; } /* only works for BROWSER_ENTRY_DIR and BROWSER_ENTRY_FILE */ static int entry_cmp(const struct browser_entry *a, const struct browser_entry *b) { if (a->type == BROWSER_ENTRY_DIR) { if (b->type == BROWSER_ENTRY_FILE) return -1; if (!strcmp(a->name, "../")) return -1; if (!strcmp(b->name, "../")) return 1; return strcmp(a->name, b->name); } if (b->type == BROWSER_ENTRY_DIR) return 1; return strcmp(a->name, b->name); } static char *fullname(const char *path, const char *name) { int l1, l2; char *full; l1 = strlen(path); l2 = strlen(name); if (path[l1 - 1] == '/') l1--; full = xnew(char, l1 + 1 + l2 + 1); memcpy(full, path, l1); full[l1] = '/'; memcpy(full + l1 + 1, name, l2 + 1); return full; } static void free_browser_list(void) { struct list_head *item; item = browser_head.next; while (item != &browser_head) { struct list_head *next = item->next; struct browser_entry *entry; entry = list_entry(item, struct browser_entry, node); free(entry); item = next; } list_init(&browser_head); } static int add_pl_line(void *data, const char *line) { struct browser_entry *e; int name_size = strlen(line) + 1; e = xmalloc(sizeof(struct browser_entry) + name_size); memcpy(e->name, line, name_size); e->type = BROWSER_ENTRY_PLLINE; list_add_tail(&e->node, &browser_head); return 0; } static int do_browser_load(const char *name) { struct stat st; if (stat(name, &st)) return -1; if (S_ISREG(st.st_mode) && cmus_is_playlist(name)) { char *buf; ssize_t size; buf = mmap_file(name, &size); if (size == -1) return -1; free_browser_list(); if (buf) { struct browser_entry *parent_dir_e = xmalloc(sizeof(struct browser_entry) + 4); strcpy(parent_dir_e->name, "../"); parent_dir_e->type = BROWSER_ENTRY_DIR; list_add_tail(&parent_dir_e->node, &browser_head); cmus_playlist_for_each(buf, size, 0, add_pl_line, NULL); munmap(buf, size); } } else if (S_ISDIR(st.st_mode)) { int (*filter)(const char *, const struct stat *) = normal_filter; struct directory dir; const char *str; int root = !strcmp(name, "/"); if (show_hidden) filter = hidden_filter; if (dir_open(&dir, name)) return -1; free_browser_list(); while ((str = dir_read(&dir))) { struct browser_entry *e; struct list_head *item; int len; if (!filter(str, &dir.st)) continue; /* ignore .. if we are in the root dir */ if (root && !strcmp(str, "..")) continue; len = strlen(str); e = xmalloc(sizeof(struct browser_entry) + len + 2); e->type = BROWSER_ENTRY_FILE; memcpy(e->name, str, len); if (S_ISDIR(dir.st.st_mode)) { e->type = BROWSER_ENTRY_DIR; e->name[len++] = '/'; } e->name[len] = 0; item = browser_head.prev; while (item != &browser_head) { struct browser_entry *other; other = container_of(item, struct browser_entry, node); if (entry_cmp(e, other) >= 0) break; item = item->prev; } /* add after item */ list_add(&e->node, item); } dir_close(&dir); /* try to update currect working directory */ if (chdir(name)) return -1; } else { errno = ENOTDIR; return -1; } return 0; } static int browser_load(const char *name) { int rc; rc = do_browser_load(name); if (rc) return rc; window_set_contents(browser_win, &browser_head); free(browser_dir); browser_dir = xstrdup(name); return 0; } static GENERIC_ITER_PREV(browser_get_prev, struct browser_entry, node) static GENERIC_ITER_NEXT(browser_get_next, struct browser_entry, node) static int browser_search_get_current(void *data, struct iter *iter, enum search_direction dir) { return window_get_sel(browser_win, iter); } static int browser_search_matches(void *data, struct iter *iter, const char *text) { char **words = get_words(text); int matched = 0; if (words[0] != NULL) { struct browser_entry *e; int i; e = iter_to_browser_entry(iter); for (i = 0; ; i++) { if (words[i] == NULL) { window_set_sel(browser_win, iter); matched = 1; break; } if (u_strcasestr_filename(e->name, words[i]) == NULL) break; } } free_str_array(words); return matched; } static const struct searchable_ops browser_search_ops = { .get_prev = browser_get_prev, .get_next = browser_get_next, .get_current = browser_search_get_current, .matches = browser_search_matches }; void browser_init(void) { struct iter iter; char cwd[1024]; char *dir; if (getcwd(cwd, sizeof(cwd)) == NULL) { dir = xstrdup("/"); } else { dir = xstrdup(cwd); } if (do_browser_load(dir)) { free(dir); do_browser_load("/"); browser_dir = xstrdup("/"); } else { browser_dir = dir; } browser_win = window_new(browser_get_prev, browser_get_next); window_set_contents(browser_win, &browser_head); window_changed(browser_win); iter.data0 = &browser_head; iter.data1 = NULL; iter.data2 = NULL; browser_searchable = searchable_new(NULL, &iter, &browser_search_ops); } void browser_exit(void) { searchable_free(browser_searchable); free_browser_list(); window_free(browser_win); free(browser_dir); } int browser_chdir(const char *dir) { if (browser_load(dir)) { } return 0; } void browser_up(void) { char *new, *ptr, *pos; struct browser_entry *e; int len; if (strcmp(browser_dir, "/") == 0) return; ptr = strrchr(browser_dir, '/'); if (ptr == browser_dir) { new = xstrdup("/"); } else { new = xstrndup(browser_dir, ptr - browser_dir); } /* remember old position */ ptr++; len = strlen(ptr); pos = xstrdup(ptr); errno = 0; if (browser_load(new)) { if (errno == ENOENT) { free(pos); free(browser_dir); browser_dir = new; browser_up(); return; } error_msg("could not open directory '%s': %s\n", new, strerror(errno)); free(new); free(pos); return; } free(new); /* select old position */ list_for_each_entry(e, &browser_head, node) { if (strncmp(e->name, pos, len) == 0 && (e->name[len] == '/' || e->name[len] == '\0')) { struct iter iter; browser_entry_to_iter(e, &iter); window_set_sel(browser_win, &iter); break; } } free(pos); } static void browser_cd(const char *dir) { char *new; int len; if (strcmp(dir, "../") == 0) { browser_up(); return; } new = fullname(browser_dir, dir); len = strlen(new); if (new[len - 1] == '/') new[len - 1] = 0; if (browser_load(new)) error_msg("could not open directory '%s': %s\n", dir, strerror(errno)); free(new); } static void browser_cd_playlist(const char *filename) { if (browser_load(filename)) error_msg("could not read playlist '%s': %s\n", filename, strerror(errno)); } void browser_enter(void) { struct browser_entry *e; struct iter sel; int len; if (!window_get_sel(browser_win, &sel)) return; e = iter_to_browser_entry(&sel); len = strlen(e->name); if (len == 0) return; if (e->type == BROWSER_ENTRY_DIR) { browser_cd(e->name); } else { if (e->type == BROWSER_ENTRY_PLLINE) { cmus_play_file(e->name); } else { char *filename; filename = fullname(browser_dir, e->name); if (cmus_is_playlist(filename)) { browser_cd_playlist(filename); } else { cmus_play_file(filename); } free(filename); } } } char *browser_get_sel(void) { struct browser_entry *e; struct iter sel; if (!window_get_sel(browser_win, &sel)) return NULL; e = iter_to_browser_entry(&sel); if (e->type == BROWSER_ENTRY_PLLINE) return xstrdup(e->name); return fullname(browser_dir, e->name); } void browser_delete(void) { struct browser_entry *e; struct iter sel; int len; if (!window_get_sel(browser_win, &sel)) return; e = iter_to_browser_entry(&sel); len = strlen(e->name); if (len == 0) return; if (e->type == BROWSER_ENTRY_FILE) { char *name; name = fullname(browser_dir, e->name); if (yes_no_query("Delete file '%s'? [y/N]", e->name) == UI_QUERY_ANSWER_YES) { if (unlink(name) == -1) { error_msg("deleting '%s': %s", e->name, strerror(errno)); } else { window_row_vanishes(browser_win, &sel); list_del(&e->node); free(e); } } free(name); } } void browser_reload(void) { char *tmp = xstrdup(browser_dir); char *sel = NULL; struct iter iter; struct browser_entry *e; /* remember selection */ if (window_get_sel(browser_win, &iter)) { e = iter_to_browser_entry(&iter); sel = xstrdup(e->name); } /* have to use tmp */ if (browser_load(tmp)) { error_msg("could not update contents '%s': %s\n", tmp, strerror(errno)); free(tmp); free(sel); return; } if (sel) { /* set selection */ list_for_each_entry(e, &browser_head, node) { if (strcmp(e->name, sel) == 0) { browser_entry_to_iter(e, &iter); window_set_sel(browser_win, &iter); break; } } } free(tmp); free(sel); }