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