1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2010 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 */
20 #include "screen_file.h"
21 #include "screen_browser.h"
22 #include "screen_interface.h"
23 #include "screen_status.h"
24 #include "screen_queue.h"
25 #include "screen.h"
26 #include "config.h"
27 #include "i18n.h"
28 #include "charset.h"
29 #include "mpdclient.h"
30 #include "filelist.h"
31 #include "screen_utils.h"
32 #include "screen_client.h"
34 #include <mpd/client.h>
36 #include <ctype.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <glib.h>
41 static struct screen_browser browser;
42 static char *current_path;
44 static void
45 screen_file_paint(void);
47 static void
48 screen_file_repaint(void)
49 {
50 screen_file_paint();
51 wrefresh(browser.lw->w);
52 }
54 static void
55 screen_file_load_list(struct mpdclient *c, struct filelist *filelist)
56 {
57 struct mpd_connection *connection;
59 connection = mpdclient_get_connection(c);
60 if (connection == NULL)
61 return;
63 mpd_send_list_meta(connection, current_path);
64 filelist_recv(filelist, connection);
66 if (mpdclient_finish_command(c))
67 filelist_sort_dir_play(filelist, compare_filelist_entry_path);
68 }
70 static void
71 screen_file_reload(struct mpdclient *c)
72 {
73 if (browser.filelist != NULL)
74 filelist_free(browser.filelist);
76 browser.filelist = filelist_new();
77 if (*current_path != 0)
78 /* add a dummy entry for ./.. */
79 filelist_append(browser.filelist, NULL);
81 screen_file_load_list(c, browser.filelist);
83 list_window_set_length(browser.lw,
84 filelist_length(browser.filelist));
85 }
87 /**
88 * Change to the specified absolute directory.
89 */
90 static bool
91 change_directory(struct mpdclient *c, const char *new_path)
92 {
93 g_free(current_path);
94 current_path = g_strdup(new_path);
96 screen_file_reload(c);
98 screen_browser_sync_highlights(browser.filelist, &c->playlist);
100 list_window_reset(browser.lw);
102 return browser.filelist != NULL;
103 }
105 /**
106 * Change to the parent directory of the current directory.
107 */
108 static bool
109 change_to_parent(struct mpdclient *c)
110 {
111 char *parent = g_path_get_dirname(current_path);
112 if (strcmp(parent, ".") == 0)
113 parent[0] = '\0';
115 char *old_path = current_path;
116 current_path = NULL;
118 bool success = change_directory(c, parent);
119 g_free(parent);
121 int idx = success
122 ? filelist_find_directory(browser.filelist, old_path)
123 : -1;
124 g_free(old_path);
126 if (success && idx >= 0) {
127 /* set the cursor on the previous working directory */
128 list_window_set_cursor(browser.lw, idx);
129 list_window_center(browser.lw, idx);
130 }
132 return success;
133 }
135 /**
136 * Change to the directory referred by the specified #filelist_entry
137 * object.
138 */
139 static bool
140 change_to_entry(struct mpdclient *c, const struct filelist_entry *entry)
141 {
142 assert(entry != NULL);
144 if (entry->entity == NULL)
145 return change_to_parent(c);
146 else if (mpd_entity_get_type(entry->entity) == MPD_ENTITY_TYPE_DIRECTORY)
147 return change_directory(c, mpd_directory_get_path(mpd_entity_get_directory(entry->entity)));
148 else
149 return false;
150 }
152 static bool
153 screen_file_handle_enter(struct mpdclient *c)
154 {
155 const struct filelist_entry *entry = browser_get_selected_entry(&browser);
157 if (entry == NULL)
158 return false;
160 return change_to_entry(c, entry);
161 }
163 static void
164 handle_save(struct mpdclient *c)
165 {
166 struct list_window_range range;
167 const char *defaultname = NULL;
169 list_window_get_range(browser.lw, &range);
170 if (range.start == range.end)
171 return;
173 for (unsigned i = range.start; i < range.end; ++i) {
174 struct filelist_entry *entry =
175 filelist_get(browser.filelist, i);
176 if( entry && entry->entity ) {
177 struct mpd_entity *entity = entry->entity;
178 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
179 const struct mpd_playlist *playlist =
180 mpd_entity_get_playlist(entity);
181 defaultname = mpd_playlist_get_path(playlist);
182 }
183 }
184 }
186 char *defaultname_utf8 = NULL;
187 if(defaultname)
188 defaultname_utf8 = utf8_to_locale(defaultname);
189 playlist_save(c, NULL, defaultname_utf8);
190 g_free(defaultname_utf8);
191 }
193 static void
194 handle_delete(struct mpdclient *c)
195 {
196 struct mpd_connection *connection = mpdclient_get_connection(c);
198 if (connection == NULL)
199 return;
201 struct list_window_range range;
202 list_window_get_range(browser.lw, &range);
203 for (unsigned i = range.start; i < range.end; ++i) {
204 struct filelist_entry *entry =
205 filelist_get(browser.filelist, i);
206 if( entry==NULL || entry->entity==NULL )
207 continue;
209 struct mpd_entity *entity = entry->entity;
211 if (mpd_entity_get_type(entity) != MPD_ENTITY_TYPE_PLAYLIST) {
212 /* translators: the "delete" command is only possible
213 for playlists; the user attempted to delete a song
214 or a directory or something else */
215 screen_status_printf(_("Deleting this item is not possible"));
216 screen_bell();
217 continue;
218 }
220 const struct mpd_playlist *playlist = mpd_entity_get_playlist(entity);
221 char *str = utf8_to_locale(g_basename(mpd_playlist_get_path(playlist)));
222 char *buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
223 g_free(str);
224 bool delete = screen_get_yesno(buf, false);
225 g_free(buf);
227 if (!delete) {
228 /* translators: a dialog was aborted by the user */
229 screen_status_printf(_("Aborted"));
230 return;
231 }
233 if (!mpd_run_rm(connection, mpd_playlist_get_path(playlist))) {
234 mpdclient_handle_error(c);
235 break;
236 }
238 c->events |= MPD_IDLE_STORED_PLAYLIST;
240 /* translators: MPD deleted the playlist, as requested by the
241 user */
242 screen_status_printf(_("Playlist deleted"));
243 }
244 }
246 static void
247 screen_file_init(WINDOW *w, int cols, int rows)
248 {
249 current_path = g_strdup("");
251 browser.lw = list_window_init(w, cols, rows);
252 }
254 static void
255 screen_file_resize(int cols, int rows)
256 {
257 list_window_resize(browser.lw, cols, rows);
258 }
260 static void
261 screen_file_exit(void)
262 {
263 if (browser.filelist)
264 filelist_free(browser.filelist);
265 list_window_free(browser.lw);
267 g_free(current_path);
268 }
270 static void
271 screen_file_open(struct mpdclient *c)
272 {
273 screen_file_reload(c);
274 screen_browser_sync_highlights(browser.filelist, &c->playlist);
275 }
277 static const char *
278 screen_file_get_title(char *str, size_t size)
279 {
280 const char *path = NULL, *prev = NULL, *slash = current_path;
282 /* determine the last 2 parts of the path */
283 while ((slash = strchr(slash, '/')) != NULL) {
284 path = prev;
285 prev = ++slash;
286 }
288 if (path == NULL)
289 /* fall back to full path */
290 path = current_path;
292 char *path_locale = utf8_to_locale(path);
293 g_snprintf(str, size, "%s: %s",
294 /* translators: caption of the browser screen */
295 _("Browse"), path_locale);
296 g_free(path_locale);
297 return str;
298 }
300 static void
301 screen_file_paint(void)
302 {
303 screen_browser_paint(&browser);
304 }
306 static void
307 screen_file_update(struct mpdclient *c)
308 {
309 if (c->events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST)) {
310 /* the db has changed -> update the filelist */
311 screen_file_reload(c);
312 }
314 if (c->events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST
315 #ifndef NCMPC_MINI
316 | MPD_IDLE_QUEUE
317 #endif
318 )) {
319 screen_browser_sync_highlights(browser.filelist, &c->playlist);
320 screen_file_repaint();
321 }
322 }
324 static bool
325 screen_file_cmd(struct mpdclient *c, command_t cmd)
326 {
327 switch(cmd) {
328 case CMD_PLAY:
329 if (screen_file_handle_enter(c)) {
330 screen_file_repaint();
331 return true;
332 }
334 break;
336 case CMD_GO_ROOT_DIRECTORY:
337 change_directory(c, "");
338 screen_file_repaint();
339 return true;
340 case CMD_GO_PARENT_DIRECTORY:
341 change_to_parent(c);
342 screen_file_repaint();
343 return true;
345 case CMD_LOCATE:
346 /* don't let browser_cmd() evaluate the locate command
347 - it's a no-op, and by the way, leads to a
348 segmentation fault in the current implementation */
349 return false;
351 case CMD_SCREEN_UPDATE:
352 screen_file_reload(c);
353 screen_browser_sync_highlights(browser.filelist, &c->playlist);
354 screen_file_repaint();
355 return false;
357 default:
358 break;
359 }
361 if (browser_cmd(&browser, c, cmd)) {
362 if (screen_is_visible(&screen_browse))
363 screen_file_repaint();
364 return true;
365 }
367 if (!mpdclient_is_connected(c))
368 return false;
370 switch(cmd) {
371 case CMD_DELETE:
372 handle_delete(c);
373 screen_file_repaint();
374 break;
376 case CMD_SAVE_PLAYLIST:
377 handle_save(c);
378 break;
380 case CMD_DB_UPDATE:
381 screen_database_update(c, current_path);
382 return true;
384 default:
385 break;
386 }
388 return false;
389 }
391 const struct screen_functions screen_browse = {
392 .init = screen_file_init,
393 .exit = screen_file_exit,
394 .open = screen_file_open,
395 .resize = screen_file_resize,
396 .paint = screen_file_paint,
397 .update = screen_file_update,
398 .cmd = screen_file_cmd,
399 .get_title = screen_file_get_title,
400 };
402 bool
403 screen_file_goto_song(struct mpdclient *c, const struct mpd_song *song)
404 {
405 const char *uri, *slash, *parent;
406 char *allocated = NULL;
408 assert(song != NULL);
410 uri = mpd_song_get_uri(song);
412 if (strstr(uri, "//") != NULL)
413 /* an URL? */
414 return false;
416 /* determine the song's parent directory and go there */
418 slash = strrchr(uri, '/');
419 if (slash != NULL)
420 parent = allocated = g_strndup(uri, slash - uri);
421 else
422 parent = "";
424 bool ret = change_directory(c, parent);
425 g_free(allocated);
426 if (!ret)
427 return false;
429 /* select the specified song */
431 int i = filelist_find_song(browser.filelist, song);
432 if (i < 0)
433 i = 0;
435 list_window_set_cursor(browser.lw, i);
437 /* finally, switch to the file screen */
438 screen_switch(&screen_browse, c);
439 return true;
440 }