summaryrefslogtreecommitdiff
path: root/apps/onplay.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/onplay.c')
-rw-r--r--apps/onplay.c784
1 files changed, 145 insertions, 639 deletions
diff --git a/apps/onplay.c b/apps/onplay.c
index 572138e583..4880af58f3 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -26,7 +26,6 @@
26 26
27#include "debug.h" 27#include "debug.h"
28#include "lcd.h" 28#include "lcd.h"
29#include "file.h"
30#include "audio.h" 29#include "audio.h"
31#include "menu.h" 30#include "menu.h"
32#include "lang.h" 31#include "lang.h"
@@ -43,6 +42,7 @@
43#include "talk.h" 42#include "talk.h"
44#include "onplay.h" 43#include "onplay.h"
45#include "filetypes.h" 44#include "filetypes.h"
45#include "fileop.h"
46#include "open_plugin.h" 46#include "open_plugin.h"
47#include "plugin.h" 47#include "plugin.h"
48#include "bookmark.h" 48#include "bookmark.h"
@@ -84,31 +84,6 @@ extern struct menu_item_ex file_menu; /* settings_menu.c */
84 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ 84 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
85 { (void*)name##_},{.callback_and_desc = & name##__}}; 85 { (void*)name##_},{.callback_and_desc = & name##__}};
86 86
87/* Used for directory move, copy and delete */
88struct dirrecurse_params
89{
90 char path[MAX_PATH]; /* Buffer for full path */
91 size_t append; /* Append position in 'path' for stack push */
92};
93
94enum clipboard_op_flags
95{
96 PASTE_CUT = 0x00, /* Is a move (cut) operation (default) */
97 PASTE_COPY = 0x01, /* Is a copy operation */
98 PASTE_OVERWRITE = 0x02, /* Overwrite destination */
99 PASTE_EXDEV = 0x04, /* Actually copy/move across volumes */
100};
101
102/* result codec of various onplay operations */
103enum onplay_result_code
104{
105 /* Anything < 0 is failure */
106 OPRC_SUCCESS = 0, /* All operations completed successfully */
107 OPRC_NOOP = 1, /* Operation didn't need to do anything */
108 OPRC_CANCELLED = 2, /* Operation was cancelled by user */
109 OPRC_NOOVERWRT = 3,
110};
111
112static struct clipboard 87static struct clipboard
113{ 88{
114 char path[MAX_PATH]; /* Clipped file's path */ 89 char path[MAX_PATH]; /* Clipped file's path */
@@ -191,8 +166,6 @@ static int bookmark_menu_callback(int action,
191 return action; 166 return action;
192} 167}
193 168
194
195
196/* CONTEXT_WPS playlist options */ 169/* CONTEXT_WPS playlist options */
197static bool shuffle_playlist(void) 170static bool shuffle_playlist(void)
198{ 171{
@@ -244,46 +217,82 @@ MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_CURRENT_PLAYLIST),
244 ); 217 );
245 218
246/* argument for add_to_playlist (for use by menu callbacks) */ 219/* argument for add_to_playlist (for use by menu callbacks) */
220#define PL_NONE 0x00
221#define PL_QUEUE 0x01
222#define PL_REPLACE 0x02
247struct add_to_pl_param 223struct add_to_pl_param
248{ 224{
249 int8_t position; 225 int8_t position;
250 unsigned int queue: 1; 226 uint8_t flags;
251 unsigned int replace: 1;
252}; 227};
253 228
254static struct add_to_pl_param addtopl_insert = {PLAYLIST_INSERT, 0, 0}; 229static struct add_to_pl_param addtopl_insert = {PLAYLIST_INSERT, PL_NONE};
255static struct add_to_pl_param addtopl_insert_first = {PLAYLIST_INSERT_FIRST, 0, 0}; 230static struct add_to_pl_param addtopl_insert_first = {PLAYLIST_INSERT_FIRST, PL_NONE};
256static struct add_to_pl_param addtopl_insert_last = {PLAYLIST_INSERT_LAST, 0, 0}; 231static struct add_to_pl_param addtopl_insert_last = {PLAYLIST_INSERT_LAST, PL_NONE};
257static struct add_to_pl_param addtopl_insert_shuf = {PLAYLIST_INSERT_SHUFFLED, 0, 0}; 232static struct add_to_pl_param addtopl_insert_shuf = {PLAYLIST_INSERT_SHUFFLED, PL_NONE};
258static struct add_to_pl_param addtopl_insert_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, 0, 0}; 233static struct add_to_pl_param addtopl_insert_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_NONE};
259 234
260static struct add_to_pl_param addtopl_queue = {PLAYLIST_INSERT, 1, 0}; 235static struct add_to_pl_param addtopl_queue = {PLAYLIST_INSERT, PL_QUEUE};
261static struct add_to_pl_param addtopl_queue_first = {PLAYLIST_INSERT_FIRST, 1, 0}; 236static struct add_to_pl_param addtopl_queue_first = {PLAYLIST_INSERT_FIRST, PL_QUEUE};
262static struct add_to_pl_param addtopl_queue_last = {PLAYLIST_INSERT_LAST, 1, 0}; 237static struct add_to_pl_param addtopl_queue_last = {PLAYLIST_INSERT_LAST, PL_QUEUE};
263static struct add_to_pl_param addtopl_queue_shuf = {PLAYLIST_INSERT_SHUFFLED, 1, 0}; 238static struct add_to_pl_param addtopl_queue_shuf = {PLAYLIST_INSERT_SHUFFLED, PL_QUEUE};
264static struct add_to_pl_param addtopl_queue_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, 1, 0}; 239static struct add_to_pl_param addtopl_queue_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_QUEUE};
265 240
266static struct add_to_pl_param addtopl_replace = {PLAYLIST_INSERT, 0, 1}; 241static struct add_to_pl_param addtopl_replace = {PLAYLIST_INSERT, PL_REPLACE};
267static struct add_to_pl_param addtopl_replace_shuffled = {PLAYLIST_INSERT_LAST_SHUFFLED, 0, 1}; 242static struct add_to_pl_param addtopl_replace_shuffled = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_REPLACE};
243
244static void op_playlist_insert_selected(int position, bool queue)
245{
246#ifdef HAVE_TAGCACHE
247 if (context == CONTEXT_STD && ctx_current_playlist_insert != NULL)
248 {
249 ctx_current_playlist_insert(position, queue, false);
250 return;
251 }
252#endif
253 if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
254 playlist_insert_track(NULL, selected_file, position, queue, true);
255 else if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)
256 playlist_insert_playlist(NULL, selected_file, position, queue);
257 else if (selected_file_attr & ATTR_DIRECTORY)
258 {
259#ifdef HAVE_TAGCACHE
260 if (context == CONTEXT_ID3DB)
261 {
262 tagtree_current_playlist_insert(position, queue);
263 return;
264 }
265#endif
266 bool recurse = (global_settings.recursive_dir_insert == RECURSE_ON);
267 if (global_settings.recursive_dir_insert == RECURSE_ASK)
268 {
269
270 const char *lines[] = {
271 ID2P(LANG_RECURSE_DIRECTORY_QUESTION),
272 selected_file
273 };
274 const struct text_message message={lines, 2};
275 /* Ask if user wants to recurse directory */
276 recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
277 }
278
279 playlist_insert_directory(NULL, selected_file, position, queue,
280 recurse == RECURSE_ON);
281 }
282}
268 283
269/* CONTEXT_[TREE|ID3DB|STD] playlist options */ 284/* CONTEXT_[TREE|ID3DB|STD] playlist options */
270static int add_to_playlist(void* arg) 285static int add_to_playlist(void* arg)
271{ 286{
272 struct add_to_pl_param* param = arg; 287 struct add_to_pl_param* param = arg;
273 int position = param->position; 288 int position = param->position;
274 bool new_playlist = !!param->replace; 289 bool new_playlist = (param->flags & PL_REPLACE) == PL_REPLACE;
275 bool queue = !!param->queue; 290 bool queue = (param->flags & PL_QUEUE) == PL_QUEUE;
276 291
277 /* warn if replacing the playlist */ 292 /* warn if replacing the playlist */
278 if (new_playlist && !warn_on_pl_erase()) 293 if (new_playlist && !warn_on_pl_erase())
279 return 0; 294 return 0;
280 295
281 const char *lines[] = {
282 ID2P(LANG_RECURSE_DIRECTORY_QUESTION),
283 selected_file
284 };
285 const struct text_message message={lines, 2};
286
287 splash(0, ID2P(LANG_WAIT)); 296 splash(0, ID2P(LANG_WAIT));
288 297
289 if (new_playlist && global_settings.keep_current_track_on_replace_playlist) 298 if (new_playlist && global_settings.keep_current_track_on_replace_playlist)
@@ -307,38 +316,7 @@ static int add_to_playlist(void* arg)
307 playlist_set_last_shuffled_start(); 316 playlist_set_last_shuffled_start();
308 } 317 }
309 318
310#ifdef HAVE_TAGCACHE 319 op_playlist_insert_selected(position, queue);
311 if ((context == CONTEXT_ID3DB) && (selected_file_attr & ATTR_DIRECTORY))
312 {
313 tagtree_current_playlist_insert(position, queue);
314 }
315 else if (context == CONTEXT_STD && ctx_current_playlist_insert != NULL)
316 {
317 ctx_current_playlist_insert(position, queue, false);
318 }
319 else
320#endif
321 {
322 if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
323 playlist_insert_track(NULL, selected_file, position, queue, true);
324 else if (selected_file_attr & ATTR_DIRECTORY)
325 {
326 bool recurse = false;
327
328 if (global_settings.recursive_dir_insert != RECURSE_ASK)
329 recurse = (bool)global_settings.recursive_dir_insert;
330 else
331 {
332 /* Ask if user wants to recurse directory */
333 recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
334 }
335
336 playlist_insert_directory(NULL, selected_file, position, queue,
337 recurse);
338 }
339 else if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)
340 playlist_insert_playlist(NULL, selected_file, position, queue);
341 }
342 320
343 if (new_playlist && (playlist_amount() > 0)) 321 if (new_playlist && (playlist_amount() > 0))
344 { 322 {
@@ -447,18 +425,21 @@ MAKE_ONPLAYMENU(tree_playlist_menu, ID2P(LANG_PLAYING_NEXT),
447 &replace_pl_item, 425 &replace_pl_item,
448 &replace_shuf_pl_item 426 &replace_shuf_pl_item
449 ); 427 );
428
450static int treeplaylist_callback(int action, 429static int treeplaylist_callback(int action,
451 const struct menu_item_ex *this_item, 430 const struct menu_item_ex *this_item,
452 struct gui_synclist *this_list) 431 struct gui_synclist *this_list)
453{ 432{
454 (void)this_list; 433 (void)this_list;
434 int sel_file_attr = (selected_file_attr & FILE_ATTR_MASK);
435
455 switch (action) 436 switch (action)
456 { 437 {
457 case ACTION_REQUEST_MENUITEM: 438 case ACTION_REQUEST_MENUITEM:
458 if (this_item == &tree_playlist_menu) 439 if (this_item == &tree_playlist_menu)
459 { 440 {
460 if ((selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO && 441 if (sel_file_attr != FILE_ATTR_AUDIO &&
461 (selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_M3U && 442 sel_file_attr != FILE_ATTR_M3U &&
462 (selected_file_attr & ATTR_DIRECTORY) == 0) 443 (selected_file_attr & ATTR_DIRECTORY) == 0)
463 return ACTION_EXIT_MENUITEM; 444 return ACTION_EXIT_MENUITEM;
464 } 445 }
@@ -476,7 +457,7 @@ static int treeplaylist_callback(int action,
476 { 457 {
477 struct add_to_pl_param *param = this_item->function_param->param; 458 struct add_to_pl_param *param = this_item->function_param->param;
478 459
479 if (param->queue) 460 if ((param->flags & PL_QUEUE) == PL_QUEUE)
480 { 461 {
481 if (global_settings.show_queue_options != QUEUE_SHOW_AT_TOPLEVEL && 462 if (global_settings.show_queue_options != QUEUE_SHOW_AT_TOPLEVEL &&
482 !in_queue_submenu) 463 !in_queue_submenu)
@@ -489,12 +470,12 @@ static int treeplaylist_callback(int action,
489 if (!global_settings.show_shuffled_adding_options) 470 if (!global_settings.show_shuffled_adding_options)
490 return ACTION_EXIT_MENUITEM; 471 return ACTION_EXIT_MENUITEM;
491 472
492 if ((selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_M3U && 473 if (sel_file_attr != FILE_ATTR_M3U &&
493 (selected_file_attr & ATTR_DIRECTORY) == 0) 474 (selected_file_attr & ATTR_DIRECTORY) == 0)
494 return ACTION_EXIT_MENUITEM; 475 return ACTION_EXIT_MENUITEM;
495 } 476 }
496 477
497 if (!param->replace) 478 if ((param->flags & PL_REPLACE) != PL_REPLACE)
498 { 479 {
499 if (!(audio_status() & AUDIO_STATUS_PLAY)) 480 if (!(audio_status() & AUDIO_STATUS_PLAY))
500 return ACTION_EXIT_MENUITEM; 481 return ACTION_EXIT_MENUITEM;
@@ -583,482 +564,17 @@ static int cat_playlist_callback(int action,
583 return action; 564 return action;
584} 565}
585 566
586static void draw_slider(void)
587{
588 struct viewport *last_vp;
589 FOR_NB_SCREENS(i)
590 {
591 struct viewport vp;
592 int slider_height = 2*screens[i].getcharheight();
593 viewport_set_defaults(&vp, i);
594 last_vp = screens[i].set_viewport(&vp);
595 show_busy_slider(&screens[i], 1, vp.height - slider_height,
596 vp.width-2, slider_height-1);
597 screens[i].update_viewport();
598 screens[i].set_viewport(last_vp);
599 }
600}
601
602static void clear_display(bool update)
603{
604 struct viewport vp;
605 struct viewport *last_vp;
606 FOR_NB_SCREENS(i)
607 {
608 struct screen * screen = &screens[i];
609 viewport_set_defaults(&vp, screen->screen_type);
610 last_vp = screen->set_viewport(&vp);
611 screen->clear_viewport();
612 if (update) {
613 screen->update_viewport();
614 }
615 screen->set_viewport(last_vp);
616 }
617}
618
619static void splash_path(const char *path)
620{
621 clear_display(false);
622 path_basename(path, &path);
623 splash(0, path);
624 draw_slider();
625}
626
627/* Splashes the path and checks the keys */
628static bool poll_cancel_action(const char *path)
629{
630 splash_path(path);
631 return ACTION_STD_CANCEL == get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
632}
633
634static bool check_new_name(const char *basename)
635{
636 /* at least prevent escapes out of the base directory from keyboard-
637 entered filenames; the file code should reject other invalidities */
638 return *basename != '\0' && !strchr(basename, PATH_SEPCH) &&
639 !is_dotdir_name(basename);
640}
641
642static void splash_cancelled(void) 567static void splash_cancelled(void)
643{ 568{
644 clear_display(true); 569 clear_screen_buffer(true);
645 splash(HZ, ID2P(LANG_CANCEL)); 570 splash(HZ, ID2P(LANG_CANCEL));
646} 571}
647 572
648static void splash_failed(int lang_what) 573static void splash_failed(int lang_what, int err)
649{ 574{
650 cond_talk_ids_fq(lang_what, LANG_FAILED); 575 cond_talk_ids_fq(lang_what, LANG_FAILED);
651 clear_display(true); 576 clear_screen_buffer(true);
652 splashf(HZ*2, "%s %s", str(lang_what), str(LANG_FAILED)); 577 splashf(HZ*2, "%s %s (%d)", str(lang_what), str(LANG_FAILED), err);
653}
654
655/* helper function to remove a non-empty directory */
656static int remove_dir(struct dirrecurse_params *parm)
657{
658 DIR *dir = opendir(parm->path);
659 if (!dir) {
660 return -1; /* open error */
661 }
662
663 size_t append = parm->append;
664 int rc = OPRC_SUCCESS;
665
666 /* walk through the directory content */
667 while (rc == OPRC_SUCCESS) {
668 errno = 0; /* distinguish failure from eod */
669 struct dirent *entry = readdir(dir);
670 if (!entry) {
671 if (errno) {
672 rc = -1;
673 }
674 break;
675 }
676
677 struct dirinfo info = dir_get_info(dir, entry);
678 if ((info.attribute & ATTR_DIRECTORY) &&
679 is_dotdir_name(entry->d_name)) {
680 continue; /* skip these */
681 }
682
683 /* append name to current directory */
684 parm->append = append + path_append(&parm->path[append],
685 PA_SEP_HARD, entry->d_name,
686 sizeof (parm->path) - append);
687 if (parm->append >= sizeof (parm->path)) {
688 rc = -1;
689 break; /* no space left in buffer */
690 }
691
692 if (info.attribute & ATTR_DIRECTORY) {
693 /* remove a subdirectory */
694 rc = remove_dir(parm);
695 } else {
696 /* remove a file */
697 if (poll_cancel_action(parm->path)) {
698 rc = OPRC_CANCELLED;
699 break;
700 }
701
702 rc = remove(parm->path);
703 }
704
705 /* Remove basename we added above */
706 parm->path[append] = '\0';
707 }
708
709 closedir(dir);
710
711 if (rc == 0) {
712 /* remove the now empty directory */
713 if (poll_cancel_action(parm->path)) {
714 rc = OPRC_CANCELLED;
715 } else {
716 rc = rmdir(parm->path);
717 }
718 }
719
720 return rc;
721}
722
723/* share code for file and directory deletion, saves space */
724static int delete_file_dir(void)
725{
726 const char *to_delete=selected_file;
727 const int to_delete_attr=selected_file_attr;
728 if (confirm_delete_yesno(to_delete) != YESNO_YES) {
729 return 1;
730 }
731
732 clear_display(true);
733 splash(HZ/2, ID2P(LANG_DELETING));
734
735 int rc = -1;
736
737 if (to_delete_attr & ATTR_DIRECTORY) { /* true if directory */
738 struct dirrecurse_params parm;
739 parm.append = strlcpy(parm.path, to_delete, sizeof (parm.path));
740
741 if (parm.append < sizeof (parm.path)) {
742 cpu_boost(true);
743 rc = remove_dir(&parm);
744 cpu_boost(false);
745 }
746 } else {
747 rc = remove(to_delete);
748 }
749
750 if (rc < OPRC_SUCCESS) {
751 splash_failed(LANG_DELETE);
752 } else if (rc == OPRC_CANCELLED) {
753 splash_cancelled();
754 }
755
756 if (rc != OPRC_NOOP) {
757 /* Could have failed after some but not all needed changes; reload */
758 onplay_result = ONPLAY_RELOAD_DIR;
759 }
760
761 return 1;
762}
763
764static int rename_file(void)
765{
766 int rc = -1;
767 char newname[MAX_PATH];
768 const char *oldbase, *selection = selected_file;
769
770 path_basename(selection, &oldbase);
771 size_t pathlen = oldbase - selection;
772 char *newbase = newname + pathlen;
773
774 if (strmemccpy(newname, selection, sizeof (newname)) == NULL) {
775 /* Too long */
776 } else if (kbd_input(newbase, sizeof (newname) - pathlen, NULL) < 0) {
777 rc = OPRC_CANCELLED;
778 } else if (!strcmp(oldbase, newbase)) {
779 rc = OPRC_NOOP; /* No change at all */
780 } else if (check_new_name(newbase)) {
781 switch (relate(selection, newname))
782 {
783 case RELATE_DIFFERENT:
784 if (file_exists(newname)) {
785 break; /* don't overwrite */
786 }
787 /* Fall-through */
788 case RELATE_SAME:
789 rc = rename(selection, newname);
790 break;
791 case RELATE_PREFIX:
792 default:
793 break;
794 }
795 }
796
797 if (rc < OPRC_SUCCESS) {
798 splash_failed(LANG_RENAME);
799 } else if (rc == OPRC_CANCELLED) {
800 /* splash_cancelled(); kbd_input() splashes it */
801 } else if (rc == OPRC_SUCCESS) {
802 onplay_result = ONPLAY_RELOAD_DIR;
803 }
804
805 return 1;
806}
807
808static int create_dir(void)
809{
810 int rc = -1;
811 char dirname[MAX_PATH];
812 size_t pathlen = path_append(dirname, getcwd(NULL, 0), PA_SEP_HARD,
813 sizeof (dirname));
814 char *basename = dirname + pathlen;
815
816 if (pathlen >= sizeof (dirname)) {
817 /* Too long */
818 } else if (kbd_input(basename, sizeof (dirname) - pathlen, NULL) < 0) {
819 rc = OPRC_CANCELLED;
820 } else if (check_new_name(basename)) {
821 rc = mkdir(dirname);
822 }
823
824 if (rc < OPRC_SUCCESS) {
825 splash_failed(LANG_CREATE_DIR);
826 } else if (rc == OPRC_CANCELLED) {
827 /* splash_cancelled(); kbd_input() splashes it */
828 } else if (rc == OPRC_SUCCESS) {
829 onplay_result = ONPLAY_RELOAD_DIR;
830 }
831
832 return 1;
833}
834
835/* Paste a file */
836static int clipboard_pastefile(const char *src, const char *target,
837 unsigned int flags)
838{
839 int rc = -1;
840
841 while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
842 if ((flags & PASTE_OVERWRITE) || !file_exists(target)) {
843 /* Rename and possibly overwrite the file */
844 if (poll_cancel_action(src)) {
845 rc = OPRC_CANCELLED;
846 } else {
847 rc = rename(src, target);
848 }
849
850 #ifdef HAVE_MULTIVOLUME
851 if (rc < 0 && errno == EXDEV) {
852 /* Failed because cross volume rename doesn't work; force
853 a move instead */
854 flags |= PASTE_EXDEV;
855 break;
856 }
857 #endif /* HAVE_MULTIVOLUME */
858 }
859
860 return rc;
861 }
862
863 /* See if we can get the plugin buffer for the file copy buffer */
864 size_t buffersize;
865 char *buffer = (char *) plugin_get_buffer(&buffersize);
866 if (buffer == NULL || buffersize < 512) {
867 /* Not large enough, try for a disk sector worth of stack
868 instead */
869 buffersize = 512;
870 buffer = (char *)alloca(buffersize);
871 }
872
873 if (buffer == NULL) {
874 return -1;
875 }
876
877 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
878 size */
879
880 int src_fd = open(src, O_RDONLY);
881 if (src_fd >= 0) {
882 int oflag = O_WRONLY|O_CREAT;
883
884 if (!(flags & PASTE_OVERWRITE)) {
885 oflag |= O_EXCL;
886 }
887
888 int target_fd = open(target, oflag, 0666);
889 if (target_fd >= 0) {
890 off_t total_size = 0;
891 off_t next_cancel_test = 0; /* No excessive button polling */
892
893 rc = OPRC_SUCCESS;
894
895 while (rc == OPRC_SUCCESS) {
896 if (total_size >= next_cancel_test) {
897 next_cancel_test = total_size + 0x10000;
898 if (poll_cancel_action(src)) {
899 rc = OPRC_CANCELLED;
900 break;
901 }
902 }
903
904 ssize_t bytesread = read(src_fd, buffer, buffersize);
905 if (bytesread <= 0) {
906 if (bytesread < 0) {
907 rc = -1;
908 }
909 /* else eof on buffer boundary; nothing to write */
910 break;
911 }
912
913 ssize_t byteswritten = write(target_fd, buffer, bytesread);
914 if (byteswritten < bytesread) {
915 /* Some I/O error */
916 rc = -1;
917 break;
918 }
919
920 total_size += byteswritten;
921
922 if (bytesread < (ssize_t)buffersize) {
923 /* EOF with trailing bytes */
924 break;
925 }
926 }
927
928 if (rc == OPRC_SUCCESS) {
929 /* If overwriting, set the correct length if original was
930 longer */
931 rc = ftruncate(target_fd, total_size);
932 }
933
934 close(target_fd);
935
936 if (rc != OPRC_SUCCESS) {
937 /* Copy failed. Cleanup. */
938 remove(target);
939 }
940 }
941
942 close(src_fd);
943 }
944
945 if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) {
946 /* Remove the source file */
947 rc = remove(src);
948 }
949
950 return rc;
951}
952
953/* Paste a directory */
954static int clipboard_pastedirectory(struct dirrecurse_params *src,
955 struct dirrecurse_params *target,
956 unsigned int flags)
957{
958 int rc = -1;
959
960 while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
961 if ((flags & PASTE_OVERWRITE) || !file_exists(target->path)) {
962 /* Just try to move the directory */
963 if (poll_cancel_action(src->path)) {
964 rc = OPRC_CANCELLED;
965 } else {
966 rc = rename(src->path, target->path);
967 }
968
969 if (rc < 0) {
970 int errnum = errno;
971 if (errnum == ENOTEMPTY && (flags & PASTE_OVERWRITE)) {
972 /* Directory is not empty thus rename() will not do a quick
973 overwrite */
974 break;
975 }
976 #ifdef HAVE_MULTIVOLUME
977 else if (errnum == EXDEV) {
978 /* Failed because cross volume rename doesn't work; force
979 a move instead */
980 flags |= PASTE_EXDEV;
981 break;
982 }
983 #endif /* HAVE_MULTIVOLUME */
984 }
985 }
986
987 return rc;
988 }
989
990 DIR *srcdir = opendir(src->path);
991
992 if (srcdir) {
993 /* Make a directory to copy things to */
994 rc = mkdir(target->path);
995 if (rc < 0 && errno == EEXIST && (flags & PASTE_OVERWRITE)) {
996 /* Exists and overwrite was approved */
997 rc = OPRC_SUCCESS;
998 }
999 }
1000
1001 size_t srcap = src->append, targetap = target->append;
1002
1003 /* Walk through the directory content; this loop will exit as soon as
1004 there's a problem */
1005 while (rc == OPRC_SUCCESS) {
1006 errno = 0; /* Distinguish failure from eod */
1007 struct dirent *entry = readdir(srcdir);
1008 if (!entry) {
1009 if (errno) {
1010 rc = -1;
1011 }
1012 break;
1013 }
1014
1015 struct dirinfo info = dir_get_info(srcdir, entry);
1016 if ((info.attribute & ATTR_DIRECTORY) &&
1017 is_dotdir_name(entry->d_name)) {
1018 continue; /* Skip these */
1019 }
1020
1021 /* Append names to current directories */
1022 src->append = srcap +
1023 path_append(&src->path[srcap], PA_SEP_HARD, entry->d_name,
1024 sizeof(src->path) - srcap);
1025
1026 target->append = targetap +
1027 path_append(&target->path[targetap], PA_SEP_HARD, entry->d_name,
1028 sizeof (target->path) - targetap);
1029
1030 if (src->append >= sizeof (src->path) ||
1031 target->append >= sizeof (target->path)) {
1032 rc = -1; /* No space left in buffer */
1033 break;
1034 }
1035
1036 if (poll_cancel_action(src->path)) {
1037 rc = OPRC_CANCELLED;
1038 break;
1039 }
1040
1041 DEBUGF("Copy %s to %s\n", src->path, target->path);
1042
1043 if (info.attribute & ATTR_DIRECTORY) {
1044 /* Copy/move a subdirectory */
1045 rc = clipboard_pastedirectory(src, target, flags); /* recursion */
1046 } else {
1047 /* Copy/move a file */
1048 rc = clipboard_pastefile(src->path, target->path, flags);
1049 }
1050
1051 /* Remove basenames we added above */
1052 src->path[srcap] = target->path[targetap] = '\0';
1053 }
1054
1055 if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) {
1056 /* Remove the now empty directory */
1057 rc = rmdir(src->path);
1058 }
1059
1060 closedir(srcdir);
1061 return rc;
1062} 578}
1063 579
1064static bool clipboard_cut(void) 580static bool clipboard_cut(void)
@@ -1079,82 +595,27 @@ static int clipboard_paste(void)
1079 if (!clipboard.path[0]) 595 if (!clipboard.path[0])
1080 return 1; 596 return 1;
1081 597
1082 int rc = -1; 598 int rc = copy_move_fileobject(clipboard.path, getcwd(NULL, 0), clipboard.flags);
1083
1084 struct dirrecurse_params src, target;
1085 unsigned int flags = clipboard.flags;
1086
1087 /* Figure out the name of the selection */
1088 const char *nameptr;
1089 path_basename(clipboard.path, &nameptr);
1090 599
1091 /* Final target is current directory plus name of selection */
1092 target.append = path_append(target.path, getcwd(NULL, 0),
1093 nameptr, sizeof (target.path));
1094 600
1095 switch (target.append < sizeof (target.path) ? 601 clear_screen_buffer(true);
1096 relate(clipboard.path, target.path) : -1)
1097 {
1098 case RELATE_SAME:
1099 rc = OPRC_NOOP;
1100 break;
1101
1102 case RELATE_DIFFERENT:
1103 if (file_exists(target.path)) {
1104 /* If user chooses not to overwrite, cancel */
1105 if (confirm_overwrite_yesno() == YESNO_NO) {
1106 rc = OPRC_NOOVERWRT;
1107 break;
1108 }
1109
1110 flags |= PASTE_OVERWRITE;
1111 }
1112
1113 clear_display(true);
1114 splash(HZ/2, (flags & PASTE_COPY) ? ID2P(LANG_COPYING) :
1115 ID2P(LANG_MOVING));
1116
1117 /* Now figure out what we're doing */
1118 cpu_boost(true);
1119
1120 if (clipboard.attr & ATTR_DIRECTORY) {
1121 /* Copy or move a subdirectory */
1122 src.append = strlcpy(src.path, clipboard.path,
1123 sizeof (src.path));
1124 if (src.append < sizeof (src.path)) {
1125 rc = clipboard_pastedirectory(&src, &target, flags);
1126 }
1127 } else {
1128 /* Copy or move a file */
1129 rc = clipboard_pastefile(clipboard.path, target.path, flags);
1130 }
1131
1132 cpu_boost(false);
1133 break;
1134
1135 case RELATE_PREFIX:
1136 default: /* Some other relation / failure */
1137 break;
1138 }
1139
1140 clear_display(true);
1141 602
1142 switch (rc) 603 switch (rc)
1143 { 604 {
1144 case OPRC_CANCELLED: 605 case FORC_CANCELLED:
1145 splash_cancelled(); 606 splash_cancelled();
1146 /* Fallthrough */ 607 /* Fallthrough */
1147 case OPRC_SUCCESS: 608 case FORC_SUCCESS:
1148 onplay_result = ONPLAY_RELOAD_DIR; 609 onplay_result = ONPLAY_RELOAD_DIR;
1149 /* Fallthrough */ 610 /* Fallthrough */
1150 case OPRC_NOOP: 611 case FORC_NOOP:
1151 clipboard_clear_selection(&clipboard); 612 clipboard_clear_selection(&clipboard);
1152 /* Fallthrough */ 613 /* Fallthrough */
1153 case OPRC_NOOVERWRT: 614 case FORC_NOOVERWRT:
1154 break; 615 break;
1155 default: 616 default:
1156 if (rc < OPRC_SUCCESS) { 617 if (rc < FORC_SUCCESS) {
1157 splash_failed(LANG_PASTE); 618 splash_failed(LANG_PASTE, rc);
1158 onplay_result = ONPLAY_RELOAD_DIR; 619 onplay_result = ONPLAY_RELOAD_DIR;
1159 } 620 }
1160 } 621 }
@@ -1249,13 +710,57 @@ MENUITEM_FUNCTION(pitch_screen_item, 0, ID2P(LANG_PITCH),
1249 gui_syncpitchscreen_run, NULL, Icon_Audio); 710 gui_syncpitchscreen_run, NULL, Icon_Audio);
1250#endif 711#endif
1251 712
713static int clipboard_delete_selected_fileobject(void)
714{
715 int rc = delete_fileobject(selected_file);
716 if (rc < FORC_SUCCESS) {
717 splash_failed(LANG_DELETE, rc);
718 } else if (rc == FORC_CANCELLED) {
719 splash_cancelled();
720 }
721 if (rc != FORC_NOOP) {
722 /* Could have failed after some but not all needed changes; reload */
723 onplay_result = ONPLAY_RELOAD_DIR;
724 }
725 return 1;
726}
727
728static void show_result(int rc, int lang_what)
729{
730 if (rc < FORC_SUCCESS) {
731 splash_failed(lang_what, rc);
732 } else if (rc == FORC_CANCELLED) {
733 /* splash_cancelled(); kbd_input() splashes it */
734 } else if (rc == FORC_SUCCESS) {
735 onplay_result = ONPLAY_RELOAD_DIR;
736 }
737}
738
739static int clipboard_create_dir(void)
740{
741 int rc = create_dir();
742
743 show_result(rc, LANG_CREATE_DIR);
744
745 return 1;
746}
747
748static int clipboard_rename_selected_file(void)
749{
750 int rc = rename_file(selected_file);
751
752 show_result(rc, LANG_RENAME);
753
754 return 1;
755}
756
1252/* CONTEXT_[TREE|ID3DB] items */ 757/* CONTEXT_[TREE|ID3DB] items */
1253static int clipboard_callback(int action, 758static int clipboard_callback(int action,
1254 const struct menu_item_ex *this_item, 759 const struct menu_item_ex *this_item,
1255 struct gui_synclist *this_list); 760 struct gui_synclist *this_list);
1256 761
1257MENUITEM_FUNCTION(rename_file_item, 0, ID2P(LANG_RENAME), 762MENUITEM_FUNCTION(rename_file_item, 0, ID2P(LANG_RENAME),
1258 rename_file, clipboard_callback, Icon_NOICON); 763 clipboard_rename_selected_file, clipboard_callback, Icon_NOICON);
1259MENUITEM_FUNCTION(clipboard_cut_item, 0, ID2P(LANG_CUT), 764MENUITEM_FUNCTION(clipboard_cut_item, 0, ID2P(LANG_CUT),
1260 clipboard_cut, clipboard_callback, Icon_NOICON); 765 clipboard_cut, clipboard_callback, Icon_NOICON);
1261MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY), 766MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY),
@@ -1263,11 +768,11 @@ MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY),
1263MENUITEM_FUNCTION(clipboard_paste_item, 0, ID2P(LANG_PASTE), 768MENUITEM_FUNCTION(clipboard_paste_item, 0, ID2P(LANG_PASTE),
1264 clipboard_paste, clipboard_callback, Icon_NOICON); 769 clipboard_paste, clipboard_callback, Icon_NOICON);
1265MENUITEM_FUNCTION(delete_file_item, 0, ID2P(LANG_DELETE), 770MENUITEM_FUNCTION(delete_file_item, 0, ID2P(LANG_DELETE),
1266 delete_file_dir, clipboard_callback, Icon_NOICON); 771 clipboard_delete_selected_fileobject, clipboard_callback, Icon_NOICON);
1267MENUITEM_FUNCTION(delete_dir_item, 0, ID2P(LANG_DELETE_DIR), 772MENUITEM_FUNCTION(delete_dir_item, 0, ID2P(LANG_DELETE_DIR),
1268 delete_file_dir, clipboard_callback, Icon_NOICON); 773 clipboard_delete_selected_fileobject, clipboard_callback, Icon_NOICON);
1269MENUITEM_FUNCTION(create_dir_item, 0, ID2P(LANG_CREATE_DIR), 774MENUITEM_FUNCTION(create_dir_item, 0, ID2P(LANG_CREATE_DIR),
1270 create_dir, clipboard_callback, Icon_NOICON); 775 clipboard_create_dir, clipboard_callback, Icon_NOICON);
1271 776
1272/* other items */ 777/* other items */
1273static bool list_viewers(void) 778static bool list_viewers(void)
@@ -1337,12 +842,18 @@ MENUITEM_FUNCTION(add_to_faves_item, 0, ID2P(LANG_ADD_TO_FAVES),
1337 onplay_add_to_shortcuts, 842 onplay_add_to_shortcuts,
1338 clipboard_callback, Icon_NOICON); 843 clipboard_callback, Icon_NOICON);
1339 844
845static void set_dir_helper(char* dirnamebuf, size_t bufsz)
846{
847 path_append(dirnamebuf, selected_file, PA_SEP_HARD, bufsz);
848 settings_save();
849}
850
1340#if LCD_DEPTH > 1 851#if LCD_DEPTH > 1
1341static bool set_backdrop(void) 852static bool set_backdrop(void)
1342{ 853{
1343 path_append(global_settings.backdrop_file, selected_file, 854 set_dir_helper(global_settings.backdrop_file,
1344 PA_SEP_HARD, sizeof(global_settings.backdrop_file)); 855 sizeof(global_settings.backdrop_file));
1345 settings_save(); 856
1346 skin_backdrop_load_setting(); 857 skin_backdrop_load_setting();
1347 skin_backdrop_show(sb_get_backdrop(SCREEN_MAIN)); 858 skin_backdrop_show(sb_get_backdrop(SCREEN_MAIN));
1348 return true; 859 return true;
@@ -1353,9 +864,8 @@ MENUITEM_FUNCTION(set_backdrop_item, 0, ID2P(LANG_SET_AS_BACKDROP),
1353#ifdef HAVE_RECORDING 864#ifdef HAVE_RECORDING
1354static bool set_recdir(void) 865static bool set_recdir(void)
1355{ 866{
1356 path_append(global_settings.rec_directory, selected_file, 867 set_dir_helper(global_settings.rec_directory,
1357 PA_SEP_HARD, sizeof(global_settings.rec_directory)); 868 sizeof(global_settings.rec_directory));
1358 settings_save();
1359 return false; 869 return false;
1360} 870}
1361MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_RECORDING_DIR), 871MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_RECORDING_DIR),
@@ -1363,10 +873,8 @@ MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_RECORDING_DIR),
1363#endif 873#endif
1364static bool set_startdir(void) 874static bool set_startdir(void)
1365{ 875{
1366 path_append(global_settings.start_directory, selected_file, 876 set_dir_helper(global_settings.start_directory,
1367 PA_SEP_HARD, sizeof(global_settings.start_directory)); 877 sizeof(global_settings.start_directory));
1368
1369 settings_save();
1370 return false; 878 return false;
1371} 879}
1372MENUITEM_FUNCTION(set_startdir_item, 0, ID2P(LANG_START_DIR), 880MENUITEM_FUNCTION(set_startdir_item, 0, ID2P(LANG_START_DIR),
@@ -1384,16 +892,14 @@ MENUITEM_FUNCTION(set_catalogdir_item, 0, ID2P(LANG_PLAYLIST_DIR),
1384#ifdef HAVE_TAGCACHE 892#ifdef HAVE_TAGCACHE
1385static bool set_databasedir(void) 893static bool set_databasedir(void)
1386{ 894{
1387 path_append(global_settings.tagcache_db_path, selected_file,
1388 PA_SEP_SOFT, sizeof(global_settings.tagcache_db_path));
1389
1390 struct tagcache_stat *tc_stat = tagcache_get_stat(); 895 struct tagcache_stat *tc_stat = tagcache_get_stat();
1391 if (strcasecmp(global_settings.tagcache_db_path, tc_stat->db_path)) 896 if (strcasecmp(selected_file, tc_stat->db_path))
1392 { 897 {
1393 splash(HZ, ID2P(LANG_PLEASE_REBOOT)); 898 splash(HZ, ID2P(LANG_PLEASE_REBOOT));
1394 } 899 }
1395 900
1396 settings_save(); 901 set_dir_helper(global_settings.tagcache_db_path,
902 sizeof(global_settings.tagcache_db_path));
1397 return false; 903 return false;
1398} 904}
1399MENUITEM_FUNCTION(set_databasedir_item, 0, ID2P(LANG_DATABASE_DIR), 905MENUITEM_FUNCTION(set_databasedir_item, 0, ID2P(LANG_DATABASE_DIR),
@@ -1589,7 +1095,7 @@ static bool hotkey_delete_item(void)
1589 return false; 1095 return false;
1590#endif 1096#endif
1591 1097
1592 return delete_file_dir(); 1098 return clipboard_delete_selected_fileobject();
1593} 1099}
1594 1100
1595static bool hotkey_open_with(void) 1101static bool hotkey_open_with(void)