LyricsPage: use ScreenManager::get_io_service()
[ncmpc-debian.git] / src / FileBrowserPage.cxx
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2018 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #include "FileBrowserPage.hxx"
21 #include "PageMeta.hxx"
22 #include "FileListPage.hxx"
23 #include "screen_status.hxx"
24 #include "save_playlist.hxx"
25 #include "screen.hxx"
26 #include "config.h"
27 #include "i18n.h"
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"
35
36 #include <mpd/client.h>
37
38 #include <string>
39
40 #include <stdlib.h>
41 #include <string.h>
42
43 class FileBrowserPage final : public FileListPage {
44         std::string current_path;
45
46 public:
47         FileBrowserPage(ScreenManager &_screen, WINDOW *_w,
48                         Size size)
49                 :FileListPage(_screen, _w, size,
50                               options.list_format.c_str()) {}
51
52         bool GotoSong(struct mpdclient &c, const struct mpd_song &song);
53
54 private:
55         void Reload(struct mpdclient &c);
56
57         /**
58          * Change to the specified absolute directory.
59          */
60         bool ChangeDirectory(struct mpdclient &c, std::string &&new_path);
61
62         /**
63          * Change to the parent directory of the current directory.
64          */
65         bool ChangeToParent(struct mpdclient &c);
66
67         /**
68          * Change to the directory referred by the specified
69          * #FileListEntry object.
70          */
71         bool ChangeToEntry(struct mpdclient &c, const FileListEntry &entry);
72
73         bool HandleEnter(struct mpdclient &c);
74         void HandleSave(struct mpdclient &c);
75         void HandleDelete(struct mpdclient &c);
76
77 public:
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;
82 };
83
84 static void
85 screen_file_load_list(struct mpdclient *c, const char *current_path,
86                       FileList *filelist)
87 {
88         auto *connection = c->GetConnection();
89         if (connection == nullptr)
90                 return;
91
92         mpd_send_list_meta(connection, current_path);
93         filelist->Receive(*connection);
94
95         if (c->FinishCommand())
96                 filelist->Sort();
97 }
98
99 void
100 FileBrowserPage::Reload(struct mpdclient &c)
101 {
102         delete filelist;
103
104         filelist = new FileList();
105         if (!current_path.empty())
106                 /* add a dummy entry for ./.. */
107                 filelist->emplace_back(nullptr);
108
109         screen_file_load_list(&c, current_path.c_str(), filelist);
110
111         lw.SetLength(filelist->size());
112
113         SetDirty();
114 }
115
116 bool
117 FileBrowserPage::ChangeDirectory(struct mpdclient &c, std::string &&new_path)
118 {
119         current_path = std::move(new_path);
120
121         Reload(c);
122
123         screen_browser_sync_highlights(filelist, &c.playlist);
124
125         lw.Reset();
126
127         return filelist != nullptr;
128 }
129
130 bool
131 FileBrowserPage::ChangeToParent(struct mpdclient &c)
132 {
133         auto parent = GetParentUri(current_path.c_str());
134         const auto old_path = std::move(current_path);
135
136         bool success = ChangeDirectory(c, std::move(parent));
137
138         int idx = success
139                 ? filelist->FindDirectory(old_path.c_str())
140                 : -1;
141
142         if (success && idx >= 0) {
143                 /* set the cursor on the previous working directory */
144                 lw.SetCursor(idx);
145                 lw.Center(idx);
146         }
147
148         return success;
149 }
150
151 /**
152  * Change to the directory referred by the specified #FileListEntry
153  * object.
154  */
155 bool
156 FileBrowserPage::ChangeToEntry(struct mpdclient &c, const FileListEntry &entry)
157 {
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)));
162         else
163                 return false;
164 }
165
166 bool
167 FileBrowserPage::GotoSong(struct mpdclient &c, const struct mpd_song &song)
168 {
169         const char *uri = mpd_song_get_uri(&song);
170         if (strstr(uri, "//") != nullptr)
171                 /* an URL? */
172                 return false;
173
174         /* determine the song's parent directory and go there */
175
176         if (!ChangeDirectory(c, GetParentUri(uri)))
177                 return false;
178
179         /* select the specified song */
180
181         int i = filelist->FindSong(song);
182         if (i < 0)
183                 i = 0;
184
185         lw.SetCursor(i);
186         SetDirty();
187         return true;
188 }
189
190 bool
191 FileBrowserPage::HandleEnter(struct mpdclient &c)
192 {
193         const auto *entry = GetSelectedEntry();
194         if (entry == nullptr)
195                 return false;
196
197         return ChangeToEntry(c, *entry);
198 }
199
200 void
201 FileBrowserPage::HandleSave(struct mpdclient &c)
202 {
203         const char *defaultname = nullptr;
204
205         const auto range = lw.GetRange();
206         if (range.start_index == range.end_index)
207                 return;
208
209         for (const unsigned i : range) {
210                 auto &entry = (*filelist)[i];
211                 if (entry.entity) {
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);
217                         }
218                 }
219         }
220
221         if(defaultname)
222                 playlist_save(&c, nullptr, Utf8ToLocale(defaultname).c_str());
223         else
224                 playlist_save(&c, nullptr, nullptr);
225 }
226
227 void
228 FileBrowserPage::HandleDelete(struct mpdclient &c)
229 {
230         auto *connection = c.GetConnection();
231
232         if (connection == nullptr)
233                 return;
234
235         const auto range = lw.GetRange();
236         for (const unsigned i : range) {
237                 auto &entry = (*filelist)[i];
238                 if (entry.entity == nullptr)
239                         continue;
240
241                 const auto *entity = entry.entity;
242
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"));
248                         screen_bell();
249                         continue;
250                 }
251
252                 const auto *playlist = mpd_entity_get_playlist(entity);
253                 char prompt[256];
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);
258                 if (!confirmed) {
259                         /* translators: a dialog was aborted by the user */
260                         screen_status_message(_("Aborted"));
261                         return;
262                 }
263
264                 if (!mpd_run_rm(connection, mpd_playlist_get_path(playlist))) {
265                         c.HandleError();
266                         break;
267                 }
268
269                 c.events |= MPD_IDLE_STORED_PLAYLIST;
270
271                 /* translators: MPD deleted the playlist, as requested by the
272                    user */
273                 screen_status_message(_("Playlist deleted"));
274         }
275 }
276
277 static std::unique_ptr<Page>
278 screen_file_init(ScreenManager &_screen, WINDOW *w, Size size)
279 {
280         return std::make_unique<FileBrowserPage>(_screen, w, size);
281 }
282
283 const char *
284 FileBrowserPage::GetTitle(char *str, size_t size) const noexcept
285 {
286         const char *path = nullptr, *prev = nullptr, *slash = current_path.c_str();
287
288         /* determine the last 2 parts of the path */
289         while ((slash = strchr(slash, '/')) != nullptr) {
290                 path = prev;
291                 prev = ++slash;
292         }
293
294         if (path == nullptr)
295                 /* fall back to full path */
296                 path = current_path.c_str();
297
298         snprintf(str, size, "%s: %s",
299                  /* translators: caption of the browser screen */
300                  _("Browse"), Utf8ToLocale(path).c_str());
301         return str;
302 }
303
304 void
305 FileBrowserPage::Update(struct mpdclient &c, unsigned events) noexcept
306 {
307         if (events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST)) {
308                 /* the db has changed -> update the filelist */
309                 Reload(c);
310         }
311
312         if (events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST
313 #ifndef NCMPC_MINI
314                       | MPD_IDLE_QUEUE
315 #endif
316                       )) {
317                 screen_browser_sync_highlights(filelist, &c.playlist);
318                 SetDirty();
319         }
320 }
321
322 bool
323 FileBrowserPage::OnCommand(struct mpdclient &c, Command cmd)
324 {
325         switch(cmd) {
326         case Command::PLAY:
327                 if (HandleEnter(c))
328                         return true;
329
330                 break;
331
332         case Command::GO_ROOT_DIRECTORY:
333                 ChangeDirectory(c, {});
334                 return true;
335         case Command::GO_PARENT_DIRECTORY:
336                 ChangeToParent(c);
337                 return true;
338
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 */
343                 return false;
344
345         case Command::SCREEN_UPDATE:
346                 Reload(c);
347                 screen_browser_sync_highlights(filelist, &c.playlist);
348                 return false;
349
350         default:
351                 break;
352         }
353
354         if (FileListPage::OnCommand(c, cmd))
355                 return true;
356
357         if (!c.IsConnected())
358                 return false;
359
360         switch(cmd) {
361         case Command::DELETE:
362                 HandleDelete(c);
363                 break;
364
365         case Command::SAVE_PLAYLIST:
366                 HandleSave(c);
367                 break;
368
369         case Command::DB_UPDATE:
370                 screen_database_update(&c, current_path.c_str());
371                 return true;
372
373         default:
374                 break;
375         }
376
377         return false;
378 }
379
380 const PageMeta screen_browse = {
381         "browse",
382         N_("Browse"),
383         Command::SCREEN_FILE,
384         screen_file_init,
385 };
386
387 bool
388 screen_file_goto_song(ScreenManager &_screen, struct mpdclient &c,
389                       const struct mpd_song &song)
390 {
391         auto pi = _screen.MakePage(screen_browse);
392         auto &page = (FileBrowserPage &)*pi->second;
393         if (!page.GotoSong(c, song))
394                 return false;
395
396         /* finally, switch to the file screen */
397         _screen.Switch(screen_browse, c);
398         return true;
399 }