summaryrefslogtreecommitdiff
path: root/apps/onplay.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2013-08-05 22:02:45 -0400
committerMichael Sevakis <jethead71@rockbox.org>2014-08-30 03:48:23 +0200
commit7d1a47cf13726c95ac46027156cc12dd9da5b855 (patch)
treeeb20d07656806479a8e1fea25887a490ea30d1d8 /apps/onplay.c
parent95a4c3afcd53a1f8b835dec33de51f9c304de4d9 (diff)
downloadrockbox-7d1a47cf13726c95ac46027156cc12dd9da5b855.tar.gz
rockbox-7d1a47cf13726c95ac46027156cc12dd9da5b855.zip
Rewrite filesystem code (WIP)
This patch redoes the filesystem code from the FAT driver up to the clipboard code in onplay.c. Not every aspect of this is finished therefore it is still "WIP". I don't wish to do too much at once (haha!). What is left to do is get dircache back in the sim and find an implementation for the dircache indicies in the tagcache and playlist code or do something else that has the same benefit. Leaving these out for now does not make anything unusable. All the basics are done. Phone app code should probably get vetted (and app path handling just plain rewritten as environment expansions); the SDL app and Android run well. Main things addressed: 1) Thread safety: There is none right now in the trunk code. Most of what currently works is luck when multiple threads are involved or multiple descriptors to the same file are open. 2) POSIX compliance: Many of the functions behave nothing like their counterparts on a host system. This leads to inconsistent code or very different behavior from native to hosted. One huge offender was rename(). Going point by point would fill a book. 3) Actual running RAM usage: Many targets will use less RAM and less stack space (some more RAM because I upped the number of cache buffers for large memory). There's very little memory lying fallow in rarely-used areas (see 'Key core changes' below). Also, all targets may open the same number of directory streams whereas before those with less than 8MB RAM were limited to 8, not 12 implying those targets will save slightly less. 4) Performance: The test_disk plugin shows markedly improved performance, particularly in the area of (uncached) directory scanning, due partly to more optimal directory reading and to a better sector cache algorithm. Uncached times tend to be better while there is a bit of a slowdown in dircache due to it being a bit heavier of an implementation. It's not noticeable by a human as far as I can say. Key core changes: 1) Files and directories share core code and data structures. 2) The filesystem code knows which descriptors refer to same file. This ensures that changes from one stream are appropriately reflected in every open descriptor for that file (fileobj_mgr.c). 3) File and directory cache buffers are borrowed from the main sector cache. This means that when they are not in use by a file, they are not wasted, but used for the cache. Most of the time, only a few of them are needed. It also means that adding more file and directory handles is less expensive. All one must do in ensure a large enough cache to borrow from. 4) Relative path components are supported and the namespace is unified. It does not support full relative paths to an implied current directory; what is does support is use of "." and "..". Adding the former would not be very difficult. The namespace is unified in the sense that volumes may be specified several times along with relative parts, e.g.: "/<0>/foo/../../<1>/bar" :<=> "/<1>/bar". 5) Stack usage is down due to sharing of data, static allocation and less duplication of strings on the stack. This requires more serialization than I would like but since the number of threads is limited to a low number, the tradoff in favor of the stack seems reasonable. 6) Separates and heirarchicalizes (sic) the SIM and APP filesystem code. SIM path and volume handling is just like the target. Some aspects of the APP file code get more straightforward (e.g. no path hashing is needed). Dircache: Deserves its own section. Dircache is new but pays homage to the old. The old one was not compatible and so it, since it got redone, does all the stuff it always should have done such as: 1) It may be update and used at any time during the build process. No longer has one to wait for it to finish building to do basic file management (create, remove, rename, etc.). 2) It does not need to be either fully scanned or completely disabled; it can be incomplete (i.e. overfilled, missing paths), still be of benefit and be correct. 3) Handles mounting and dismounting of individual volumes which means a full rebuild is not needed just because you pop a new SD card in the slot. Now, because it reuses its freed entry data, may rebuild only that volume. 4) Much more fundamental to the file code. When it is built, it is the keeper of the master file list whether enabled or not ("disabled" is just a state of the cache). Its must always to ready to be started and bind all streams opened prior to being enabled. 5) Maintains any short filenames in OEM format which means that it does not need to be rebuilt when changing the default codepage. Miscellaneous Compatibility: 1) Update any other code that would otherwise not work such as the hotswap mounting code in various card drivers. 2) File management: Clipboard needed updating because of the behavioral changes. Still needs a little more work on some finer points. 3) Remove now-obsolete functionality such as the mutex's "no preempt" flag (which was only for the prior FAT driver). 4) struct dirinfo uses time_t rather than raw FAT directory entry time fields. I plan to follow up on genericizing everything there (i.e. no FAT attributes). 5) unicode.c needed some redoing so that the file code does not try try to load codepages during a scan, which is actually a problem with the current code. The default codepage, if any is required, is now kept in RAM separarately (bufalloced) from codepages specified to iso_decode() (which must not be bufalloced because the conversion may be done by playback threads). Brings with it some additional reusable core code: 1) Revised file functions: Reusable code that does things such as safe path concatenation and parsing without buffer limitations or data duplication. Variants that copy or alter the input path may be based off these. To do: 1) Put dircache functionality back in the sim. Treating it internally as a different kind of file system seems the best approach at this time. 2) Restore use of dircache indexes in the playlist and database or something effectively the same. Since the cache doesn't have to be complete in order to be used, not getting a hit on the cache doesn't unambiguously say if the path exists or not. Change-Id: Ia30f3082a136253e3a0eae0784e3091d138915c8 Reviewed-on: http://gerrit.rockbox.org/566 Reviewed-by: Michael Sevakis <jethead71@rockbox.org> Tested: Michael Sevakis <jethead71@rockbox.org>
Diffstat (limited to 'apps/onplay.c')
-rw-r--r--apps/onplay.c844
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
68static int context; 68static int context;
69static const char* selected_file = NULL; 69static const char *selected_file = NULL;
70static int selected_file_attr = 0; 70static int selected_file_attr = 0;
71static int onplay_result = ONPLAY_OK; 71static int onplay_result = ONPLAY_OK;
72static char clipboard_selection[MAX_PATH];
73static int clipboard_selection_attr = 0;
74static 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 */
83struct 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
89enum 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 */
98enum 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
107static 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 */
115static 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 */
123static 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 */ 549static void clear_display(bool update)
496static 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
566static 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 */
575static 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
581static 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
588static 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
597static 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
605static void splash_cancelled(void)
606{
607 clear_display(true);
608 splash(HZ, ID2P(LANG_CANCEL));
609}
610
611static 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 */
619static 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 */
556static bool delete_file_dir(void) 687static 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
596static bool rename_file(void) 725static 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
615static bool create_dir(void) 769static 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 */
645static bool clipboard_clip(bool copy) 797static 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
655static 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
660static 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 */
666static 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 */ 915static int clipboard_pastedirectory(struct dirrecurse_params *src,
761static 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) { 1025static 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; 1031static 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 */
847static bool clipboard_paste(void) 1038static 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();