1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2018 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "FileBrowserPage.hxx"
21 #include "PageMeta.hxx"
22 #include "FileListPage.hxx"
23 #include "screen_status.hxx"
24 #include "save_playlist.hxx"
28 #include "charset.hxx"
29 #include "mpdclient.hxx"
30 #include "filelist.hxx"
31 #include "screen_utils.hxx"
32 #include "screen_client.hxx"
33 #include "Options.hxx"
34 #include "util/UriUtil.hxx"
36 #include <mpd/client.h>
43 class FileBrowserPage final : public FileListPage {
44 std::string current_path;
47 FileBrowserPage(ScreenManager &_screen, WINDOW *_w,
49 :FileListPage(_screen, _w, size,
50 options.list_format.c_str()) {}
52 bool GotoSong(struct mpdclient &c, const struct mpd_song &song);
55 void Reload(struct mpdclient &c);
58 * Change to the specified absolute directory.
60 bool ChangeDirectory(struct mpdclient &c, std::string &&new_path);
63 * Change to the parent directory of the current directory.
65 bool ChangeToParent(struct mpdclient &c);
68 * Change to the directory referred by the specified
69 * #FileListEntry object.
71 bool ChangeToEntry(struct mpdclient &c, const FileListEntry &entry);
73 bool HandleEnter(struct mpdclient &c);
74 void HandleSave(struct mpdclient &c);
75 void HandleDelete(struct mpdclient &c);
78 /* virtual methods from class Page */
79 void Update(struct mpdclient &c, unsigned events) noexcept override;
80 bool OnCommand(struct mpdclient &c, Command cmd) override;
81 const char *GetTitle(char *s, size_t size) const noexcept override;
85 screen_file_load_list(struct mpdclient *c, const char *current_path,
88 auto *connection = c->GetConnection();
89 if (connection == nullptr)
92 mpd_send_list_meta(connection, current_path);
93 filelist->Receive(*connection);
95 if (c->FinishCommand())
100 FileBrowserPage::Reload(struct mpdclient &c)
104 filelist = new FileList();
105 if (!current_path.empty())
106 /* add a dummy entry for ./.. */
107 filelist->emplace_back(nullptr);
109 screen_file_load_list(&c, current_path.c_str(), filelist);
111 lw.SetLength(filelist->size());
117 FileBrowserPage::ChangeDirectory(struct mpdclient &c, std::string &&new_path)
119 current_path = std::move(new_path);
123 screen_browser_sync_highlights(filelist, &c.playlist);
127 return filelist != nullptr;
131 FileBrowserPage::ChangeToParent(struct mpdclient &c)
133 auto parent = GetParentUri(current_path.c_str());
134 const auto old_path = std::move(current_path);
136 bool success = ChangeDirectory(c, std::move(parent));
139 ? filelist->FindDirectory(old_path.c_str())
142 if (success && idx >= 0) {
143 /* set the cursor on the previous working directory */
152 * Change to the directory referred by the specified #FileListEntry
156 FileBrowserPage::ChangeToEntry(struct mpdclient &c, const FileListEntry &entry)
158 if (entry.entity == nullptr)
159 return ChangeToParent(c);
160 else if (mpd_entity_get_type(entry.entity) == MPD_ENTITY_TYPE_DIRECTORY)
161 return ChangeDirectory(c, mpd_directory_get_path(mpd_entity_get_directory(entry.entity)));
167 FileBrowserPage::GotoSong(struct mpdclient &c, const struct mpd_song &song)
169 const char *uri = mpd_song_get_uri(&song);
170 if (strstr(uri, "//") != nullptr)
174 /* determine the song's parent directory and go there */
176 if (!ChangeDirectory(c, GetParentUri(uri)))
179 /* select the specified song */
181 int i = filelist->FindSong(song);
191 FileBrowserPage::HandleEnter(struct mpdclient &c)
193 const auto *entry = GetSelectedEntry();
194 if (entry == nullptr)
197 return ChangeToEntry(c, *entry);
201 FileBrowserPage::HandleSave(struct mpdclient &c)
203 const char *defaultname = nullptr;
205 const auto range = lw.GetRange();
206 if (range.start_index == range.end_index)
209 for (const unsigned i : range) {
210 auto &entry = (*filelist)[i];
212 struct mpd_entity *entity = entry.entity;
213 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
214 const struct mpd_playlist *playlist =
215 mpd_entity_get_playlist(entity);
216 defaultname = mpd_playlist_get_path(playlist);
222 playlist_save(&c, nullptr, Utf8ToLocale(defaultname).c_str());
224 playlist_save(&c, nullptr, nullptr);
228 FileBrowserPage::HandleDelete(struct mpdclient &c)
230 auto *connection = c.GetConnection();
232 if (connection == nullptr)
235 const auto range = lw.GetRange();
236 for (const unsigned i : range) {
237 auto &entry = (*filelist)[i];
238 if (entry.entity == nullptr)
241 const auto *entity = entry.entity;
243 if (mpd_entity_get_type(entity) != MPD_ENTITY_TYPE_PLAYLIST) {
244 /* translators: the "delete" command is only possible
245 for playlists; the user attempted to delete a song
246 or a directory or something else */
247 screen_status_message(_("Deleting this item is not possible"));
252 const auto *playlist = mpd_entity_get_playlist(entity);
254 snprintf(prompt, sizeof(prompt),
255 _("Delete playlist %s?"),
256 Utf8ToLocale(GetUriFilename(mpd_playlist_get_path(playlist))).c_str());
257 bool confirmed = screen_get_yesno(prompt, false);
259 /* translators: a dialog was aborted by the user */
260 screen_status_message(_("Aborted"));
264 if (!mpd_run_rm(connection, mpd_playlist_get_path(playlist))) {
269 c.events |= MPD_IDLE_STORED_PLAYLIST;
271 /* translators: MPD deleted the playlist, as requested by the
273 screen_status_message(_("Playlist deleted"));
277 static std::unique_ptr<Page>
278 screen_file_init(ScreenManager &_screen, WINDOW *w, Size size)
280 return std::make_unique<FileBrowserPage>(_screen, w, size);
284 FileBrowserPage::GetTitle(char *str, size_t size) const noexcept
286 const char *path = nullptr, *prev = nullptr, *slash = current_path.c_str();
288 /* determine the last 2 parts of the path */
289 while ((slash = strchr(slash, '/')) != nullptr) {
295 /* fall back to full path */
296 path = current_path.c_str();
298 snprintf(str, size, "%s: %s",
299 /* translators: caption of the browser screen */
300 _("Browse"), Utf8ToLocale(path).c_str());
305 FileBrowserPage::Update(struct mpdclient &c, unsigned events) noexcept
307 if (events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST)) {
308 /* the db has changed -> update the filelist */
312 if (events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST
317 screen_browser_sync_highlights(filelist, &c.playlist);
323 FileBrowserPage::OnCommand(struct mpdclient &c, Command cmd)
332 case Command::GO_ROOT_DIRECTORY:
333 ChangeDirectory(c, {});
335 case Command::GO_PARENT_DIRECTORY:
339 case Command::LOCATE:
340 /* don't let browser_cmd() evaluate the locate command
341 - it's a no-op, and by the way, leads to a
342 segmentation fault in the current implementation */
345 case Command::SCREEN_UPDATE:
347 screen_browser_sync_highlights(filelist, &c.playlist);
354 if (FileListPage::OnCommand(c, cmd))
357 if (!c.IsConnected())
361 case Command::DELETE:
365 case Command::SAVE_PLAYLIST:
369 case Command::DB_UPDATE:
370 screen_database_update(&c, current_path.c_str());
380 const PageMeta screen_browse = {
383 Command::SCREEN_FILE,
388 screen_file_goto_song(ScreenManager &_screen, struct mpdclient &c,
389 const struct mpd_song &song)
391 auto pi = _screen.MakePage(screen_browse);
392 auto &page = (FileBrowserPage &)*pi->second;
393 if (!page.GotoSong(c, song))
396 /* finally, switch to the file screen */
397 _screen.Switch(screen_browse, c);