diff options
Diffstat (limited to 'apps/onplay.c')
-rw-r--r-- | apps/onplay.c | 844 |
1 files changed, 517 insertions, 327 deletions
diff --git a/apps/onplay.c b/apps/onplay.c index 7c5f517090..091680e949 100644 --- a/apps/onplay.c +++ b/apps/onplay.c | |||
@@ -62,16 +62,13 @@ | |||
62 | #include "statusbar-skinned.h" | 62 | #include "statusbar-skinned.h" |
63 | #include "pitchscreen.h" | 63 | #include "pitchscreen.h" |
64 | #include "viewport.h" | 64 | #include "viewport.h" |
65 | #include "filefuncs.h" | 65 | #include "pathfuncs.h" |
66 | #include "shortcuts.h" | 66 | #include "shortcuts.h" |
67 | 67 | ||
68 | static int context; | 68 | static int context; |
69 | static const char* selected_file = NULL; | 69 | static const char *selected_file = NULL; |
70 | static int selected_file_attr = 0; | 70 | static int selected_file_attr = 0; |
71 | static int onplay_result = ONPLAY_OK; | 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; | ||
75 | 72 | ||
76 | /* redefine MAKE_MENU so the MENU_EXITAFTERTHISMENU flag can be added easily */ | 73 | /* redefine MAKE_MENU so the MENU_EXITAFTERTHISMENU flag can be added easily */ |
77 | #define MAKE_ONPLAYMENU( name, str, callback, icon, ... ) \ | 74 | #define MAKE_ONPLAYMENU( name, str, callback, icon, ... ) \ |
@@ -82,6 +79,63 @@ static bool clipboard_is_copy = false; | |||
82 | MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ | 79 | MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ |
83 | { (void*)name##_},{.callback_and_desc = & name##__}}; | 80 | { (void*)name##_},{.callback_and_desc = & name##__}}; |
84 | 81 | ||
82 | /* Used for directory move, copy and delete */ | ||
83 | struct dirrecurse_params | ||
84 | { | ||
85 | char path[MAX_PATH]; /* Buffer for full path */ | ||
86 | size_t append; /* Append position in 'path' for stack push */ | ||
87 | }; | ||
88 | |||
89 | enum clipboard_op_flags | ||
90 | { | ||
91 | PASTE_CUT = 0x00, /* Is a move (cut) operation (default) */ | ||
92 | PASTE_COPY = 0x01, /* Is a copy operation */ | ||
93 | PASTE_OVERWRITE = 0x02, /* Overwrite destination */ | ||
94 | PASTE_EXDEV = 0x04, /* Actually copy/move across volumes */ | ||
95 | }; | ||
96 | |||
97 | /* result codec of various onplay operations */ | ||
98 | enum onplay_result_code | ||
99 | { | ||
100 | /* Anything < 0 is failure */ | ||
101 | OPRC_SUCCESS = 0, /* All operations completed successfully */ | ||
102 | OPRC_NOOP = 1, /* Operation didn't need to do anything */ | ||
103 | OPRC_CANCELLED = 2, /* Operation was cancelled by user */ | ||
104 | OPRC_NOOVERWRT = 3, | ||
105 | }; | ||
106 | |||
107 | static struct clipboard | ||
108 | { | ||
109 | char path[MAX_PATH]; /* Clipped file's path */ | ||
110 | unsigned int attr; /* Clipped file's attributes */ | ||
111 | unsigned int flags; /* Operation type flags */ | ||
112 | } clipboard; | ||
113 | |||
114 | /* Empty the clipboard */ | ||
115 | static void clipboard_clear_selection(struct clipboard *clip) | ||
116 | { | ||
117 | clip->path[0] = '\0'; | ||
118 | clip->attr = 0; | ||
119 | clip->flags = 0; | ||
120 | } | ||
121 | |||
122 | /* Store the selection in the clipboard */ | ||
123 | static bool clipboard_clip(struct clipboard *clip, const char *path, | ||
124 | unsigned int attr, unsigned int flags) | ||
125 | { | ||
126 | /* if it fits it clips */ | ||
127 | if (strlcpy(clip->path, path, sizeof (clip->path)) | ||
128 | < sizeof (clip->path)) { | ||
129 | clip->attr = attr; | ||
130 | clip->flags = flags; | ||
131 | return true; | ||
132 | } | ||
133 | else { | ||
134 | clipboard_clear_selection(clip); | ||
135 | return false; | ||
136 | } | ||
137 | } | ||
138 | |||
85 | /* ----------------------------------------------------------------------- */ | 139 | /* ----------------------------------------------------------------------- */ |
86 | /* Displays the bookmark menu options for the user to decide. This is an */ | 140 | /* Displays the bookmark menu options for the user to decide. This is an */ |
87 | /* interface function. */ | 141 | /* interface function. */ |
@@ -492,438 +546,578 @@ static void draw_slider(void) | |||
492 | #define draw_slider() | 546 | #define draw_slider() |
493 | #endif | 547 | #endif |
494 | 548 | ||
495 | /* helper function to remove a non-empty directory */ | 549 | static void clear_display(bool update) |
496 | static int remove_dir(char* dirname, int len) | ||
497 | { | 550 | { |
498 | int result = 0; | 551 | struct viewport vp; |
499 | DIR* dir; | ||
500 | int dirlen = strlen(dirname); | ||
501 | 552 | ||
502 | dir = opendir(dirname); | 553 | FOR_NB_SCREENS(i) |
503 | if (!dir) | 554 | { |
555 | struct screen * screen = &screens[i]; | ||
556 | viewport_set_defaults(&vp, screen->screen_type); | ||
557 | screen->set_viewport(&vp); | ||
558 | screen->clear_viewport(); | ||
559 | if (update) { | ||
560 | screen->update_viewport(); | ||
561 | } | ||
562 | screen->set_viewport(NULL); | ||
563 | } | ||
564 | } | ||
565 | |||
566 | static void splash_path(const char *path) | ||
567 | { | ||
568 | clear_display(false); | ||
569 | path_basename(path, &path); | ||
570 | splash(0, path); | ||
571 | draw_slider(); | ||
572 | } | ||
573 | |||
574 | /* Splashes the path and checks the keys */ | ||
575 | static bool poll_cancel_action(const char *path) | ||
576 | { | ||
577 | splash_path(path); | ||
578 | return ACTION_STD_CANCEL == get_action(CONTEXT_STD, TIMEOUT_NOBLOCK); | ||
579 | } | ||
580 | |||
581 | static int confirm_overwrite(void) | ||
582 | { | ||
583 | static const char *lines[] = { ID2P(LANG_REALLY_OVERWRITE) }; | ||
584 | static const struct text_message message = { lines, 1 }; | ||
585 | return gui_syncyesno_run(&message, NULL, NULL); | ||
586 | } | ||
587 | |||
588 | static int confirm_delete(const char *file) | ||
589 | { | ||
590 | const char *lines[] = { ID2P(LANG_REALLY_DELETE), file }; | ||
591 | const char *yes_lines[] = { ID2P(LANG_DELETING), file }; | ||
592 | const struct text_message message = { lines, 2 }; | ||
593 | const struct text_message yes_message = { yes_lines, 2 }; | ||
594 | return gui_syncyesno_run(&message, &yes_message, NULL); | ||
595 | } | ||
596 | |||
597 | static bool check_new_name(const char *basename) | ||
598 | { | ||
599 | /* at least prevent escapes out of the base directory from keyboard- | ||
600 | entered filenames; the file code should reject other invalidities */ | ||
601 | return *basename != '\0' && !strchr(basename, PATH_SEPCH) && | ||
602 | !is_dotdir_name(basename); | ||
603 | } | ||
604 | |||
605 | static void splash_cancelled(void) | ||
606 | { | ||
607 | clear_display(true); | ||
608 | splash(HZ, ID2P(LANG_CANCEL)); | ||
609 | } | ||
610 | |||
611 | static void splash_failed(int lang_what) | ||
612 | { | ||
613 | cond_talk_ids_fq(lang_what, LANG_FAILED); | ||
614 | clear_display(true); | ||
615 | splashf(HZ*2, "%s %s", str(lang_what), str(LANG_FAILED)); | ||
616 | } | ||
617 | |||
618 | /* helper function to remove a non-empty directory */ | ||
619 | static int remove_dir(struct dirrecurse_params *parm) | ||
620 | { | ||
621 | DIR *dir = opendir(parm->path); | ||
622 | if (!dir) { | ||
504 | return -1; /* open error */ | 623 | return -1; /* open error */ |
624 | } | ||
505 | 625 | ||
506 | while(true) | 626 | size_t append = parm->append; |
507 | { | 627 | int rc = OPRC_SUCCESS; |
508 | struct dirent* entry; | 628 | |
509 | /* walk through the directory content */ | 629 | /* walk through the directory content */ |
510 | entry = readdir(dir); | 630 | while (rc == OPRC_SUCCESS) { |
511 | if (!entry) | 631 | errno = 0; /* distinguish failure from eod */ |
632 | struct dirent *entry = readdir(dir); | ||
633 | if (!entry) { | ||
634 | if (errno) { | ||
635 | rc = -1; | ||
636 | } | ||
512 | break; | 637 | break; |
638 | } | ||
639 | |||
513 | struct dirinfo info = dir_get_info(dir, entry); | 640 | struct dirinfo info = dir_get_info(dir, entry); |
514 | dirname[dirlen] ='\0'; | 641 | if ((info.attribute & ATTR_DIRECTORY) && |
515 | /* inform the user which dir we're deleting */ | 642 | is_dotdir_name(entry->d_name)) { |
516 | splash(0, dirname); | 643 | continue; /* skip these */ |
644 | } | ||
517 | 645 | ||
518 | /* append name to current directory */ | 646 | /* append name to current directory */ |
519 | snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name); | 647 | parm->append = append + path_append(&parm->path[append], |
520 | if (info.attribute & ATTR_DIRECTORY) | 648 | PA_SEP_HARD, entry->d_name, |
521 | { /* remove a subdirectory */ | 649 | sizeof (parm->path) - append); |
522 | if (!strcmp((char *)entry->d_name, ".") || | 650 | if (parm->append >= sizeof (parm->path)) { |
523 | !strcmp((char *)entry->d_name, "..")) | 651 | rc = -1; |
524 | continue; /* skip these */ | 652 | break; /* no space left in buffer */ |
525 | |||
526 | result = remove_dir(dirname, len); /* recursion */ | ||
527 | if (result) | ||
528 | break; /* or better continue, delete what we can? */ | ||
529 | } | ||
530 | else | ||
531 | { /* remove a file */ | ||
532 | draw_slider(); | ||
533 | result = remove(dirname); | ||
534 | } | 653 | } |
535 | if(ACTION_STD_CANCEL == get_action(CONTEXT_STD,TIMEOUT_NOBLOCK)) | 654 | |
536 | { | 655 | if (info.attribute & ATTR_DIRECTORY) { |
537 | splash(HZ, ID2P(LANG_CANCEL)); | 656 | /* remove a subdirectory */ |
538 | result = -1; | 657 | rc = remove_dir(parm); |
539 | break; | 658 | } else { |
659 | /* remove a file */ | ||
660 | if (poll_cancel_action(parm->path)) { | ||
661 | rc = OPRC_CANCELLED; | ||
662 | break; | ||
663 | } | ||
664 | |||
665 | rc = remove(parm->path); | ||
540 | } | 666 | } |
667 | |||
668 | /* Remove basename we added above */ | ||
669 | parm->path[append] = '\0'; | ||
541 | } | 670 | } |
542 | closedir(dir); | ||
543 | 671 | ||
544 | if (!result) | 672 | closedir(dir); |
545 | { /* remove the now empty directory */ | ||
546 | dirname[dirlen] = '\0'; /* terminate to original length */ | ||
547 | 673 | ||
548 | result = rmdir(dirname); | 674 | if (rc == 0) { |
675 | /* remove the now empty directory */ | ||
676 | if (poll_cancel_action(parm->path)) { | ||
677 | rc = OPRC_CANCELLED; | ||
678 | } else { | ||
679 | rc = rmdir(parm->path); | ||
680 | } | ||
549 | } | 681 | } |
550 | 682 | ||
551 | return result; | 683 | return rc; |
552 | } | 684 | } |
553 | 685 | ||
554 | |||
555 | /* share code for file and directory deletion, saves space */ | 686 | /* share code for file and directory deletion, saves space */ |
556 | static bool delete_file_dir(void) | 687 | static int delete_file_dir(void) |
557 | { | 688 | { |
558 | char file_to_delete[MAX_PATH]; | 689 | if (confirm_delete(selected_file) != YESNO_YES) { |
559 | strcpy(file_to_delete, selected_file); | 690 | return 1; |
691 | } | ||
560 | 692 | ||
561 | const char *lines[]={ | 693 | clear_display(true); |
562 | ID2P(LANG_REALLY_DELETE), | 694 | splash(HZ/2, str(LANG_DELETING)); |
563 | file_to_delete | ||
564 | }; | ||
565 | const char *yes_lines[]={ | ||
566 | ID2P(LANG_DELETING), | ||
567 | file_to_delete | ||
568 | }; | ||
569 | 695 | ||
570 | const struct text_message message={lines, 2}; | 696 | int rc = -1; |
571 | const struct text_message yes_message={yes_lines, 2}; | ||
572 | 697 | ||
573 | if(gui_syncyesno_run(&message, &yes_message, NULL)!=YESNO_YES) | 698 | if (selected_file_attr & ATTR_DIRECTORY) { /* true if directory */ |
574 | return false; | 699 | struct dirrecurse_params parm; |
700 | parm.append = strlcpy(parm.path, selected_file, sizeof (parm.path)); | ||
575 | 701 | ||
576 | splash(0, str(LANG_DELETING)); | 702 | if (parm.append < sizeof (parm.path)) { |
703 | cpu_boost(true); | ||
704 | rc = remove_dir(&parm); | ||
705 | cpu_boost(false); | ||
706 | } | ||
707 | } else { | ||
708 | rc = remove(selected_file); | ||
709 | } | ||
577 | 710 | ||
578 | int res; | 711 | if (rc < OPRC_SUCCESS) { |
579 | if (selected_file_attr & ATTR_DIRECTORY) /* true if directory */ | 712 | splash_failed(LANG_DELETE); |
580 | { | 713 | } else if (rc == OPRC_CANCELLED) { |
581 | char pathname[MAX_PATH]; /* space to go deep */ | 714 | splash_cancelled(); |
582 | cpu_boost(true); | ||
583 | strlcpy(pathname, file_to_delete, sizeof(pathname)); | ||
584 | res = remove_dir(pathname, sizeof(pathname)); | ||
585 | cpu_boost(false); | ||
586 | } | 715 | } |
587 | else | ||
588 | res = remove(file_to_delete); | ||
589 | 716 | ||
590 | if (!res) | 717 | if (rc != OPRC_NOOP) { |
718 | /* Could have failed after some but not all needed changes; reload */ | ||
591 | onplay_result = ONPLAY_RELOAD_DIR; | 719 | onplay_result = ONPLAY_RELOAD_DIR; |
720 | } | ||
592 | 721 | ||
593 | return (res == 0); | 722 | return 1; |
594 | } | 723 | } |
595 | 724 | ||
596 | static bool rename_file(void) | 725 | static int rename_file(void) |
597 | { | 726 | { |
727 | int rc = -1; | ||
598 | char newname[MAX_PATH]; | 728 | char newname[MAX_PATH]; |
599 | char* ptr = strrchr(selected_file, '/') + 1; | 729 | const char *oldbase, *selection = selected_file; |
600 | int pathlen = (ptr - selected_file); | 730 | |
601 | strlcpy(newname, selected_file, sizeof(newname)); | 731 | path_basename(selection, &oldbase); |
602 | if (!kbd_input(newname + pathlen, (sizeof newname)-pathlen)) { | 732 | size_t pathlen = oldbase - selection; |
603 | if (!strlen(newname + pathlen) || | 733 | char *newbase = newname + pathlen; |
604 | (rename(selected_file, newname) < 0)) { | 734 | |
605 | cond_talk_ids_fq(LANG_RENAME, LANG_FAILED); | 735 | if (strlcpy(newname, selection, sizeof (newname)) >= sizeof (newname)) { |
606 | splashf(HZ*2, "%s %s", str(LANG_RENAME), str(LANG_FAILED)); | 736 | /* Too long */ |
737 | } else if (kbd_input(newbase, sizeof (newname) - pathlen) < 0) { | ||
738 | rc = OPRC_CANCELLED; | ||
739 | } else if (!strcmp(oldbase, newbase)) { | ||
740 | rc = OPRC_NOOP; /* No change at all */ | ||
741 | } else if (check_new_name(newbase)) { | ||
742 | switch (relate(selection, newname)) | ||
743 | { | ||
744 | case RELATE_DIFFERENT: | ||
745 | if (file_exists(newname)) { | ||
746 | break; /* don't overwrite */ | ||
747 | } | ||
748 | /* Fall-through */ | ||
749 | case RELATE_SAME: | ||
750 | rc = rename(selection, newname); | ||
751 | break; | ||
752 | case RELATE_PREFIX: | ||
753 | default: | ||
754 | break; | ||
607 | } | 755 | } |
608 | else | ||
609 | onplay_result = ONPLAY_RELOAD_DIR; | ||
610 | } | 756 | } |
611 | 757 | ||
612 | return false; | 758 | if (rc < OPRC_SUCCESS) { |
759 | splash_failed(LANG_RENAME); | ||
760 | } else if (rc == OPRC_CANCELLED) { | ||
761 | /* splash_cancelled(); kbd_input() splashes it */ | ||
762 | } else if (rc == OPRC_SUCCESS) { | ||
763 | onplay_result = ONPLAY_RELOAD_DIR; | ||
764 | } | ||
765 | |||
766 | return 1; | ||
613 | } | 767 | } |
614 | 768 | ||
615 | static bool create_dir(void) | 769 | static int create_dir(void) |
616 | { | 770 | { |
771 | int rc = -1; | ||
617 | char dirname[MAX_PATH]; | 772 | char dirname[MAX_PATH]; |
618 | char *cwd; | 773 | size_t pathlen = path_append(dirname, getcwd(NULL, 0), PA_SEP_HARD, |
619 | int rc; | 774 | sizeof (dirname)); |
620 | int pathlen; | 775 | char *basename = dirname + pathlen; |
621 | 776 | ||
622 | cwd = getcwd(NULL, 0); | 777 | if (pathlen >= sizeof (dirname)) { |
623 | memset(dirname, 0, sizeof dirname); | 778 | /* Too long */ |
624 | 779 | } else if (kbd_input(basename, sizeof (dirname) - pathlen) < 0) { | |
625 | snprintf(dirname, sizeof dirname, "%s/", cwd[1] ? cwd : ""); | 780 | rc = OPRC_CANCELLED; |
626 | 781 | } else if (check_new_name(basename)) { | |
627 | pathlen = strlen(dirname); | 782 | rc = mkdir(dirname); |
628 | rc = kbd_input(dirname + pathlen, (sizeof dirname)-pathlen); | 783 | } |
629 | if (rc < 0) | ||
630 | return false; | ||
631 | 784 | ||
632 | rc = mkdir(dirname); | 785 | if (rc < OPRC_SUCCESS) { |
633 | if (rc < 0) { | 786 | splash_failed(LANG_CREATE_DIR); |
634 | cond_talk_ids_fq(LANG_CREATE_DIR, LANG_FAILED); | 787 | } else if (rc == OPRC_CANCELLED) { |
635 | splashf(HZ, (unsigned char *)"%s %s", str(LANG_CREATE_DIR), | 788 | /* splash_cancelled(); kbd_input() splashes it */ |
636 | str(LANG_FAILED)); | 789 | } else if (rc == OPRC_SUCCESS) { |
637 | } else { | ||
638 | onplay_result = ONPLAY_RELOAD_DIR; | 790 | onplay_result = ONPLAY_RELOAD_DIR; |
639 | } | 791 | } |
640 | 792 | ||
641 | return true; | 793 | return 1; |
642 | } | 794 | } |
643 | 795 | ||
644 | /* Store the current selection in the clipboard */ | 796 | /* Paste a file */ |
645 | static bool clipboard_clip(bool copy) | 797 | static int clipboard_pastefile(const char *src, const char *target, |
798 | unsigned int flags) | ||
646 | { | 799 | { |
647 | clipboard_selection[0] = 0; | 800 | int rc = -1; |
648 | strlcpy(clipboard_selection, selected_file, sizeof(clipboard_selection)); | 801 | |
649 | clipboard_selection_attr = selected_file_attr; | 802 | while (!(flags & (PASTE_COPY | PASTE_EXDEV))) { |
650 | clipboard_is_copy = copy; | 803 | if ((flags & PASTE_OVERWRITE) || !file_exists(target)) { |
651 | 804 | /* Rename and possibly overwrite the file */ | |
652 | return true; | 805 | if (poll_cancel_action(src)) { |
653 | } | 806 | rc = OPRC_CANCELLED; |
807 | } else { | ||
808 | rc = rename(src, target); | ||
809 | } | ||
654 | 810 | ||
655 | static bool clipboard_cut(void) | 811 | #ifdef HAVE_MULTIVOLUME |
656 | { | 812 | if (rc < 0 && errno == EXDEV) { |
657 | return clipboard_clip(false); | 813 | /* Failed because cross volume rename doesn't work; force |
658 | } | 814 | a move instead */ |
815 | flags |= PASTE_EXDEV; | ||
816 | break; | ||
817 | } | ||
818 | #endif /* HAVE_MULTIVOLUME */ | ||
819 | } | ||
659 | 820 | ||
660 | static bool clipboard_copy(void) | 821 | return rc; |
661 | { | 822 | } |
662 | return clipboard_clip(true); | ||
663 | } | ||
664 | 823 | ||
665 | /* Paste a file to a new directory. Will overwrite always. */ | 824 | /* See if we can get the plugin buffer for the file copy buffer */ |
666 | static bool clipboard_pastefile(const char *src, const char *target, bool copy) | ||
667 | { | ||
668 | int src_fd, target_fd; | ||
669 | size_t buffersize; | 825 | size_t buffersize; |
670 | ssize_t size, bytesread, byteswritten; | 826 | char *buffer = (char *) plugin_get_buffer(&buffersize); |
671 | char *buffer; | 827 | if (buffer == NULL || buffersize < 512) { |
672 | bool result = false; | 828 | /* Not large enough, try for a disk sector worth of stack |
673 | 829 | instead */ | |
674 | if (copy) { | 830 | buffersize = 512; |
675 | /* See if we can get the plugin buffer for the file copy buffer */ | 831 | buffer = (char *)alloca(buffersize); |
676 | buffer = (char *) plugin_get_buffer(&buffersize); | 832 | } |
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); | ||
682 | } | ||
683 | 833 | ||
684 | if (buffer == NULL) { | 834 | if (buffer == NULL) { |
685 | return false; | 835 | return -1; |
686 | } | 836 | } |
687 | 837 | ||
688 | buffersize &= ~0x1ff; /* Round buffer size to multiple of sector | 838 | buffersize &= ~0x1ff; /* Round buffer size to multiple of sector |
689 | size */ | 839 | size */ |
690 | 840 | ||
691 | src_fd = open(src, O_RDONLY); | 841 | int src_fd = open(src, O_RDONLY); |
842 | if (src_fd >= 0) { | ||
843 | int oflag = O_WRONLY|O_CREAT; | ||
692 | 844 | ||
693 | if (src_fd >= 0) { | 845 | if (!(flags & PASTE_OVERWRITE)) { |
694 | target_fd = creat(target, 0666); | 846 | oflag |= O_EXCL; |
847 | } | ||
695 | 848 | ||
696 | if (target_fd >= 0) { | 849 | int target_fd = open(target, oflag, 0666); |
697 | result = true; | 850 | if (target_fd >= 0) { |
851 | off_t total_size = 0; | ||
852 | off_t next_cancel_test = 0; /* No excessive button polling */ | ||
698 | 853 | ||
699 | size = filesize(src_fd); | 854 | rc = OPRC_SUCCESS; |
700 | 855 | ||
701 | if (size == -1) { | 856 | while (rc == OPRC_SUCCESS) { |
702 | result = false; | 857 | if (total_size >= next_cancel_test) { |
858 | next_cancel_test = total_size + 0x10000; | ||
859 | if (poll_cancel_action(src)) { | ||
860 | rc = OPRC_CANCELLED; | ||
861 | break; | ||
862 | } | ||
703 | } | 863 | } |
704 | 864 | ||
705 | while(size > 0) { | 865 | ssize_t bytesread = read(src_fd, buffer, buffersize); |
706 | bytesread = read(src_fd, buffer, buffersize); | 866 | if (bytesread <= 0) { |
707 | 867 | if (bytesread < 0) { | |
708 | if (bytesread == -1) { | 868 | rc = -1; |
709 | result = false; | ||
710 | break; | ||
711 | } | 869 | } |
870 | /* else eof on buffer boundary; nothing to write */ | ||
871 | break; | ||
872 | } | ||
712 | 873 | ||
713 | size -= bytesread; | 874 | ssize_t byteswritten = write(target_fd, buffer, bytesread); |
714 | 875 | if (byteswritten < bytesread) { | |
715 | while(bytesread > 0) { | 876 | /* Some I/O error */ |
716 | byteswritten = write(target_fd, buffer, bytesread); | 877 | rc = -1; |
717 | 878 | break; | |
718 | if (byteswritten < 0) { | ||
719 | result = false; | ||
720 | size = 0; | ||
721 | break; | ||
722 | } | ||
723 | |||
724 | bytesread -= byteswritten; | ||
725 | draw_slider(); | ||
726 | } | ||
727 | } | 879 | } |
728 | 880 | ||
729 | close(target_fd); | 881 | total_size += byteswritten; |
730 | 882 | ||
731 | /* Copy failed. Cleanup. */ | 883 | if (bytesread < (ssize_t)buffersize) { |
732 | if (!result) { | 884 | /* EOF with trailing bytes */ |
733 | remove(target); | 885 | break; |
734 | } | 886 | } |
735 | } | 887 | } |
736 | 888 | ||
737 | close(src_fd); | 889 | if (rc == OPRC_SUCCESS) { |
738 | } | 890 | /* If overwriting, set the correct length if original was |
739 | } else { | 891 | longer */ |
740 | result = rename(src, target) == 0; | 892 | rc = ftruncate(target_fd, total_size); |
741 | #ifdef HAVE_MULTIVOLUME | 893 | } |
742 | if (!result) { | 894 | |
743 | if (errno == EXDEV) { | 895 | close(target_fd); |
744 | /* Failed because cross volume rename doesn't work. Copy | 896 | |
745 | instead */ | 897 | if (rc != OPRC_SUCCESS) { |
746 | result = clipboard_pastefile(src, target, true); | 898 | /* Copy failed. Cleanup. */ |
747 | 899 | remove(target); | |
748 | if (result) { | ||
749 | result = remove(src) == 0; | ||
750 | } | ||
751 | } | 900 | } |
752 | } | 901 | } |
753 | #endif | 902 | |
903 | close(src_fd); | ||
754 | } | 904 | } |
755 | 905 | ||
756 | return result; | 906 | if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) { |
907 | /* Remove the source file */ | ||
908 | rc = remove(src); | ||
909 | } | ||
910 | |||
911 | return rc; | ||
757 | } | 912 | } |
758 | 913 | ||
759 | /* Paste a directory to a new location. Designed to be called by | 914 | /* Paste a directory */ |
760 | clipboard_paste */ | 915 | static int clipboard_pastedirectory(struct dirrecurse_params *src, |
761 | static bool clipboard_pastedirectory(char *src, int srclen, char *target, | 916 | struct dirrecurse_params *target, |
762 | int targetlen, bool copy) | 917 | unsigned int flags) |
763 | { | 918 | { |
764 | DIR *srcdir; | 919 | int rc = -1; |
765 | int srcdirlen = strlen(src); | 920 | |
766 | int targetdirlen = strlen(target); | 921 | while (!(flags & (PASTE_COPY | PASTE_EXDEV))) { |
767 | bool result = true; | 922 | if ((flags & PASTE_OVERWRITE) || !file_exists(target->path)) { |
768 | 923 | /* Just try to move the directory */ | |
769 | if (!file_exists(target)) { | 924 | if (poll_cancel_action(src->path)) { |
770 | if (!copy) { | 925 | rc = OPRC_CANCELLED; |
771 | /* Just move the directory */ | 926 | } else { |
772 | result = rename(src, target) == 0; | 927 | rc = rename(src->path, target->path); |
928 | } | ||
773 | 929 | ||
774 | #ifdef HAVE_MULTIVOLUME | 930 | if (rc < 0) { |
775 | if (!result && errno == EXDEV) { | 931 | int errnum = errno; |
776 | /* Try a copy as we're going across devices */ | 932 | if (errnum == ENOTEMPTY && (flags & PASTE_OVERWRITE)) { |
777 | result = clipboard_pastedirectory(src, srclen, target, | 933 | /* Directory is not empty thus rename() will not do a quick |
778 | targetlen, true); | 934 | overwrite */ |
779 | 935 | break; | |
780 | /* If it worked, remove the source directory */ | ||
781 | if (result) { | ||
782 | remove_dir(src, srclen); | ||
783 | } | 936 | } |
937 | #ifdef HAVE_MULTIVOLUME | ||
938 | else if (errnum == EXDEV) { | ||
939 | /* Failed because cross volume rename doesn't work; force | ||
940 | a move instead */ | ||
941 | flags |= PASTE_EXDEV; | ||
942 | break; | ||
943 | } | ||
944 | #endif /* HAVE_MULTIVOLUME */ | ||
784 | } | 945 | } |
785 | #endif | ||
786 | return result; | ||
787 | } else { | ||
788 | /* Make a directory to copy things to */ | ||
789 | result = mkdir(target) == 0; | ||
790 | } | 946 | } |
791 | } | ||
792 | 947 | ||
793 | /* Check if something went wrong already */ | 948 | return rc; |
794 | if (!result) { | ||
795 | return result; | ||
796 | } | 949 | } |
797 | 950 | ||
798 | srcdir = opendir(src); | 951 | DIR *srcdir = opendir(src->path); |
799 | if (!srcdir) { | 952 | |
800 | return false; | 953 | if (srcdir) { |
954 | /* Make a directory to copy things to */ | ||
955 | rc = mkdir(target->path); | ||
956 | if (rc < 0 && errno == EEXIST && (flags & PASTE_OVERWRITE)) { | ||
957 | /* Exists and overwrite was approved */ | ||
958 | rc = OPRC_SUCCESS; | ||
959 | } | ||
801 | } | 960 | } |
802 | 961 | ||
803 | /* This loop will exit as soon as there's a problem */ | 962 | size_t srcap = src->append, targetap = target->append; |
804 | while(result) | 963 | |
805 | { | 964 | /* Walk through the directory content; this loop will exit as soon as |
806 | struct dirent* entry; | 965 | there's a problem */ |
807 | /* walk through the directory content */ | 966 | while (rc == OPRC_SUCCESS) { |
808 | entry = readdir(srcdir); | 967 | errno = 0; /* Distinguish failure from eod */ |
809 | if (!entry) | 968 | struct dirent *entry = readdir(srcdir); |
969 | if (!entry) { | ||
970 | if (errno) { | ||
971 | rc = -1; | ||
972 | } | ||
810 | break; | 973 | break; |
974 | } | ||
811 | 975 | ||
812 | struct dirinfo info = dir_get_info(srcdir, entry); | 976 | struct dirinfo info = dir_get_info(srcdir, entry); |
813 | /* append name to current directory */ | 977 | if ((info.attribute & ATTR_DIRECTORY) && |
814 | snprintf(src+srcdirlen, srclen-srcdirlen, "/%s", entry->d_name); | 978 | is_dotdir_name(entry->d_name)) { |
815 | snprintf(target+targetdirlen, targetlen-targetdirlen, "/%s", | 979 | continue; /* Skip these */ |
816 | entry->d_name); | 980 | } |
817 | 981 | ||
818 | DEBUGF("Copy %s to %s\n", src, target); | 982 | /* Append names to current directories */ |
983 | src->append = srcap + | ||
984 | path_append(&src->path[srcap], PA_SEP_HARD, entry->d_name, | ||
985 | sizeof(src->path) - srcap); | ||
819 | 986 | ||
820 | if (info.attribute & ATTR_DIRECTORY) | 987 | target->append = targetap + |
821 | { /* copy/move a subdirectory */ | 988 | path_append(&target->path[targetap], PA_SEP_HARD, entry->d_name, |
822 | if (!strcmp((char *)entry->d_name, ".") || | 989 | sizeof (target->path) - targetap); |
823 | !strcmp((char *)entry->d_name, "..")) | ||
824 | continue; /* skip these */ | ||
825 | 990 | ||
826 | result = clipboard_pastedirectory(src, srclen, target, targetlen, | 991 | if (src->append >= sizeof (src->path) || |
827 | copy); /* recursion */ | 992 | target->append >= sizeof (target->path)) { |
993 | rc = -1; /* No space left in buffer */ | ||
994 | break; | ||
828 | } | 995 | } |
829 | else | 996 | |
830 | { /* copy/move a file */ | 997 | if (poll_cancel_action(src->path)) { |
831 | draw_slider(); | 998 | rc = OPRC_CANCELLED; |
832 | result = clipboard_pastefile(src, target, copy); | 999 | break; |
1000 | } | ||
1001 | |||
1002 | DEBUGF("Copy %s to %s\n", src->path, target->path); | ||
1003 | |||
1004 | if (info.attribute & ATTR_DIRECTORY) { | ||
1005 | /* Copy/move a subdirectory */ | ||
1006 | rc = clipboard_pastedirectory(src, target, flags); /* recursion */ | ||
1007 | } else { | ||
1008 | /* Copy/move a file */ | ||
1009 | rc = clipboard_pastefile(src->path, target->path, flags); | ||
833 | } | 1010 | } |
1011 | |||
1012 | /* Remove basenames we added above */ | ||
1013 | src->path[srcap] = target->path[targetap] = '\0'; | ||
1014 | } | ||
1015 | |||
1016 | if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) { | ||
1017 | /* Remove the now empty directory */ | ||
1018 | rc = rmdir(src->path); | ||
834 | } | 1019 | } |
835 | 1020 | ||
836 | closedir(srcdir); | 1021 | closedir(srcdir); |
1022 | return rc; | ||
1023 | } | ||
837 | 1024 | ||
838 | if (result) { | 1025 | static bool clipboard_cut(void) |
839 | src[srcdirlen] = '\0'; /* terminate to original length */ | 1026 | { |
840 | target[targetdirlen] = '\0'; /* terminate to original length */ | 1027 | return clipboard_clip(&clipboard, selected_file, selected_file_attr, |
841 | } | 1028 | PASTE_CUT); |
1029 | } | ||
842 | 1030 | ||
843 | return result; | 1031 | static bool clipboard_copy(void) |
1032 | { | ||
1033 | return clipboard_clip(&clipboard, selected_file, selected_file_attr, | ||
1034 | PASTE_COPY); | ||
844 | } | 1035 | } |
845 | 1036 | ||
846 | /* Paste the clipboard to the current directory */ | 1037 | /* Paste the clipboard to the current directory */ |
847 | static bool clipboard_paste(void) | 1038 | static int clipboard_paste(void) |
848 | { | 1039 | { |
849 | char target[MAX_PATH]; | 1040 | if (!clipboard.path[0]) |
850 | char *cwd, *nameptr; | 1041 | return 1; |
851 | bool success; | ||
852 | 1042 | ||
853 | static const char *lines[]={ID2P(LANG_REALLY_OVERWRITE)}; | 1043 | int rc = -1; |
854 | static const struct text_message message={lines, 1}; | ||
855 | 1044 | ||
856 | /* Get the name of the current directory */ | 1045 | struct dirrecurse_params src, target; |
857 | cwd = getcwd(NULL, 0); | 1046 | unsigned int flags = clipboard.flags; |
858 | 1047 | ||
859 | /* Figure out the name of the selection */ | 1048 | /* Figure out the name of the selection */ |
860 | nameptr = strrchr(clipboard_selection, '/'); | 1049 | const char *nameptr; |
1050 | path_basename(clipboard.path, &nameptr); | ||
861 | 1051 | ||
862 | /* Final target is current directory plus name of selection */ | 1052 | /* Final target is current directory plus name of selection */ |
863 | snprintf(target, sizeof(target), "%s%s", cwd[1] ? cwd : "", nameptr); | 1053 | target.append = path_append(target.path, getcwd(NULL, 0), |
864 | 1054 | nameptr, sizeof (target.path)); | |
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; | ||
869 | } | ||
870 | 1055 | ||
871 | if (clipboard_is_copy) { | 1056 | switch (target.append < sizeof (target.path) ? |
872 | splash(0, ID2P(LANG_COPYING)); | 1057 | relate(clipboard.path, target.path) : -1) |
873 | } | ||
874 | else | ||
875 | { | 1058 | { |
876 | splash(0, ID2P(LANG_MOVING)); | 1059 | case RELATE_SAME: |
877 | } | 1060 | rc = OPRC_NOOP; |
1061 | break; | ||
1062 | |||
1063 | case RELATE_DIFFERENT: | ||
1064 | if (file_exists(target.path)) { | ||
1065 | /* If user chooses not to overwrite, cancel */ | ||
1066 | if (confirm_overwrite() == YESNO_NO) { | ||
1067 | rc = OPRC_NOOVERWRT; | ||
1068 | break; | ||
1069 | } | ||
878 | 1070 | ||
879 | /* Now figure out what we're doing */ | 1071 | flags |= PASTE_OVERWRITE; |
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))) | ||
886 | { | ||
887 | /* Do not allow the user to paste a directory into a dir they are | ||
888 | copying */ | ||
889 | success = 0; | ||
890 | } | 1072 | } |
891 | else | ||
892 | { | ||
893 | strlcpy(srcpath, clipboard_selection, sizeof(srcpath)); | ||
894 | strlcpy(targetpath, target, sizeof(targetpath)); | ||
895 | 1073 | ||
896 | success = clipboard_pastedirectory(srcpath, sizeof(srcpath), | 1074 | clear_display(true); |
897 | target, sizeof(targetpath), clipboard_is_copy); | 1075 | splash(HZ/2, (flags & PASTE_COPY) ? ID2P(LANG_COPYING) : |
1076 | ID2P(LANG_MOVING)); | ||
898 | 1077 | ||
899 | if (success && !clipboard_is_copy) | 1078 | /* Now figure out what we're doing */ |
900 | { | 1079 | cpu_boost(true); |
901 | strlcpy(srcpath, clipboard_selection, sizeof(srcpath)); | 1080 | |
902 | remove_dir(srcpath, sizeof(srcpath)); | 1081 | if (clipboard.attr & ATTR_DIRECTORY) { |
1082 | /* Copy or move a subdirectory */ | ||
1083 | src.append = strlcpy(src.path, clipboard.path, | ||
1084 | sizeof (src.path)); | ||
1085 | if (src.append < sizeof (src.path)) { | ||
1086 | rc = clipboard_pastedirectory(&src, &target, flags); | ||
903 | } | 1087 | } |
1088 | } else { | ||
1089 | /* Copy or move a file */ | ||
1090 | rc = clipboard_pastefile(clipboard.path, target.path, flags); | ||
904 | } | 1091 | } |
905 | } else { | 1092 | |
906 | success = clipboard_pastefile(clipboard_selection, target, | 1093 | cpu_boost(false); |
907 | clipboard_is_copy); | 1094 | break; |
1095 | |||
1096 | case RELATE_PREFIX: | ||
1097 | default: /* Some other relation / failure */ | ||
1098 | break; | ||
908 | } | 1099 | } |
909 | cpu_boost(false); | ||
910 | 1100 | ||
911 | /* Did it work? */ | 1101 | clear_display(true); |
912 | if (success) { | ||
913 | /* Reset everything */ | ||
914 | clipboard_selection[0] = 0; | ||
915 | clipboard_selection_attr = 0; | ||
916 | clipboard_is_copy = false; | ||
917 | 1102 | ||
918 | /* Force reload of the current directory */ | 1103 | switch (rc) |
1104 | { | ||
1105 | case OPRC_CANCELLED: | ||
1106 | splash_cancelled(); | ||
1107 | case OPRC_SUCCESS: | ||
919 | onplay_result = ONPLAY_RELOAD_DIR; | 1108 | onplay_result = ONPLAY_RELOAD_DIR; |
920 | } else { | 1109 | case OPRC_NOOP: |
921 | cond_talk_ids_fq(LANG_PASTE, LANG_FAILED); | 1110 | clipboard_clear_selection(&clipboard); |
922 | splashf(HZ, (unsigned char *)"%s %s", str(LANG_PASTE), | 1111 | case OPRC_NOOVERWRT: |
923 | str(LANG_FAILED)); | 1112 | break; |
1113 | default: | ||
1114 | if (rc < OPRC_SUCCESS) { | ||
1115 | splash_failed(LANG_PASTE); | ||
1116 | onplay_result = ONPLAY_RELOAD_DIR; | ||
1117 | } | ||
924 | } | 1118 | } |
925 | 1119 | ||
926 | return true; | 1120 | return 1; |
927 | } | 1121 | } |
928 | 1122 | ||
929 | #ifdef HAVE_TAGCACHE | 1123 | #ifdef HAVE_TAGCACHE |
@@ -1094,15 +1288,12 @@ static int clipboard_callback(int action,const struct menu_item_ex *this_item) | |||
1094 | { | 1288 | { |
1095 | case ACTION_REQUEST_MENUITEM: | 1289 | case ACTION_REQUEST_MENUITEM: |
1096 | #ifdef HAVE_MULTIVOLUME | 1290 | #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 */ | 1291 | /* no rename+delete for volumes */ |
1103 | if ((selected_file_attr & ATTR_VOLUME) && | 1292 | if ((selected_file_attr & ATTR_VOLUME) && |
1104 | (this_item == &delete_file_item || | 1293 | (this_item == &rename_file_item || |
1105 | this_item == &list_viewers_item)) | 1294 | this_item == &delete_dir_item || |
1295 | this_item == &clipboard_cut_item || | ||
1296 | this_item == &list_viewers_item)) | ||
1106 | return ACTION_EXIT_MENUITEM; | 1297 | return ACTION_EXIT_MENUITEM; |
1107 | #endif | 1298 | #endif |
1108 | #ifdef HAVE_TAGCACHE | 1299 | #ifdef HAVE_TAGCACHE |
@@ -1117,7 +1308,7 @@ static int clipboard_callback(int action,const struct menu_item_ex *this_item) | |||
1117 | #endif | 1308 | #endif |
1118 | if (this_item == &clipboard_paste_item) | 1309 | if (this_item == &clipboard_paste_item) |
1119 | { /* visible if there is something to paste */ | 1310 | { /* visible if there is something to paste */ |
1120 | return (clipboard_selection[0] != 0) ? | 1311 | return (clipboard.path[0] != 0) ? |
1121 | action : ACTION_EXIT_MENUITEM; | 1312 | action : ACTION_EXIT_MENUITEM; |
1122 | } | 1313 | } |
1123 | else if (this_item == &create_dir_item) | 1314 | else if (this_item == &create_dir_item) |
@@ -1232,8 +1423,7 @@ static bool delete_item(void) | |||
1232 | { | 1423 | { |
1233 | #ifdef HAVE_MULTIVOLUME | 1424 | #ifdef HAVE_MULTIVOLUME |
1234 | /* no delete for volumes */ | 1425 | /* no delete for volumes */ |
1235 | if ((selected_file_attr & FAT_ATTR_VOLUME) || | 1426 | if (selected_file_attr & ATTR_VOLUME) |
1236 | (selected_file_attr & ATTR_VOLUME)) | ||
1237 | return false; | 1427 | return false; |
1238 | #endif | 1428 | #endif |
1239 | return delete_file_dir(); | 1429 | return delete_file_dir(); |