diff options
Diffstat (limited to 'src/browser.cpp')
-rw-r--r-- | src/browser.cpp | 761 |
1 files changed, 0 insertions, 761 deletions
diff --git a/src/browser.cpp b/src/browser.cpp deleted file mode 100644 index 320597b6..00000000 --- a/src/browser.cpp +++ /dev/null @@ -1,761 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2008-2016 by Andrzej Rybczak * - * electricityispower@gmail.com * - * * - * 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, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ***************************************************************************/ - -#include <algorithm> -#include <boost/algorithm/string/predicate.hpp> -#include <boost/filesystem.hpp> -#include <boost/locale/conversion.hpp> -#include <time.h> - -#include "browser.h" -#include "charset.h" -#include "display.h" -#include "global.h" -#include "helpers.h" -#include "playlist.h" -#include "curses/menu_impl.h" -#include "screen_switcher.h" -#include "settings.h" -#include "status.h" -#include "statusbar.h" -#include "tag_editor.h" -#include "title.h" -#include "tags.h" -#include "helpers/song_iterator_maker.h" -#include "utility/comparators.h" -#include "utility/string.h" -#include "configuration.h" - -using Global::MainHeight; -using Global::MainStartY; -using Global::myScreen; - -namespace fs = boost::filesystem; -namespace ph = std::placeholders; - -Browser *myBrowser; - -namespace { - -std::set<std::string> lm_supported_extensions; - -std::string realPath(bool local_browser, std::string path); -bool isStringParentDirectory(const std::string &directory); -bool isItemParentDirectory(const MPD::Item &item); -bool isRootDirectory(const std::string &directory); -bool isHidden(const fs::directory_iterator &entry); -bool hasSupportedExtension(const fs::directory_entry &entry); -MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags); -void getLocalDirectory(std::vector<MPD::Item> &items, const std::string &directory); -void getLocalDirectoryRecursively(std::vector<MPD::Song> &songs, const std::string &directory); -void clearDirectory(const std::string &directory); - -std::string itemToString(const MPD::Item &item); -bool browserEntryMatcher(const Regex::Regex &rx, const MPD::Item &item, bool filter); - -} - -template <> -struct SongPropertiesExtractor<MPD::Item> -{ - template <typename ItemT> - auto &operator()(ItemT &item) const - { - auto s = item.value().type() == MPD::Item::Type::Song - ? &item.value().song() - : nullptr; - m_cache.assign(&item.properties(), s); - return m_cache; - } - -private: - mutable SongProperties m_cache; -}; - -SongIterator BrowserWindow::currentS() -{ - return makeSongIterator(current()); -} - -ConstSongIterator BrowserWindow::currentS() const -{ - return makeConstSongIterator(current()); -} - -SongIterator BrowserWindow::beginS() -{ - return makeSongIterator(begin()); -} - -ConstSongIterator BrowserWindow::beginS() const -{ - return makeConstSongIterator(begin()); -} - -SongIterator BrowserWindow::endS() -{ - return makeSongIterator(end()); -} - -ConstSongIterator BrowserWindow::endS() const -{ - return makeConstSongIterator(end()); -} - -std::vector<MPD::Song> BrowserWindow::getSelectedSongs() -{ - return {}; // TODO -} - -/**********************************************************************/ - -Browser::Browser() -: m_update_request(true) -, m_local_browser(false) -, m_scroll_beginning(0) -, m_current_directory("/") -{ - w = NC::Menu<MPD::Item>(0, MainStartY, COLS, MainHeight, Config.browser_display_mode == DisplayMode::Columns && Config.titles_visibility ? Display::Columns(COLS) : "", Config.main_color, NC::Border()); - w.setHighlightColor(Config.main_highlight_color); - w.cyclicScrolling(Config.use_cyclic_scrolling); - w.centeredCursor(Config.centered_cursor); - w.setSelectedPrefix(Config.selected_item_prefix); - w.setSelectedSuffix(Config.selected_item_suffix); - w.setItemDisplayer(std::bind(Display::Items, ph::_1, std::cref(w))); -} - -void Browser::resize() -{ - size_t x_offset, width; - getWindowResizeParams(x_offset, width); - w.resize(width, MainHeight); - w.moveTo(x_offset, MainStartY); - switch (Config.browser_display_mode) - { - case DisplayMode::Columns: - if (Config.titles_visibility) - { - w.setTitle(Display::Columns(w.getWidth())); - break; - } - case DisplayMode::Classic: - w.setTitle(""); - break; - } - hasToBeResized = 0; -} - -void Browser::switchTo() -{ - SwitchTo::execute(this); - markSongsInPlaylist(w); - drawHeader(); -} - -std::wstring Browser::title() -{ - std::wstring result = L"Browse: "; - result += Scroller(ToWString(m_current_directory), m_scroll_beginning, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length())); - return result; -} - -void Browser::update() -{ - if (m_update_request) - { - m_update_request = false; - bool directory_changed = false; - do - { - try - { - getDirectory(m_current_directory); - w.refresh(); - } - catch (MPD::ServerError &err) - { - // If current directory doesn't exist, try getting its - // parent until we either succeed or reach the root. - if (err.code() == MPD_SERVER_ERROR_NO_EXIST) - { - m_current_directory = getParentDirectory(m_current_directory); - directory_changed = true; - } - else - throw; - } - } - while (w.empty() && !inRootDirectory()); - if (directory_changed) - drawHeader(); - } -} - -void Browser::mouseButtonPressed(MEVENT me) -{ - if (w.empty() || !w.hasCoords(me.x, me.y) || size_t(me.y) >= w.size()) - return; - if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)) - { - w.Goto(me.y); - switch (w.current()->value().type()) - { - case MPD::Item::Type::Directory: - if (me.bstate & BUTTON1_PRESSED) - enterDirectory(); - else - addItemToPlaylist(false); - break; - case MPD::Item::Type::Playlist: - case MPD::Item::Type::Song: - { - bool play = me.bstate & BUTTON3_PRESSED; - addItemToPlaylist(play); - break; - } - } - } - else - Screen<WindowType>::mouseButtonPressed(me); -} - -/***********************************************************************/ - -bool Browser::allowsSearching() -{ - return true; -} - -const std::string &Browser::searchConstraint() -{ - return m_search_predicate.constraint(); -} - -void Browser::setSearchConstraint(const std::string &constraint) -{ - m_search_predicate = Regex::Filter<MPD::Item>( - constraint, - Config.regex_type, - std::bind(browserEntryMatcher, ph::_1, ph::_2, false)); -} - -void Browser::clearSearchConstraint() -{ - m_search_predicate.clear(); -} - -bool Browser::search(SearchDirection direction, bool wrap, bool skip_current) -{ - return ::search(w, m_search_predicate, direction, wrap, skip_current); -} - -/***********************************************************************/ - -bool Browser::allowsFiltering() -{ - return allowsSearching(); -} - -std::string Browser::currentFilter() -{ - std::string result; - if (auto pred = w.filterPredicate<Regex::Filter<MPD::Item>>()) - result = pred->constraint(); - return result; -} - -void Browser::applyFilter(const std::string &constraint) -{ - if (!constraint.empty()) - { - w.applyFilter(Regex::Filter<MPD::Item>( - constraint, - Config.regex_type, - std::bind(browserEntryMatcher, ph::_1, ph::_2, true))); - } - else - w.clearFilter(); -} - - -/***********************************************************************/ - -bool Browser::itemAvailable() -{ - return !w.empty() - // ignore parent directory - && !isParentDirectory(w.current()->value()); -} - -bool Browser::addItemToPlaylist(bool play) -{ - bool result = false; - - auto tryToPlay = [] { - // Cheap trick that might fail in presence of multiple - // clients modifying the playlist at the same time, but - // oh well, this approach correctly loads cue playlists - // and is much faster in general as it doesn't require - // fetching song data. - try - { - Mpd.Play(Status::State::playlistLength()); - } - catch (MPD::ServerError &e) - { - // If not bad index, rethrow. - if (e.code() != MPD_SERVER_ERROR_ARG) - throw; - } - }; - - const MPD::Item &item = w.current()->value(); - switch (item.type()) - { - case MPD::Item::Type::Directory: - { - if (m_local_browser) - { - std::vector<MPD::Song> songs; - getLocalDirectoryRecursively(songs, item.directory().path()); - result = addSongsToPlaylist(songs.begin(), songs.end(), play, -1); - } - else - { - Mpd.Add(item.directory().path()); - if (play) - tryToPlay(); - result = true; - } - Statusbar::printf("Directory \"%1%\" added%2%", - item.directory().path(), withErrors(result)); - break; - } - case MPD::Item::Type::Song: - result = addSongToPlaylist(item.song(), play); - break; - case MPD::Item::Type::Playlist: - Mpd.LoadPlaylist(item.playlist().path()); - if (play) - tryToPlay(); - Statusbar::printf("Playlist \"%1%\" loaded", item.playlist().path()); - result = true; - break; - } - return result; -} - -std::vector<MPD::Song> Browser::getSelectedSongs() -{ - std::vector<MPD::Song> songs; - auto item_handler = [this, &songs](const MPD::Item &item) { - switch (item.type()) - { - case MPD::Item::Type::Directory: - if (m_local_browser) - getLocalDirectoryRecursively(songs, item.directory().path()); - else - { - std::copy( - std::make_move_iterator(Mpd.GetDirectoryRecursive(item.directory().path())), - std::make_move_iterator(MPD::SongIterator()), - std::back_inserter(songs) - ); - } - break; - case MPD::Item::Type::Song: - songs.push_back(item.song()); - break; - case MPD::Item::Type::Playlist: - std::copy( - std::make_move_iterator(Mpd.GetPlaylistContent(item.playlist().path())), - std::make_move_iterator(MPD::SongIterator()), - std::back_inserter(songs) - ); - break; - } - }; - for (const auto &item : w) - if (item.isSelected()) - item_handler(item.value()); - // if no item is selected, add current one - if (songs.empty() && !w.empty()) - item_handler(w.current()->value()); - return songs; -} - -/***********************************************************************/ - -bool Browser::inRootDirectory() -{ - return isRootDirectory(m_current_directory); -} - -bool Browser::isParentDirectory(const MPD::Item &item) -{ - return isItemParentDirectory(item); -} - -const std::string& Browser::currentDirectory() -{ - return m_current_directory; -} - -void Browser::locateSong(const MPD::Song &s) -{ - if (s.getDirectory().empty()) - throw std::runtime_error("Song's directory is empty"); - - m_local_browser = !s.isFromDatabase(); - - if (myScreen != this) - switchTo(); - - w.clearFilter(); - - // change to relevant directory - if (m_current_directory != s.getDirectory()) - { - getDirectory(s.getDirectory()); - drawHeader(); - } - - // highlight the item - auto begin = w.beginV(), end = w.endV(); - auto it = std::find(begin, end, MPD::Item(s)); - if (it != end) - w.highlight(it-begin); -} - -bool Browser::enterDirectory() -{ - bool result = false; - if (!w.empty()) - { - const auto &item = w.current()->value(); - if (item.type() == MPD::Item::Type::Directory) - { - getDirectory(item.directory().path()); - drawHeader(); - result = true; - } - } - return result; -} - -void Browser::getDirectory(std::string directory) -{ - ScopedUnfilteredMenu<MPD::Item> sunfilter(ReapplyFilter::Yes, w); - - m_scroll_beginning = 0; - w.clear(); - - // reset the position if we change directories - if (m_current_directory != directory) - w.reset(); - - // check if it's a parent directory - if (isStringParentDirectory(directory)) - { - directory.resize(directory.length()-3); - directory = getParentDirectory(directory); - } - // when we go down to root, it can be empty - if (directory.empty()) - directory = "/"; - - std::vector<MPD::Item> items; - if (m_local_browser) - getLocalDirectory(items, directory); - else - { - std::copy( - std::make_move_iterator(Mpd.GetDirectory(directory)), - std::make_move_iterator(MPD::ItemIterator()), - std::back_inserter(items) - ); - } - - // sort items - if (Config.browser_sort_mode != SortMode::NoOp) - { - std::sort(items.begin(), items.end(), - LocaleBasedItemSorting(std::locale(), Config.ignore_leading_the, Config.browser_sort_mode) - ); - } - - // if the requested directory is not root, add parent directory - if (!isRootDirectory(directory)) - { - // make it so that display function doesn't have to handle special cases - w.addItem(MPD::Directory(directory + "/.."), NC::List::Properties::None); - } - - for (const auto &item : items) - { - switch (item.type()) - { - case MPD::Item::Type::Playlist: - { - w.addItem(std::move(item)); - break; - } - case MPD::Item::Type::Directory: - { - bool is_current = item.directory().path() == m_current_directory; - w.addItem(std::move(item)); - if (is_current) - w.highlight(w.size()-1); - break; - } - case MPD::Item::Type::Song: - { - auto properties = NC::List::Properties::Selectable; - if (myPlaylist->checkForSong(item.song())) - properties |= NC::List::Properties::Bold; - w.addItem(std::move(item), properties); - break; - } - } - } - m_current_directory = directory; -} - -void Browser::changeBrowseMode() -{ - if (Mpd.GetHostname()[0] != '/') - { - Statusbar::print("For browsing local filesystem connection to MPD via UNIX Socket is required"); - return; - } - - m_local_browser = !m_local_browser; - Statusbar::printf("Browse mode: %1%", - m_local_browser ? "local filesystem" : "MPD database" - ); - if (m_local_browser) - { - m_current_directory = "~"; - expand_home(m_current_directory); - } - else - m_current_directory = "/"; - w.reset(); - getDirectory(m_current_directory); - drawHeader(); -} - -void Browser::remove(const MPD::Item &item) -{ - if (!Config.allow_for_physical_item_deletion) - throw std::runtime_error("physical deletion is forbidden"); - if (isParentDirectory((item))) - throw std::runtime_error("deletion of parent directory is forbidden"); - - std::string path; - switch (item.type()) - { - case MPD::Item::Type::Directory: - path = realPath(m_local_browser, item.directory().path()); - clearDirectory(path); - fs::remove(path); - break; - case MPD::Item::Type::Song: - path = realPath(m_local_browser, item.song().getURI()); - fs::remove(path); - break; - case MPD::Item::Type::Playlist: - path = item.playlist().path(); - try { - Mpd.DeletePlaylist(path); - } catch (MPD::ServerError &e) { - // if there is no such mpd playlist, it's a local one - if (e.code() == MPD_SERVER_ERROR_NO_EXIST) - { - path = realPath(m_local_browser, std::move(path)); - fs::remove(path); - } - else - throw; - } - break; - } -} - -/***********************************************************************/ - -void Browser::fetchSupportedExtensions() -{ - lm_supported_extensions.clear(); - MPD::StringIterator extension = Mpd.GetSupportedExtensions(), end; - for (; extension != end; ++extension) - lm_supported_extensions.insert("." + std::move(*extension)); -} - -/***********************************************************************/ - -namespace { - -std::string realPath(bool local_browser, std::string path) -{ - if (!local_browser) - path = Config.mpd_music_dir + path; - return path; -} - -bool isStringParentDirectory(const std::string &directory) -{ - return boost::algorithm::ends_with(directory, "/.."); -} - -bool isItemParentDirectory(const MPD::Item &item) -{ - return item.type() == MPD::Item::Type::Directory - && isStringParentDirectory(item.directory().path()); -} - -bool isRootDirectory(const std::string &directory) -{ - return directory == "/"; -} - -bool isHidden(const fs::directory_iterator &entry) -{ - return entry->path().filename().native()[0] == '.'; -} - -bool hasSupportedExtension(const fs::directory_entry &entry) -{ - return lm_supported_extensions.find(entry.path().extension().native()) - != lm_supported_extensions.end(); -} - -MPD::Song getLocalSong(const fs::directory_entry &entry, bool read_tags) -{ - mpd_pair pair = { "file", entry.path().c_str() }; - mpd_song *s = mpd_song_begin(&pair); - if (s == nullptr) - throw std::runtime_error("invalid path: " + entry.path().native()); - if (read_tags) - { -#ifdef HAVE_TAGLIB_H - Tags::setAttribute(s, "Last-Modified", - timeFormat("%Y-%m-%dT%H:%M:%SZ", fs::last_write_time(entry.path())) - ); - // read tags - Tags::read(s); -#endif // HAVE_TAGLIB_H - } - return s; -} - -void getLocalDirectory(std::vector<MPD::Item> &items, const std::string &directory) -{ - for (fs::directory_iterator entry(directory), end; entry != end; ++entry) - { - if (!Config.local_browser_show_hidden_files && isHidden(entry)) - continue; - - if (fs::is_directory(*entry)) - { - items.push_back(MPD::Directory( - entry->path().native(), - fs::last_write_time(entry->path()) - )); - } - else if (hasSupportedExtension(*entry)) - items.push_back(getLocalSong(*entry, true)); - } -} - -void getLocalDirectoryRecursively(std::vector<MPD::Song> &songs, const std::string &directory) -{ - size_t sort_offset = songs.size(); - for (fs::directory_iterator entry(directory), end; entry != end; ++entry) - { - if (!Config.local_browser_show_hidden_files && isHidden(entry)) - continue; - - if (fs::is_directory(*entry)) - { - getLocalDirectoryRecursively(songs, entry->path().native()); - sort_offset = songs.size(); - } - else if (hasSupportedExtension(*entry)) - songs.push_back(getLocalSong(*entry, false)); - }; - - if (Config.browser_sort_mode != SortMode::NoOp) - { - std::sort(songs.begin()+sort_offset, songs.end(), - LocaleBasedSorting(std::locale(), Config.ignore_leading_the) - ); - } -} - -void clearDirectory(const std::string &directory) -{ - for (fs::directory_iterator entry(directory), end; entry != end; ++entry) - { - if (!fs::is_symlink(*entry) && fs::is_directory(*entry)) - clearDirectory(entry->path().native()); - const char msg[] = "Deleting \"%1%\"..."; - Statusbar::printf(msg, wideShorten(entry->path().native(), COLS-const_strlen(msg))); - fs::remove(entry->path()); - }; -} - -/***********************************************************************/ - -std::string itemToString(const MPD::Item &item) -{ - std::string result; - switch (item.type()) - { - case MPD::Item::Type::Directory: - result = "[" + getBasename(item.directory().path()) + "]"; - break; - case MPD::Item::Type::Song: - switch (Config.browser_display_mode) - { - case DisplayMode::Classic: - result = Format::stringify<char>(Config.song_list_format, &item.song()); - break; - case DisplayMode::Columns: - result = Format::stringify<char>(Config.song_columns_mode_format, &item.song()); - break; - } - break; - case MPD::Item::Type::Playlist: - result = Config.browser_playlist_prefix.str(); - result += getBasename(item.playlist().path()); - break; - } - return result; -} - -bool browserEntryMatcher(const Regex::Regex &rx, const MPD::Item &item, bool filter) -{ - if (isItemParentDirectory(item)) - return filter; - return Regex::search(itemToString(item), rx); -} - -} |