skin tags: fix the id3 track/disc numbers in conditionals
[maemo-rb.git] / apps / onplay.c
blob9152d87bf51993e53f365b265e3853b91f04312f
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Björn Stenberg
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include <errno.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <stdbool.h>
27 #include "debug.h"
28 #include "lcd.h"
29 #include "file.h"
30 #include "audio.h"
31 #include "menu.h"
32 #include "lang.h"
33 #include "playlist.h"
34 #include "button.h"
35 #include "kernel.h"
36 #include "keyboard.h"
37 #include "mp3data.h"
38 #include "metadata.h"
39 #include "screens.h"
40 #include "tree.h"
41 #include "settings.h"
42 #include "playlist_viewer.h"
43 #include "talk.h"
44 #include "onplay.h"
45 #include "filetypes.h"
46 #include "plugin.h"
47 #include "bookmark.h"
48 #include "action.h"
49 #include "splash.h"
50 #include "yesno.h"
51 #include "menus/exported_menus.h"
52 #ifdef HAVE_LCD_BITMAP
53 #include "icons.h"
54 #endif
55 #include "sound_menu.h"
56 #include "playlist_menu.h"
57 #include "playlist_catalog.h"
58 #ifdef HAVE_TAGCACHE
59 #include "tagtree.h"
60 #endif
61 #include "cuesheet.h"
62 #include "statusbar-skinned.h"
63 #include "pitchscreen.h"
64 #include "viewport.h"
65 #include "filefuncs.h"
66 #include "shortcuts.h"
68 static int context;
69 static const char* selected_file = NULL;
70 static int selected_file_attr = 0;
71 static int onplay_result = ONPLAY_OK;
72 static char clipboard_selection[MAX_PATH];
73 static int clipboard_selection_attr = 0;
74 static bool clipboard_is_copy = false;
76 /* redefine MAKE_MENU so the MENU_EXITAFTERTHISMENU flag can be added easily */
77 #define MAKE_ONPLAYMENU( name, str, callback, icon, ... ) \
78 static const struct menu_item_ex *name##_[] = {__VA_ARGS__}; \
79 static const struct menu_callback_with_desc name##__ = {callback,str,icon};\
80 static const struct menu_item_ex name = \
81 {MT_MENU|MENU_HAS_DESC|MENU_EXITAFTERTHISMENU| \
82 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
83 { (void*)name##_},{.callback_and_desc = & name##__}};
85 /* ----------------------------------------------------------------------- */
86 /* Displays the bookmark menu options for the user to decide. This is an */
87 /* interface function. */
88 /* ----------------------------------------------------------------------- */
90 static int bookmark_menu_callback(int action,
91 const struct menu_item_ex *this_item);
92 MENUITEM_FUNCTION(bookmark_create_menu_item, 0,
93 ID2P(LANG_BOOKMARK_MENU_CREATE),
94 bookmark_create_menu, NULL,
95 bookmark_menu_callback, Icon_Bookmark);
96 MENUITEM_FUNCTION(bookmark_load_menu_item, 0,
97 ID2P(LANG_BOOKMARK_MENU_LIST),
98 bookmark_load_menu, NULL,
99 bookmark_menu_callback, Icon_Bookmark);
100 MAKE_ONPLAYMENU(bookmark_menu, ID2P(LANG_BOOKMARK_MENU),
101 bookmark_menu_callback, Icon_Bookmark,
102 &bookmark_create_menu_item, &bookmark_load_menu_item);
103 static int bookmark_menu_callback(int action,
104 const struct menu_item_ex *this_item)
106 switch (action)
108 case ACTION_REQUEST_MENUITEM:
109 /* hide create bookmark option if bookmarking isn't currently possible (no track playing, queued tracks...) */
110 if (this_item == &bookmark_create_menu_item)
112 if (!bookmark_is_bookmarkable_state())
113 return ACTION_EXIT_MENUITEM;
115 /* hide loading bookmarks menu if no bookmarks exist */
116 else if (this_item == &bookmark_load_menu_item)
118 if (!bookmark_exists())
119 return ACTION_EXIT_MENUITEM;
121 /* hide the bookmark menu if bookmarks can't be loaded or created */
122 else if (!bookmark_is_bookmarkable_state() && !bookmark_exists())
123 return ACTION_EXIT_MENUITEM;
124 break;
125 #ifdef HAVE_LCD_CHARCELLS
126 case ACTION_ENTER_MENUITEM:
127 status_set_param(true);
128 break;
129 #endif
130 case ACTION_EXIT_MENUITEM:
131 #ifdef HAVE_LCD_CHARCELLS
132 status_set_param(false);
133 #endif
134 settings_save();
135 break;
137 return action;
140 /* CONTEXT_WPS playlist options */
141 static bool shuffle_playlist(void)
143 playlist_sort(NULL, true);
144 playlist_randomise(NULL, current_tick, true);
146 return false;
148 static bool save_playlist(void)
150 save_playlist_screen(NULL);
151 return false;
154 extern struct menu_item_ex view_cur_playlist; /* from playlist_menu.c */
155 MENUITEM_FUNCTION(search_playlist_item, 0, ID2P(LANG_SEARCH_IN_PLAYLIST),
156 search_playlist, NULL, NULL, Icon_Playlist);
157 MENUITEM_FUNCTION(playlist_save_item, 0, ID2P(LANG_SAVE_DYNAMIC_PLAYLIST),
158 save_playlist, NULL, NULL, Icon_Playlist);
159 MENUITEM_FUNCTION(reshuffle_item, 0, ID2P(LANG_SHUFFLE_PLAYLIST),
160 shuffle_playlist, NULL, NULL, Icon_Playlist);
161 MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_PLAYLIST),
162 NULL, Icon_Playlist,
163 &view_cur_playlist, &search_playlist_item,
164 &playlist_save_item, &reshuffle_item
167 /* CONTEXT_[TREE|ID3DB] playlist options */
168 static bool add_to_playlist(int position, bool queue)
170 bool new_playlist = !(audio_status() & AUDIO_STATUS_PLAY);
171 const char *lines[] = {
172 ID2P(LANG_RECURSE_DIRECTORY_QUESTION),
173 selected_file
175 const struct text_message message={lines, 2};
177 splash(0, ID2P(LANG_WAIT));
179 if (new_playlist)
180 playlist_create(NULL, NULL);
182 /* always set seed before inserting shuffled */
183 if (position == PLAYLIST_INSERT_SHUFFLED ||
184 position == PLAYLIST_INSERT_LAST_SHUFFLED)
186 srand(current_tick);
187 if (position == PLAYLIST_INSERT_LAST_SHUFFLED)
188 playlist_set_last_shuffled_start();
191 #ifdef HAVE_TAGCACHE
192 if (context == CONTEXT_ID3DB)
194 tagtree_insert_selection_playlist(position, queue);
196 else
197 #endif
199 if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
200 playlist_insert_track(NULL, selected_file, position, queue, true);
201 else if (selected_file_attr & ATTR_DIRECTORY)
203 bool recurse = false;
205 if (global_settings.recursive_dir_insert != RECURSE_ASK)
206 recurse = (bool)global_settings.recursive_dir_insert;
207 else
209 /* Ask if user wants to recurse directory */
210 recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
213 playlist_insert_directory(NULL, selected_file, position, queue,
214 recurse);
216 else if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)
217 playlist_insert_playlist(NULL, selected_file, position, queue);
220 if (new_playlist && (playlist_amount() > 0))
222 /* nothing is currently playing so begin playing what we just
223 inserted */
224 if (global_settings.playlist_shuffle)
225 playlist_shuffle(current_tick, -1);
226 playlist_start(0,0);
227 onplay_result = ONPLAY_START_PLAY;
230 return false;
233 static bool view_playlist(void)
235 bool was_playing = audio_status() & AUDIO_STATUS_PLAY;
236 bool result;
238 result = playlist_viewer_ex(selected_file);
240 if (!was_playing && (audio_status() & AUDIO_STATUS_PLAY) &&
241 onplay_result == ONPLAY_OK)
242 /* playlist was started from viewer */
243 onplay_result = ONPLAY_START_PLAY;
245 return result;
248 static int playlist_insert_func(void *param)
250 if (((intptr_t)param == PLAYLIST_REPLACE) && !warn_on_pl_erase())
251 return 0;
252 add_to_playlist((intptr_t)param, false);
253 return 0;
256 static int playlist_queue_func(void *param)
258 add_to_playlist((intptr_t)param, true);
259 return 0;
262 static int treeplaylist_wplayback_callback(int action,
263 const struct menu_item_ex* this_item)
265 (void)this_item;
266 switch (action)
268 case ACTION_REQUEST_MENUITEM:
269 if (audio_status() & AUDIO_STATUS_PLAY)
270 return action;
271 else
272 return ACTION_EXIT_MENUITEM;
273 break;
275 return action;
278 static int treeplaylist_callback(int action,
279 const struct menu_item_ex *this_item);
281 /* insert items */
282 MENUITEM_FUNCTION(i_pl_item, MENU_FUNC_USEPARAM, ID2P(LANG_INSERT),
283 playlist_insert_func, (intptr_t*)PLAYLIST_INSERT,
284 NULL, Icon_Playlist);
285 MENUITEM_FUNCTION(i_first_pl_item, MENU_FUNC_USEPARAM, ID2P(LANG_INSERT_FIRST),
286 playlist_insert_func, (intptr_t*)PLAYLIST_INSERT_FIRST,
287 treeplaylist_wplayback_callback, Icon_Playlist);
288 MENUITEM_FUNCTION(i_last_pl_item, MENU_FUNC_USEPARAM, ID2P(LANG_INSERT_LAST),
289 playlist_insert_func, (intptr_t*)PLAYLIST_INSERT_LAST,
290 treeplaylist_wplayback_callback, Icon_Playlist);
291 MENUITEM_FUNCTION(i_shuf_pl_item, MENU_FUNC_USEPARAM,
292 ID2P(LANG_INSERT_SHUFFLED), playlist_insert_func,
293 (intptr_t*)PLAYLIST_INSERT_SHUFFLED,
294 treeplaylist_callback, Icon_Playlist);
295 MENUITEM_FUNCTION(i_last_shuf_pl_item, MENU_FUNC_USEPARAM,
296 ID2P(LANG_INSERT_LAST_SHUFFLED), playlist_insert_func,
297 (intptr_t*)PLAYLIST_INSERT_LAST_SHUFFLED,
298 treeplaylist_callback, Icon_Playlist);
299 /* queue items */
300 MENUITEM_FUNCTION(q_pl_item, MENU_FUNC_USEPARAM, ID2P(LANG_QUEUE),
301 playlist_queue_func, (intptr_t*)PLAYLIST_INSERT,
302 treeplaylist_wplayback_callback, Icon_Playlist);
303 MENUITEM_FUNCTION(q_first_pl_item, MENU_FUNC_USEPARAM, ID2P(LANG_QUEUE_FIRST),
304 playlist_queue_func, (intptr_t*)PLAYLIST_INSERT_FIRST,
305 treeplaylist_wplayback_callback, Icon_Playlist);
306 MENUITEM_FUNCTION(q_last_pl_item, MENU_FUNC_USEPARAM, ID2P(LANG_QUEUE_LAST),
307 playlist_queue_func, (intptr_t*)PLAYLIST_INSERT_LAST,
308 treeplaylist_wplayback_callback, Icon_Playlist);
309 MENUITEM_FUNCTION(q_shuf_pl_item, MENU_FUNC_USEPARAM,
310 ID2P(LANG_QUEUE_SHUFFLED), playlist_queue_func,
311 (intptr_t*)PLAYLIST_INSERT_SHUFFLED,
312 treeplaylist_wplayback_callback, Icon_Playlist);
313 MENUITEM_FUNCTION(q_last_shuf_pl_item, MENU_FUNC_USEPARAM,
314 ID2P(LANG_QUEUE_LAST_SHUFFLED), playlist_queue_func,
315 (intptr_t*)PLAYLIST_INSERT_LAST_SHUFFLED,
316 treeplaylist_callback, Icon_Playlist);
317 /* replace playlist */
318 MENUITEM_FUNCTION(replace_pl_item, MENU_FUNC_USEPARAM, ID2P(LANG_REPLACE),
319 playlist_insert_func, (intptr_t*)PLAYLIST_REPLACE,
320 treeplaylist_wplayback_callback, Icon_Playlist);
322 /* others */
323 MENUITEM_FUNCTION(view_playlist_item, 0, ID2P(LANG_VIEW),
324 view_playlist, NULL,
325 treeplaylist_callback, Icon_Playlist);
327 MAKE_ONPLAYMENU( tree_playlist_menu, ID2P(LANG_CURRENT_PLAYLIST),
328 treeplaylist_callback, Icon_Playlist,
330 /* view */
331 &view_playlist_item,
333 /* insert */
334 &i_pl_item, &i_first_pl_item, &i_last_pl_item,
335 &i_shuf_pl_item, &i_last_shuf_pl_item,
336 /* queue */
338 &q_pl_item, &q_first_pl_item, &q_last_pl_item,
339 &q_shuf_pl_item, &q_last_shuf_pl_item,
341 /* replace */
342 &replace_pl_item
344 static int treeplaylist_callback(int action,
345 const struct menu_item_ex *this_item)
347 switch (action)
349 case ACTION_REQUEST_MENUITEM:
350 if (this_item == &tree_playlist_menu)
352 if (((selected_file_attr & FILE_ATTR_MASK) ==
353 FILE_ATTR_AUDIO) ||
354 ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)||
355 (selected_file_attr & ATTR_DIRECTORY))
357 return action;
360 else if (this_item == &view_playlist_item)
362 if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U &&
363 context == CONTEXT_TREE)
365 return action;
368 else if (this_item == &i_shuf_pl_item)
370 if ((audio_status() & AUDIO_STATUS_PLAY) ||
371 (selected_file_attr & ATTR_DIRECTORY) ||
372 ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U))
374 return action;
377 else if (this_item == &i_last_shuf_pl_item ||
378 this_item == &q_last_shuf_pl_item)
380 if ((playlist_amount() > 0) &&
381 (audio_status() & AUDIO_STATUS_PLAY) &&
382 ((selected_file_attr & ATTR_DIRECTORY) ||
383 ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)))
385 return action;
388 return ACTION_EXIT_MENUITEM;
389 break;
391 return action;
394 void onplay_show_playlist_menu(char* path)
396 selected_file = path;
397 if (dir_exists(path))
398 selected_file_attr = ATTR_DIRECTORY;
399 else
400 selected_file_attr = filetype_get_attr(path);
401 do_menu(&tree_playlist_menu, NULL, NULL, false);
404 /* playlist catalog options */
405 static bool cat_add_to_a_playlist(void)
407 return catalog_add_to_a_playlist(selected_file, selected_file_attr,
408 false, NULL);
411 static bool cat_add_to_a_new_playlist(void)
413 return catalog_add_to_a_playlist(selected_file, selected_file_attr,
414 true, NULL);
416 static int clipboard_callback(int action,const struct menu_item_ex *this_item);
417 static bool set_catalogdir(void)
419 catalog_set_directory(selected_file);
420 settings_save();
421 return false;
423 MENUITEM_FUNCTION(set_catalogdir_item, 0, ID2P(LANG_SET_AS_PLAYLISTCAT_DIR),
424 set_catalogdir, NULL, clipboard_callback, Icon_Playlist);
426 static int cat_playlist_callback(int action,
427 const struct menu_item_ex *this_item);
428 MENUITEM_FUNCTION(cat_add_to_list, 0, ID2P(LANG_CATALOG_ADD_TO),
429 cat_add_to_a_playlist, 0, NULL, Icon_Playlist);
430 MENUITEM_FUNCTION(cat_add_to_new, 0, ID2P(LANG_CATALOG_ADD_TO_NEW),
431 cat_add_to_a_new_playlist, 0, NULL, Icon_Playlist);
432 MAKE_ONPLAYMENU(cat_playlist_menu, ID2P(LANG_CATALOG),
433 cat_playlist_callback, Icon_Playlist,
434 &cat_add_to_list, &cat_add_to_new, &set_catalogdir_item);
436 void onplay_show_playlist_cat_menu(char* track_name)
438 selected_file = track_name;
439 selected_file_attr = FILE_ATTR_AUDIO;
440 do_menu(&cat_playlist_menu, NULL, NULL, false);
443 static int cat_playlist_callback(int action,
444 const struct menu_item_ex *this_item)
446 (void)this_item;
447 if (!selected_file ||
448 (((selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO) &&
449 ((selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_M3U) &&
450 ((selected_file_attr & ATTR_DIRECTORY) == 0)))
452 return ACTION_EXIT_MENUITEM;
454 #ifdef HAVE_TAGCACHE
455 if (context == CONTEXT_ID3DB &&
456 ((selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO))
458 return ACTION_EXIT_MENUITEM;
460 #endif
462 switch (action)
464 case ACTION_REQUEST_MENUITEM:
465 if ((audio_status() & AUDIO_STATUS_PLAY) || context != CONTEXT_WPS)
467 return action;
469 else
470 return ACTION_EXIT_MENUITEM;
471 break;
473 return action;
476 #ifdef HAVE_LCD_BITMAP
477 static void draw_slider(void)
479 FOR_NB_SCREENS(i)
481 struct viewport vp;
482 int slider_height = 2*screens[i].getcharheight();
483 viewport_set_defaults(&vp, i);
484 screens[i].set_viewport(&vp);
485 show_busy_slider(&screens[i], 1, vp.height - slider_height,
486 vp.width-2, slider_height-1);
487 screens[i].update_viewport();
488 screens[i].set_viewport(NULL);
491 #else
492 #define draw_slider()
493 #endif
495 /* helper function to remove a non-empty directory */
496 static int remove_dir(char* dirname, int len)
498 int result = 0;
499 DIR* dir;
500 int dirlen = strlen(dirname);
502 dir = opendir(dirname);
503 if (!dir)
504 return -1; /* open error */
506 while(true)
508 struct dirent* entry;
509 /* walk through the directory content */
510 entry = readdir(dir);
511 if (!entry)
512 break;
513 struct dirinfo info = dir_get_info(dir, entry);
514 dirname[dirlen] ='\0';
515 /* inform the user which dir we're deleting */
516 splash(0, dirname);
518 /* append name to current directory */
519 snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
520 if (info.attribute & ATTR_DIRECTORY)
521 { /* remove a subdirectory */
522 if (!strcmp((char *)entry->d_name, ".") ||
523 !strcmp((char *)entry->d_name, ".."))
524 continue; /* skip these */
526 result = remove_dir(dirname, len); /* recursion */
527 if (result)
528 break; /* or better continue, delete what we can? */
530 else
531 { /* remove a file */
532 draw_slider();
533 result = remove(dirname);
535 if(ACTION_STD_CANCEL == get_action(CONTEXT_STD,TIMEOUT_NOBLOCK))
537 splash(HZ, ID2P(LANG_CANCEL));
538 result = -1;
539 break;
542 closedir(dir);
544 if (!result)
545 { /* remove the now empty directory */
546 dirname[dirlen] = '\0'; /* terminate to original length */
548 result = rmdir(dirname);
551 return result;
555 /* share code for file and directory deletion, saves space */
556 static bool delete_file_dir(void)
558 char file_to_delete[MAX_PATH];
559 strcpy(file_to_delete, selected_file);
561 const char *lines[]={
562 ID2P(LANG_REALLY_DELETE),
563 file_to_delete
565 const char *yes_lines[]={
566 ID2P(LANG_DELETING),
567 file_to_delete
570 const struct text_message message={lines, 2};
571 const struct text_message yes_message={yes_lines, 2};
573 if(gui_syncyesno_run(&message, &yes_message, NULL)!=YESNO_YES)
574 return false;
576 splash(0, str(LANG_DELETING));
578 int res;
579 if (selected_file_attr & ATTR_DIRECTORY) /* true if directory */
581 char pathname[MAX_PATH]; /* space to go deep */
582 cpu_boost(true);
583 strlcpy(pathname, file_to_delete, sizeof(pathname));
584 res = remove_dir(pathname, sizeof(pathname));
585 cpu_boost(false);
587 else
588 res = remove(file_to_delete);
590 if (!res)
591 onplay_result = ONPLAY_RELOAD_DIR;
593 return (res == 0);
596 static bool rename_file(void)
598 char newname[MAX_PATH];
599 char* ptr = strrchr(selected_file, '/') + 1;
600 int pathlen = (ptr - selected_file);
601 strlcpy(newname, selected_file, sizeof(newname));
602 if (!kbd_input(newname + pathlen, (sizeof newname)-pathlen)) {
603 if (!strlen(newname + pathlen) ||
604 (rename(selected_file, newname) < 0)) {
605 cond_talk_ids_fq(LANG_RENAME, LANG_FAILED);
606 splashf(HZ*2, "%s %s", str(LANG_RENAME), str(LANG_FAILED));
608 else
609 onplay_result = ONPLAY_RELOAD_DIR;
612 return false;
615 static bool create_dir(void)
617 char dirname[MAX_PATH];
618 char *cwd;
619 int rc;
620 int pathlen;
622 cwd = getcwd(NULL, 0);
623 memset(dirname, 0, sizeof dirname);
625 snprintf(dirname, sizeof dirname, "%s/", cwd[1] ? cwd : "");
627 pathlen = strlen(dirname);
628 rc = kbd_input(dirname + pathlen, (sizeof dirname)-pathlen);
629 if (rc < 0)
630 return false;
632 rc = mkdir(dirname);
633 if (rc < 0) {
634 cond_talk_ids_fq(LANG_CREATE_DIR, LANG_FAILED);
635 splashf(HZ, (unsigned char *)"%s %s", str(LANG_CREATE_DIR),
636 str(LANG_FAILED));
637 } else {
638 onplay_result = ONPLAY_RELOAD_DIR;
641 return true;
644 /* Store the current selection in the clipboard */
645 static bool clipboard_clip(bool copy)
647 clipboard_selection[0] = 0;
648 strlcpy(clipboard_selection, selected_file, sizeof(clipboard_selection));
649 clipboard_selection_attr = selected_file_attr;
650 clipboard_is_copy = copy;
652 return true;
655 static bool clipboard_cut(void)
657 return clipboard_clip(false);
660 static bool clipboard_copy(void)
662 return clipboard_clip(true);
665 /* Paste a file to a new directory. Will overwrite always. */
666 static bool clipboard_pastefile(const char *src, const char *target, bool copy)
668 int src_fd, target_fd;
669 size_t buffersize;
670 ssize_t size, bytesread, byteswritten;
671 char *buffer;
672 bool result = false;
674 if (copy) {
675 /* See if we can get the plugin buffer for the file copy buffer */
676 buffer = (char *) plugin_get_buffer(&buffersize);
677 if (buffer == NULL || buffersize < 512) {
678 /* Not large enough, try for a disk sector worth of stack
679 instead */
680 buffersize = 512;
681 buffer = (char *) __builtin_alloca(buffersize);
684 if (buffer == NULL) {
685 return false;
688 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
689 size */
691 src_fd = open(src, O_RDONLY);
693 if (src_fd >= 0) {
694 target_fd = creat(target, 0666);
696 if (target_fd >= 0) {
697 result = true;
699 size = filesize(src_fd);
701 if (size == -1) {
702 result = false;
705 while(size > 0) {
706 bytesread = read(src_fd, buffer, buffersize);
708 if (bytesread == -1) {
709 result = false;
710 break;
713 size -= bytesread;
715 while(bytesread > 0) {
716 byteswritten = write(target_fd, buffer, bytesread);
718 if (byteswritten < 0) {
719 result = false;
720 size = 0;
721 break;
724 bytesread -= byteswritten;
725 draw_slider();
729 close(target_fd);
731 /* Copy failed. Cleanup. */
732 if (!result) {
733 remove(target);
737 close(src_fd);
739 } else {
740 result = rename(src, target) == 0;
741 #ifdef HAVE_MULTIVOLUME
742 if (!result) {
743 if (errno == EXDEV) {
744 /* Failed because cross volume rename doesn't work. Copy
745 instead */
746 result = clipboard_pastefile(src, target, true);
748 if (result) {
749 result = remove(src) == 0;
753 #endif
756 return result;
759 /* Paste a directory to a new location. Designed to be called by
760 clipboard_paste */
761 static bool clipboard_pastedirectory(char *src, int srclen, char *target,
762 int targetlen, bool copy)
764 DIR *srcdir;
765 int srcdirlen = strlen(src);
766 int targetdirlen = strlen(target);
767 bool result = true;
769 if (!file_exists(target)) {
770 if (!copy) {
771 /* Just move the directory */
772 result = rename(src, target) == 0;
774 #ifdef HAVE_MULTIVOLUME
775 if (!result && errno == EXDEV) {
776 /* Try a copy as we're going across devices */
777 result = clipboard_pastedirectory(src, srclen, target,
778 targetlen, true);
780 /* If it worked, remove the source directory */
781 if (result) {
782 remove_dir(src, srclen);
785 #endif
786 return result;
787 } else {
788 /* Make a directory to copy things to */
789 result = mkdir(target) == 0;
793 /* Check if something went wrong already */
794 if (!result) {
795 return result;
798 srcdir = opendir(src);
799 if (!srcdir) {
800 return false;
803 /* This loop will exit as soon as there's a problem */
804 while(result)
806 struct dirent* entry;
807 /* walk through the directory content */
808 entry = readdir(srcdir);
809 if (!entry)
810 break;
812 struct dirinfo info = dir_get_info(srcdir, entry);
813 /* append name to current directory */
814 snprintf(src+srcdirlen, srclen-srcdirlen, "/%s", entry->d_name);
815 snprintf(target+targetdirlen, targetlen-targetdirlen, "/%s",
816 entry->d_name);
818 DEBUGF("Copy %s to %s\n", src, target);
820 if (info.attribute & ATTR_DIRECTORY)
821 { /* copy/move a subdirectory */
822 if (!strcmp((char *)entry->d_name, ".") ||
823 !strcmp((char *)entry->d_name, ".."))
824 continue; /* skip these */
826 result = clipboard_pastedirectory(src, srclen, target, targetlen,
827 copy); /* recursion */
829 else
830 { /* copy/move a file */
831 draw_slider();
832 result = clipboard_pastefile(src, target, copy);
836 closedir(srcdir);
838 if (result) {
839 src[srcdirlen] = '\0'; /* terminate to original length */
840 target[targetdirlen] = '\0'; /* terminate to original length */
843 return result;
846 /* Paste the clipboard to the current directory */
847 static bool clipboard_paste(void)
849 char target[MAX_PATH];
850 char *cwd, *nameptr;
851 bool success;
853 static const char *lines[]={ID2P(LANG_REALLY_OVERWRITE)};
854 static const struct text_message message={lines, 1};
856 /* Get the name of the current directory */
857 cwd = getcwd(NULL, 0);
859 /* Figure out the name of the selection */
860 nameptr = strrchr(clipboard_selection, '/');
862 /* Final target is current directory plus name of selection */
863 snprintf(target, sizeof(target), "%s%s", cwd[1] ? cwd : "", nameptr);
865 /* If the target existed but they choose not to overwite, exit */
866 if (file_exists(target) &&
867 (gui_syncyesno_run(&message, NULL, NULL) == YESNO_NO)) {
868 return false;
871 if (clipboard_is_copy) {
872 splash(0, ID2P(LANG_COPYING));
874 else
876 splash(0, ID2P(LANG_MOVING));
879 /* Now figure out what we're doing */
880 cpu_boost(true);
881 if (clipboard_selection_attr & ATTR_DIRECTORY) {
882 /* Recursion. Set up external stack */
883 char srcpath[MAX_PATH];
884 char targetpath[MAX_PATH];
885 if (!strncmp(clipboard_selection, target, strlen(clipboard_selection)))
887 /* Do not allow the user to paste a directory into a dir they are
888 copying */
889 success = 0;
891 else
893 strlcpy(srcpath, clipboard_selection, sizeof(srcpath));
894 strlcpy(targetpath, target, sizeof(targetpath));
896 success = clipboard_pastedirectory(srcpath, sizeof(srcpath),
897 target, sizeof(targetpath), clipboard_is_copy);
899 if (success && !clipboard_is_copy)
901 strlcpy(srcpath, clipboard_selection, sizeof(srcpath));
902 remove_dir(srcpath, sizeof(srcpath));
905 } else {
906 success = clipboard_pastefile(clipboard_selection, target,
907 clipboard_is_copy);
909 cpu_boost(false);
911 /* Did it work? */
912 if (success) {
913 /* Reset everything */
914 clipboard_selection[0] = 0;
915 clipboard_selection_attr = 0;
916 clipboard_is_copy = false;
918 /* Force reload of the current directory */
919 onplay_result = ONPLAY_RELOAD_DIR;
920 } else {
921 cond_talk_ids_fq(LANG_PASTE, LANG_FAILED);
922 splashf(HZ, (unsigned char *)"%s %s", str(LANG_PASTE),
923 str(LANG_FAILED));
926 return true;
929 #ifdef HAVE_TAGCACHE
930 static int set_rating_inline(void)
932 struct mp3entry* id3 = audio_current_track();
933 if (id3 && id3->tagcache_idx && global_settings.runtimedb)
935 set_int_ex(str(LANG_MENU_SET_RATING), "", UNIT_INT, (void*)(&id3->rating),
936 NULL, 1, 0, 10, NULL, NULL);
937 tagcache_update_numeric(id3->tagcache_idx-1, tag_rating, id3->rating);
939 else
940 splash(HZ*2, ID2P(LANG_ID3_NO_INFO));
941 return 0;
943 static int ratingitem_callback(int action,const struct menu_item_ex *this_item)
945 (void)this_item;
946 switch (action)
948 case ACTION_REQUEST_MENUITEM:
949 if (!selected_file || !global_settings.runtimedb ||
950 !tagcache_is_usable())
951 return ACTION_EXIT_MENUITEM;
952 break;
954 return action;
956 MENUITEM_FUNCTION(rating_item, 0, ID2P(LANG_MENU_SET_RATING),
957 set_rating_inline, NULL,
958 ratingitem_callback, Icon_Questionmark);
959 #endif
960 #ifdef HAVE_PICTUREFLOW_INTEGRATION
961 MENUITEM_RETURNVALUE(pictureflow_item, ID2P(LANG_ONPLAY_PICTUREFLOW),
962 GO_TO_PICTUREFLOW, NULL, Icon_NOICON);
963 #endif
965 static bool view_cue(void)
967 struct mp3entry* id3 = audio_current_track();
968 if(id3 && id3->cuesheet)
970 browse_cuesheet(id3->cuesheet);
972 return false;
974 static int view_cue_item_callback(int action,
975 const struct menu_item_ex *this_item)
977 (void)this_item;
978 struct mp3entry* id3 = audio_current_track();
979 switch (action)
981 case ACTION_REQUEST_MENUITEM:
982 if (!selected_file
983 || !id3 || !id3->cuesheet)
984 return ACTION_EXIT_MENUITEM;
985 break;
987 return action;
989 MENUITEM_FUNCTION(view_cue_item, 0, ID2P(LANG_BROWSE_CUESHEET),
990 view_cue, NULL, view_cue_item_callback, Icon_NOICON);
993 static int browse_id3_wrapper(void)
995 if (browse_id3())
996 return GO_TO_ROOT;
997 return GO_TO_PREVIOUS;
1000 /* CONTEXT_WPS items */
1001 MENUITEM_FUNCTION(browse_id3_item, MENU_FUNC_CHECK_RETVAL, ID2P(LANG_MENU_SHOW_ID3_INFO),
1002 browse_id3_wrapper, NULL, NULL, Icon_NOICON);
1003 #ifdef HAVE_PITCHCONTROL
1004 MENUITEM_FUNCTION(pitch_screen_item, 0, ID2P(LANG_PITCH),
1005 gui_syncpitchscreen_run, NULL, NULL, Icon_Audio);
1006 #endif
1008 /* CONTEXT_[TREE|ID3DB] items */
1009 static int clipboard_callback(int action,const struct menu_item_ex *this_item);
1010 MENUITEM_FUNCTION(rename_file_item, 0, ID2P(LANG_RENAME),
1011 rename_file, NULL, clipboard_callback, Icon_NOICON);
1012 MENUITEM_FUNCTION(clipboard_cut_item, 0, ID2P(LANG_CUT),
1013 clipboard_cut, NULL, clipboard_callback, Icon_NOICON);
1014 MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY),
1015 clipboard_copy, NULL, clipboard_callback, Icon_NOICON);
1016 MENUITEM_FUNCTION(clipboard_paste_item, 0, ID2P(LANG_PASTE),
1017 clipboard_paste, NULL, clipboard_callback, Icon_NOICON);
1018 MENUITEM_FUNCTION(delete_file_item, 0, ID2P(LANG_DELETE),
1019 delete_file_dir, NULL, clipboard_callback, Icon_NOICON);
1020 MENUITEM_FUNCTION(delete_dir_item, 0, ID2P(LANG_DELETE_DIR),
1021 delete_file_dir, NULL, clipboard_callback, Icon_NOICON);
1022 MENUITEM_FUNCTION(create_dir_item, 0, ID2P(LANG_CREATE_DIR),
1023 create_dir, NULL, clipboard_callback, Icon_NOICON);
1025 /* other items */
1026 static bool list_viewers(void)
1028 int ret = filetype_list_viewers(selected_file);
1029 if (ret == PLUGIN_USB_CONNECTED)
1030 onplay_result = ONPLAY_RELOAD_DIR;
1031 return false;
1034 static bool onplay_load_plugin(void *param)
1036 int ret = filetype_load_plugin((const char*)param, selected_file);
1037 if (ret == PLUGIN_USB_CONNECTED)
1038 onplay_result = ONPLAY_RELOAD_DIR;
1039 return false;
1042 MENUITEM_FUNCTION(list_viewers_item, 0, ID2P(LANG_ONPLAY_OPEN_WITH),
1043 list_viewers, NULL, clipboard_callback, Icon_NOICON);
1044 MENUITEM_FUNCTION(properties_item, MENU_FUNC_USEPARAM, ID2P(LANG_PROPERTIES),
1045 onplay_load_plugin, (void *)"properties",
1046 clipboard_callback, Icon_NOICON);
1047 static bool onplay_add_to_shortcuts(void)
1049 shortcuts_add(SHORTCUT_BROWSER, selected_file);
1050 return false;
1052 MENUITEM_FUNCTION(add_to_faves_item, 0, ID2P(LANG_ADD_TO_FAVES),
1053 onplay_add_to_shortcuts, NULL,
1054 clipboard_callback, Icon_NOICON);
1056 #if LCD_DEPTH > 1
1057 static bool set_backdrop(void)
1059 strlcpy(global_settings.backdrop_file, selected_file,
1060 sizeof(global_settings.backdrop_file));
1061 settings_save();
1062 skin_backdrop_load_setting();
1063 skin_backdrop_show(sb_get_backdrop(SCREEN_MAIN));
1064 return true;
1066 MENUITEM_FUNCTION(set_backdrop_item, 0, ID2P(LANG_SET_AS_BACKDROP),
1067 set_backdrop, NULL, clipboard_callback, Icon_NOICON);
1068 #endif
1069 #ifdef HAVE_RECORDING
1070 static bool set_recdir(void)
1072 strlcpy(global_settings.rec_directory, selected_file,
1073 sizeof(global_settings.rec_directory));
1074 settings_save();
1075 return false;
1077 MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_SET_AS_REC_DIR),
1078 set_recdir, NULL, clipboard_callback, Icon_Recording);
1079 #endif
1080 static bool set_startdir(void)
1082 snprintf(global_settings.start_directory,
1083 sizeof(global_settings.start_directory),
1084 "%s/", selected_file);
1085 settings_save();
1086 return false;
1088 MENUITEM_FUNCTION(set_startdir_item, 0, ID2P(LANG_SET_AS_START_DIR),
1089 set_startdir, NULL, clipboard_callback, Icon_file_view_menu);
1091 static int clipboard_callback(int action,const struct menu_item_ex *this_item)
1093 switch (action)
1095 case ACTION_REQUEST_MENUITEM:
1096 #ifdef HAVE_MULTIVOLUME
1097 if ((selected_file_attr & FAT_ATTR_VOLUME) &&
1098 (this_item == &rename_file_item ||
1099 this_item == &delete_dir_item ||
1100 this_item == &clipboard_cut_item) )
1101 return ACTION_EXIT_MENUITEM;
1102 /* no rename+delete for volumes */
1103 if ((selected_file_attr & ATTR_VOLUME) &&
1104 (this_item == &delete_file_item ||
1105 this_item == &list_viewers_item))
1106 return ACTION_EXIT_MENUITEM;
1107 #endif
1108 #ifdef HAVE_TAGCACHE
1109 if (context == CONTEXT_ID3DB)
1111 if (((selected_file_attr & FILE_ATTR_MASK) ==
1112 FILE_ATTR_AUDIO) &&
1113 this_item == &properties_item)
1114 return action;
1115 return ACTION_EXIT_MENUITEM;
1117 #endif
1118 if (this_item == &clipboard_paste_item)
1119 { /* visible if there is something to paste */
1120 return (clipboard_selection[0] != 0) ?
1121 action : ACTION_EXIT_MENUITEM;
1123 else if (this_item == &create_dir_item)
1125 /* always visible */
1126 return action;
1128 else if (selected_file)
1130 /* requires an actual file */
1131 if (this_item == &rename_file_item ||
1132 this_item == &clipboard_cut_item ||
1133 this_item == &clipboard_copy_item ||
1134 this_item == &properties_item ||
1135 this_item == &add_to_faves_item)
1137 return action;
1139 else if ((selected_file_attr & ATTR_DIRECTORY))
1141 /* only for directories */
1142 if (this_item == &delete_dir_item ||
1143 this_item == &set_startdir_item ||
1144 this_item == &set_catalogdir_item
1145 #ifdef HAVE_RECORDING
1146 || this_item == &set_recdir_item
1147 #endif
1149 return action;
1151 else if (this_item == &delete_file_item ||
1152 this_item == &list_viewers_item)
1154 /* only for files */
1155 return action;
1157 #if LCD_DEPTH > 1
1158 else if (this_item == &set_backdrop_item)
1160 char *suffix = strrchr(selected_file, '.');
1161 if (suffix)
1163 if (strcasecmp(suffix, ".bmp") == 0)
1165 return action;
1169 #endif
1171 return ACTION_EXIT_MENUITEM;
1172 break;
1174 return action;
1177 static int onplaymenu_callback(int action,const struct menu_item_ex *this_item);
1178 /* used when onplay() is called in the CONTEXT_WPS context */
1179 MAKE_ONPLAYMENU( wps_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE),
1180 onplaymenu_callback, Icon_Audio,
1181 &wps_playlist_menu, &cat_playlist_menu,
1182 &sound_settings, &playback_settings,
1183 #ifdef HAVE_TAGCACHE
1184 &rating_item,
1185 #endif
1186 &bookmark_menu,
1187 #ifdef HAVE_PICTUREFLOW_INTEGRATION
1188 &pictureflow_item,
1189 #endif
1190 &browse_id3_item, &list_viewers_item,
1191 &delete_file_item, &view_cue_item,
1192 #ifdef HAVE_PITCHCONTROL
1193 &pitch_screen_item,
1194 #endif
1196 /* used when onplay() is not called in the CONTEXT_WPS context */
1197 MAKE_ONPLAYMENU( tree_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE),
1198 onplaymenu_callback, Icon_file_view_menu,
1199 &tree_playlist_menu, &cat_playlist_menu,
1200 &rename_file_item, &clipboard_cut_item, &clipboard_copy_item,
1201 &clipboard_paste_item, &delete_file_item, &delete_dir_item,
1202 #if LCD_DEPTH > 1
1203 &set_backdrop_item,
1204 #endif
1205 &list_viewers_item, &create_dir_item, &properties_item,
1206 #ifdef HAVE_RECORDING
1207 &set_recdir_item,
1208 #endif
1209 &set_startdir_item, &add_to_faves_item,
1211 static int onplaymenu_callback(int action,const struct menu_item_ex *this_item)
1213 switch (action)
1215 case ACTION_TREE_STOP:
1216 if (this_item == &wps_onplay_menu)
1218 list_stop_handler();
1219 return ACTION_STD_CANCEL;
1221 break;
1222 case ACTION_EXIT_MENUITEM:
1223 return ACTION_EXIT_AFTER_THIS_MENUITEM;
1224 break;
1226 return action;
1229 #ifdef HAVE_HOTKEY
1230 /* direct function calls, no need for menu callbacks */
1231 static bool delete_item(void)
1233 #ifdef HAVE_MULTIVOLUME
1234 /* no delete for volumes */
1235 if ((selected_file_attr & FAT_ATTR_VOLUME) ||
1236 (selected_file_attr & ATTR_VOLUME))
1237 return false;
1238 #endif
1239 return delete_file_dir();
1242 static bool open_with(void)
1244 /* only open files */
1245 if (selected_file_attr & ATTR_DIRECTORY)
1246 return false;
1247 #ifdef HAVE_MULTIVOLUME
1248 if (selected_file_attr & ATTR_VOLUME)
1249 return false;
1250 #endif
1251 return list_viewers();
1254 static int playlist_insert_shuffled(void)
1256 if ((audio_status() & AUDIO_STATUS_PLAY) ||
1257 (selected_file_attr & ATTR_DIRECTORY) ||
1258 ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U))
1260 playlist_insert_func((intptr_t*)PLAYLIST_INSERT_SHUFFLED);
1261 return ONPLAY_START_PLAY;
1264 return ONPLAY_RELOAD_DIR;
1267 struct hotkey_assignment {
1268 int action; /* hotkey_action */
1269 int lang_id; /* Language ID */
1270 struct menu_func func; /* Function to run if this entry is selected */
1271 int return_code; /* What to return after the function is run */
1274 #define HOTKEY_FUNC(func, param) {{(void *)func}, param}
1276 /* Any desired hotkey functions go here, in the enum in onplay.h,
1277 and in the settings menu in settings_list.c. The order here
1278 is not important. */
1279 static struct hotkey_assignment hotkey_items[] = {
1280 { HOTKEY_VIEW_PLAYLIST, LANG_VIEW_DYNAMIC_PLAYLIST,
1281 HOTKEY_FUNC(NULL, NULL),
1282 ONPLAY_PLAYLIST },
1283 { HOTKEY_SHOW_TRACK_INFO, LANG_MENU_SHOW_ID3_INFO,
1284 HOTKEY_FUNC(browse_id3, NULL),
1285 ONPLAY_RELOAD_DIR },
1286 #ifdef HAVE_PITCHCONTROL
1287 { HOTKEY_PITCHSCREEN, LANG_PITCH,
1288 HOTKEY_FUNC(gui_syncpitchscreen_run, NULL),
1289 ONPLAY_RELOAD_DIR },
1290 #endif
1291 { HOTKEY_OPEN_WITH, LANG_ONPLAY_OPEN_WITH,
1292 HOTKEY_FUNC(open_with, NULL),
1293 ONPLAY_RELOAD_DIR },
1294 { HOTKEY_DELETE, LANG_DELETE,
1295 HOTKEY_FUNC(delete_item, NULL),
1296 ONPLAY_RELOAD_DIR },
1297 { HOTKEY_INSERT, LANG_INSERT,
1298 HOTKEY_FUNC(playlist_insert_func, (intptr_t*)PLAYLIST_INSERT),
1299 ONPLAY_RELOAD_DIR },
1300 { HOTKEY_INSERT_SHUFFLED, LANG_INSERT_SHUFFLED,
1301 HOTKEY_FUNC(playlist_insert_shuffled, NULL),
1302 ONPLAY_RELOAD_DIR },
1303 #ifdef HAVE_PICTUREFLOW_INTEGRATION
1304 { HOTKEY_PICTUREFLOW, LANG_ONPLAY_PICTUREFLOW,
1305 HOTKEY_FUNC(NULL, NULL),
1306 ONPLAY_PICTUREFLOW },
1307 #endif
1310 /* Return the language ID for this action */
1311 int get_hotkey_lang_id(int action)
1313 int i = ARRAYLEN(hotkey_items);
1314 while (i--)
1316 if (hotkey_items[i].action == action)
1317 return hotkey_items[i].lang_id;
1320 return LANG_OFF;
1323 /* Execute the hotkey function, if listed */
1324 static int execute_hotkey(bool is_wps)
1326 int i = ARRAYLEN(hotkey_items);
1327 struct hotkey_assignment *this_item;
1328 const int action = (is_wps ? global_settings.hotkey_wps :
1329 global_settings.hotkey_tree);
1331 /* search assignment struct for a match for the hotkey setting */
1332 while (i--)
1334 this_item = &hotkey_items[i];
1335 if (this_item->action == action)
1337 /* run the associated function (with optional param), if any */
1338 const struct menu_func func = this_item->func;
1339 int func_return = ONPLAY_RELOAD_DIR;
1340 if (func.function != NULL)
1342 if (func.param != NULL)
1343 func_return = (*func.function_w_param)(func.param);
1344 else
1345 func_return = (*func.function)();
1347 /* return with the associated code */
1348 const int return_code = this_item->return_code;
1349 /* ONPLAY_OK here means to use the function return code */
1350 if (return_code == ONPLAY_OK)
1351 return func_return;
1352 return return_code;
1356 /* no valid hotkey set, ignore hotkey */
1357 return ONPLAY_RELOAD_DIR;
1359 #endif /* HOTKEY */
1361 int onplay(char* file, int attr, int from, bool hotkey)
1363 const struct menu_item_ex *menu;
1364 onplay_result = ONPLAY_OK;
1365 context = from;
1366 selected_file = file;
1367 selected_file_attr = attr;
1368 int menu_selection;
1369 #ifdef HAVE_HOTKEY
1370 if (hotkey)
1371 return execute_hotkey(context == CONTEXT_WPS);
1372 #else
1373 (void)hotkey;
1374 #endif
1376 push_current_activity(ACTIVITY_CONTEXTMENU);
1377 if (context == CONTEXT_WPS)
1378 menu = &wps_onplay_menu;
1379 else
1380 menu = &tree_onplay_menu;
1381 menu_selection = do_menu(menu, NULL, NULL, false);
1382 pop_current_activity();
1384 switch (menu_selection)
1386 case GO_TO_WPS:
1387 return ONPLAY_START_PLAY;
1388 case GO_TO_ROOT:
1389 case GO_TO_MAINMENU:
1390 return ONPLAY_MAINMENU;
1391 case GO_TO_PLAYLIST_VIEWER:
1392 return ONPLAY_PLAYLIST;
1393 #ifdef HAVE_PICTUREFLOW_INTEGRATION
1394 case GO_TO_PICTUREFLOW:
1395 return ONPLAY_PICTUREFLOW;
1396 #endif
1397 default:
1398 return onplay_result;