summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2023-03-30 12:24:02 +0100
committerAidan MacDonald <amachronic@protonmail.com>2023-10-28 14:54:02 -0400
commit90e35716e312b9446515263f75e9e7cb66483c2c (patch)
treebe7a60fd2e1934a3a5bcaf75d455e5c8752c4e26
parent1ee152b5a45b907958511e5477e0f8f18458fc15 (diff)
downloadrockbox-90e35716e312b9446515263f75e9e7cb66483c2c.tar.gz
rockbox-90e35716e312b9446515263f75e9e7cb66483c2c.zip
playlist: Rewrite playlist_save(), optimization & fixes
playlist_save() was a poorly thought out mess. This fixes the glaring issues and hopefully ensures that saving the playlist never loses state (such as queued tracks or modified status) after save+resume. Indices are now updated on the fly, which is faster and needs no extra memory. But if an error occurs, the playlist will be corrupted. There is currently no attempt to handle this since errors should be unlikely, but some error handling needs to be added in the future. Change-Id: If8a5dbd6a596460be08ee0b7bab9f24337886ea4
-rw-r--r--apps/menus/playlist_menu.c2
-rw-r--r--apps/playlist.c551
-rw-r--r--apps/playlist.h3
3 files changed, 291 insertions, 265 deletions
diff --git a/apps/menus/playlist_menu.c b/apps/menus/playlist_menu.c
index 49ff56795a..87ed7428ea 100644
--- a/apps/menus/playlist_menu.c
+++ b/apps/menus/playlist_menu.c
@@ -83,7 +83,7 @@ int save_playlist_screen(struct playlist_info* playlist)
83 playlist ? playlist->filename : 83 playlist ? playlist->filename :
84 playlist_get_current()->filename)) 84 playlist_get_current()->filename))
85 { 85 {
86 playlist_save(playlist, temp, NULL, 0); 86 playlist_save(playlist, temp);
87 87
88 /* reload in case playlist was saved to cwd */ 88 /* reload in case playlist was saved to cwd */
89 reload_directory(); 89 reload_directory();
diff --git a/apps/playlist.c b/apps/playlist.c
index 652f805aea..b82736bbac 100644
--- a/apps/playlist.c
+++ b/apps/playlist.c
@@ -111,6 +111,7 @@
111#include "dircache.h" 111#include "dircache.h"
112#endif 112#endif
113#include "logf.h" 113#include "logf.h"
114#include "panic.h"
114 115
115#if 0//def ROCKBOX_HAS_LOGDISKF 116#if 0//def ROCKBOX_HAS_LOGDISKF
116#undef DEBUGF 117#undef DEBUGF
@@ -679,101 +680,6 @@ static void display_playlist_count(int count, const unsigned char *fmt,
679} 680}
680 681
681/* 682/*
682 * recreate the control file based on current playlist entries
683 */
684static int recreate_control_unlocked(struct playlist_info* playlist)
685{
686 const char file_suffix[] = "_temp\0";
687 char temp_file[MAX_PATH + sizeof(file_suffix)];
688 int temp_fd = -1;
689 int i;
690 int result = 0;
691
692 temp_file[0] = 0;
693
694 if(playlist->control_fd >= 0)
695 {
696 char* dir = playlist->filename;
697 char* file = playlist->filename+playlist->dirlen;
698 char c = playlist->filename[playlist->dirlen-1];
699
700 pl_close_control(playlist);
701
702 snprintf(temp_file, sizeof(temp_file), "%s%s",
703 playlist->control_filename, file_suffix);
704
705 if (rename(playlist->control_filename, temp_file) < 0)
706 return -1;
707
708 temp_fd = open(temp_file, O_RDONLY);
709 if (temp_fd < 0)
710 return -1;
711
712 playlist->control_fd = open(playlist->control_filename,
713 O_CREAT|O_RDWR|O_TRUNC, 0666);
714 if (playlist->control_fd < 0)
715 {
716 close(temp_fd);
717 return -1;
718 }
719
720 playlist->filename[playlist->dirlen-1] = '\0';
721
722 update_control_unlocked(playlist, PLAYLIST_COMMAND_PLAYLIST,
723 PLAYLIST_CONTROL_FILE_VERSION, -1, dir, file, NULL);
724
725 playlist->filename[playlist->dirlen-1] = c;
726
727 if (result < 0)
728 {
729 close(temp_fd);
730 return result;
731 }
732 }
733
734 playlist->seed = 0;
735
736 for (i=0; i<playlist->amount; i++)
737 {
738 if (playlist->indices[i] & PLAYLIST_INSERT_TYPE_MASK)
739 {
740 bool queue = playlist->indices[i] & PLAYLIST_QUEUED;
741 char inserted_file[MAX_PATH+1];
742
743 lseek(temp_fd, playlist->indices[i] & PLAYLIST_SEEK_MASK,
744 SEEK_SET);
745 read_line(temp_fd, inserted_file, sizeof(inserted_file));
746
747 result = fdprintf(playlist->control_fd, "%c:%d:%d:",
748 queue?'Q':'A', i, playlist->last_insert_pos);
749 if (result > 0)
750 {
751 /* save the position in file where name is written */
752 int seek_pos = lseek(playlist->control_fd, 0, SEEK_CUR);
753
754 result = fdprintf(playlist->control_fd, "%s\n",
755 inserted_file);
756
757 playlist->indices[i] =
758 (playlist->indices[i] & ~PLAYLIST_SEEK_MASK) | seek_pos;
759 }
760
761 if (result < 0)
762 break;
763 }
764 }
765
766 close(temp_fd);
767 remove(temp_file);
768 fsync(playlist->control_fd);
769
770 if (result < 0)
771 return result;
772
773 return 0;
774}
775
776/*
777 * calculate track offsets within a playlist file 683 * calculate track offsets within a playlist file
778 */ 684 */
779static int add_indices_to_playlist(struct playlist_info* playlist, 685static int add_indices_to_playlist(struct playlist_info* playlist,
@@ -3560,173 +3466,6 @@ void playlist_resume_track(int start_index, unsigned int crc,
3560 playlist_start(0, 0, 0); 3466 playlist_start(0, 0, 0);
3561} 3467}
3562 3468
3563/* save the current dynamic playlist to specified file. The
3564 * temp_buffer (if not NULL) is used as a scratchpad when loading indices
3565 * (slow if not used). */
3566int playlist_save(struct playlist_info* playlist, char *filename,
3567 void* temp_buffer, size_t temp_buffer_size)
3568{
3569 int fd;
3570 int i, index;
3571 int count = 0;
3572 char path[MAX_PATH+1];
3573 char tmp_buf[MAX_PATH+1];
3574 int result = 0;
3575 int *seek_buf;
3576 bool reparse;
3577 ssize_t pathlen;
3578
3579 ALIGN_BUFFER(temp_buffer, temp_buffer_size, sizeof(int));
3580 seek_buf = temp_buffer;
3581
3582 /* without temp_buffer, or when it's depleted, and we overwrite the current
3583 * playlist then the newly saved playlist has to be reparsed. With
3584 * sufficient temp_buffer the indicies be remembered and added without
3585 * reparsing */
3586 reparse = temp_buffer_size == 0;
3587
3588 if (!playlist)
3589 playlist = &current_playlist;
3590
3591 if (playlist->amount <= 0)
3592 return -1;
3593
3594 /* use current working directory as base for pathname */
3595 pathlen = format_track_path(path, filename, sizeof(path), PATH_ROOTSTR);
3596 if (pathlen < 0)
3597 return -1;
3598
3599 /* Use temporary pathname and overwrite/rename later */
3600 if (strlcat(path, "_temp", sizeof(path)) >= sizeof (path))
3601 return -1;
3602
3603 if (is_m3u8_name(path))
3604 {
3605 fd = open_utf8(path, O_CREAT|O_WRONLY|O_TRUNC);
3606 }
3607 else
3608 {
3609 /* some applications require a BOM to read the file properly */
3610 fd = open(path, O_CREAT|O_WRONLY|O_TRUNC, 0666);
3611 }
3612
3613 if (fd < 0)
3614 {
3615 notify_access_error();
3616 return -1;
3617 }
3618
3619 display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), false);
3620
3621 cpu_boost(true);
3622
3623 index = playlist->first_index;
3624 for (i=0; i<playlist->amount; i++)
3625 {
3626 /* user abort */
3627 if (action_userabort(TIMEOUT_NOBLOCK))
3628 {
3629 result = -1;
3630 break;
3631 }
3632
3633 /* Don't save queued files */
3634 if (!(playlist->indices[index] & PLAYLIST_QUEUED))
3635 {
3636 if (get_track_filename(playlist, index, tmp_buf, sizeof(tmp_buf)))
3637 {
3638 result = -1;
3639 break;
3640 }
3641
3642 if (!reparse)
3643 seek_buf[count] = filesize(fd);
3644
3645 if (fdprintf(fd, "%s\n", tmp_buf) < 0)
3646 {
3647 notify_access_error();
3648 result = -1;
3649 break;
3650 }
3651
3652 count++;
3653 /* when our temp buffer is depleted we have to fall
3654 * back to reparsing the playlist (slow) */
3655 if (count*sizeof(int) >= temp_buffer_size)
3656 reparse = true;
3657
3658 if ((count % PLAYLIST_DISPLAY_COUNT) == 0)
3659 display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT),
3660 false);
3661
3662 yield();
3663 }
3664
3665 index = (index+1)%playlist->amount;
3666 }
3667
3668 close(fd);
3669 fd = -1;
3670
3671 display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), true);
3672
3673 if (result >= 0)
3674 {
3675 strmemcpy(tmp_buf, path, pathlen); /* remove "_temp" */
3676
3677 dc_thread_stop(playlist);
3678 playlist_write_lock(playlist);
3679
3680 if (!rename(path, tmp_buf))
3681 {
3682 fd = open_utf8(tmp_buf, O_RDONLY);
3683 if (fd >= 0 && fsamefile(fd, playlist->fd) > 0)
3684 {
3685 /* Replace the current playlist with the new one and update
3686 indices */
3687 close(playlist->fd);
3688 playlist->fd = fd;
3689 fd = -1;
3690
3691 if (!reparse)
3692 {
3693 index = playlist->first_index;
3694 for (i=0, count=0; i<playlist->amount; i++)
3695 {
3696 if (!(playlist->indices[index] & PLAYLIST_QUEUED))
3697 {
3698 playlist->indices[index] = seek_buf[count];
3699 count++;
3700 }
3701 index = (index+1)%playlist->amount;
3702 }
3703 }
3704 else
3705 {
3706 NOTEF("reparsing current playlist (slow)");
3707 playlist->amount = 0;
3708 add_indices_to_playlist(playlist, temp_buffer,
3709 temp_buffer_size);
3710 }
3711
3712 /* we need to recreate control because inserted tracks are
3713 now part of the playlist and shuffle has been invalidated */
3714 result = recreate_control_unlocked(playlist);
3715 }
3716 }
3717
3718 playlist_write_unlock(playlist);
3719 dc_thread_start(playlist, true);
3720 }
3721
3722 if (fd >= 0)
3723 close(fd);
3724
3725 cpu_boost(false);
3726
3727 return result;
3728}
3729
3730/* 3469/*
3731 * Set the specified playlist as the current. 3470 * Set the specified playlist as the current.
3732 * NOTE: You will get undefined behaviour if something is already playing so 3471 * NOTE: You will get undefined behaviour if something is already playing so
@@ -3938,3 +3677,291 @@ int playlist_update_resume_info(const struct mp3entry* id3)
3938 3677
3939 return 0; 3678 return 0;
3940} 3679}
3680
3681static int pl_get_tempname(const char *filename, char *buf, size_t bufsz)
3682{
3683 if (strlcpy(buf, filename, bufsz) >= bufsz)
3684 return -1;
3685
3686 if (strlcat(buf, "_temp", bufsz) >= bufsz)
3687 return -1;
3688
3689 return 0;
3690}
3691
3692/*
3693 * Save all non-queued tracks to an M3U playlist with the given filename.
3694 * On success, the playlist is updated to point to the new playlist file.
3695 * On failure, the playlist filename is unchanged, but playlist indices
3696 * may be trashed; the current playlist should be reloaded.
3697 *
3698 * Returns 0 on success, < 0 on error, and > 0 if user canceled.
3699 */
3700static int pl_save_playlist(struct playlist_info* playlist,
3701 const char *filename,
3702 char *tmpbuf, size_t tmpsize)
3703{
3704 int fd, index, num_saved;
3705 off_t offset;
3706 int ret, err;
3707
3708 if (pl_get_tempname(filename, tmpbuf, tmpsize))
3709 return -1;
3710
3711 /*
3712 * We always save playlists as UTF-8. Add a BOM only when
3713 * saving to the .m3u file extension.
3714 */
3715 if (is_m3u8_name(filename))
3716 fd = open(tmpbuf, O_CREAT|O_WRONLY|O_TRUNC, 0666);
3717 else
3718 fd = open_utf8(tmpbuf, O_CREAT|O_WRONLY|O_TRUNC);
3719
3720 if (fd < 0)
3721 return -1;
3722
3723 offset = lseek(fd, 0, SEEK_CUR);
3724 index = playlist->first_index;
3725 num_saved = 0;
3726
3727 display_playlist_count(num_saved, ID2P(LANG_PLAYLIST_SAVE_COUNT), true);
3728
3729 for (int i = 0; i < playlist->amount; ++i, ++index)
3730 {
3731 if (index == playlist->amount)
3732 index = 0;
3733
3734 /* TODO: Disabled for now, as we can't restore the playlist state yet */
3735 if (false && action_userabort(TIMEOUT_NOBLOCK))
3736 {
3737 err = 1;
3738 goto error;
3739 }
3740
3741 /* Do not save queued files to playlist. */
3742 if (playlist->indices[index] & PLAYLIST_QUEUED)
3743 continue;
3744
3745 if (get_track_filename(playlist, index, tmpbuf, tmpsize))
3746 {
3747 err = -2;
3748 goto error;
3749 }
3750
3751 /* Update seek offset so it points into the new file. */
3752 playlist->indices[index] &= ~PLAYLIST_INSERT_TYPE_MASK;
3753 playlist->indices[index] &= ~PLAYLIST_SEEK_MASK;
3754 playlist->indices[index] |= offset;
3755
3756 ret = fdprintf(fd, "%s\n", tmpbuf);
3757 if (ret < 0)
3758 {
3759 err = -3;
3760 goto error;
3761 }
3762
3763 offset += ret;
3764 num_saved++;
3765
3766 if ((num_saved % PLAYLIST_DISPLAY_COUNT) == 0)
3767 display_playlist_count(num_saved, ID2P(LANG_PLAYLIST_SAVE_COUNT), false);
3768 }
3769
3770 display_playlist_count(num_saved, ID2P(LANG_PLAYLIST_SAVE_COUNT), true);
3771 close(fd);
3772 pl_close_playlist(playlist);
3773
3774 pl_get_tempname(filename, tmpbuf, tmpsize);
3775 if (rename(tmpbuf, filename))
3776 return -4;
3777
3778 strcpy(tmpbuf, filename);
3779 char *dir = tmpbuf;
3780 char *file = strrchr(tmpbuf, '/') + 1;
3781 file[-1] = '\0';
3782
3783 update_playlist_filename_unlocked(playlist, dir, file);
3784 return 0;
3785
3786error:
3787 close(fd);
3788 pl_get_tempname(filename, tmpbuf, tmpsize);
3789 remove(tmpbuf);
3790 return err;
3791}
3792
3793/*
3794 * Update the control file after saving the playlist under a new name.
3795 * A new control file is generated, containing the new playlist filename.
3796 * Queued tracks are copied to the new control file.
3797 *
3798 * On success, the new control file replaces the old control file.
3799 * On failure, indices may be trashed and the playlist should be
3800 * reloaded. This may not be possible if the playlist was overwritten.
3801 */
3802static int pl_save_update_control(struct playlist_info* playlist,
3803 char *tmpbuf, size_t tmpsize)
3804{
3805 int old_fd, index;
3806 int err;
3807 char c;
3808 bool any_queued = false;
3809
3810 /* Nothing to update if we don't have any control file */
3811 if (!playlist->control_created)
3812 return 0;
3813
3814 if (pl_get_tempname(playlist->control_filename, tmpbuf, tmpsize))
3815 return -1;
3816
3817 /* Close the existing control file, reopen it as read-only */
3818 pl_close_control(playlist);
3819 old_fd = open(playlist->control_filename, O_RDONLY);
3820 if (old_fd < 0)
3821 return -2;
3822
3823 /* Create new control file, pointing it at a tempfile */
3824 playlist->control_fd = open(tmpbuf, O_CREAT|O_RDWR|O_TRUNC, 0666);
3825 if (playlist->control_fd < 0)
3826 {
3827 close(old_fd);
3828 return -3;
3829 }
3830
3831 /* Write out playlist filename */
3832 c = playlist->filename[playlist->dirlen-1];
3833 playlist->filename[playlist->dirlen-1] = '\0';
3834
3835 err = update_control_unlocked(playlist, PLAYLIST_COMMAND_PLAYLIST,
3836 PLAYLIST_CONTROL_FILE_VERSION, -1,
3837 playlist->filename,
3838 &playlist->filename[playlist->dirlen], NULL);
3839
3840 playlist->filename[playlist->dirlen-1] = c;
3841
3842 if (err <= 0)
3843 return -4;
3844
3845 index = playlist->first_index;
3846 for (int i = 0; i < playlist->amount; ++i, ++index)
3847 {
3848 if (index == playlist->amount)
3849 index = 0;
3850
3851 /* We only need to update queued files */
3852 if (!(playlist->indices[index] & PLAYLIST_QUEUED))
3853 continue;
3854
3855 /* Read filename from old control file */
3856 lseek(old_fd, playlist->indices[index] & PLAYLIST_SEEK_MASK, SEEK_SET);
3857 read_line(old_fd, tmpbuf, tmpsize);
3858
3859 /* Write it out to the new control file */
3860 int seekpos;
3861 err = update_control_unlocked(playlist, PLAYLIST_COMMAND_QUEUE,
3862 i, playlist->last_insert_pos,
3863 tmpbuf, NULL, &seekpos);
3864 if (err <= 0)
3865 return -5;
3866
3867 /* Update seek offset for the new control file. */
3868 playlist->indices[index] &= ~PLAYLIST_SEEK_MASK;
3869 playlist->indices[index] |= seekpos;
3870 any_queued = true;
3871 }
3872
3873 /* Preserve modified state */
3874 if (playlist_modified(playlist))
3875 {
3876 if (any_queued)
3877 {
3878 err = update_control_unlocked(playlist, PLAYLIST_COMMAND_FLAGS,
3879 PLAYLIST_FLAG_MODIFIED, 0, NULL, NULL, NULL);
3880 if (err <= 0)
3881 return -6;
3882 }
3883 else
3884 {
3885 playlist->flags &= ~PLAYLIST_FLAG_MODIFIED;
3886 }
3887 }
3888
3889 /* Clear dirplay flag, since we now point at a playlist */
3890 playlist->flags &= ~PLAYLIST_FLAG_DIRPLAY;
3891
3892 /* Reset shuffle seed */
3893 playlist->seed = 0;
3894
3895 pl_close_control(playlist);
3896 close(old_fd);
3897 remove(playlist->control_filename);
3898
3899 /* TODO: Check for errors? The old control file is gone by this point... */
3900 pl_get_tempname(playlist->control_filename, tmpbuf, tmpsize);
3901 rename(tmpbuf, playlist->control_filename);
3902
3903 playlist->control_fd = open(playlist->control_filename, O_RDWR);
3904 playlist->control_created = (playlist->control_fd >= 0);
3905 return 0;
3906}
3907
3908int playlist_save(struct playlist_info* playlist, char *filename)
3909{
3910 char save_path[MAX_PATH+1];
3911 char tmpbuf[MAX_PATH+1];
3912 ssize_t pathlen;
3913 int rc = 0;
3914
3915 if (!playlist)
3916 playlist = &current_playlist;
3917
3918 pathlen = format_track_path(save_path, filename, sizeof(save_path), PATH_ROOTSTR);
3919 if (pathlen < 0)
3920 return -1;
3921
3922 cpu_boost(true);
3923 dc_thread_stop(playlist);
3924 playlist_write_lock(playlist);
3925
3926 if (playlist->amount <= 0)
3927 {
3928 rc = -1;
3929 goto error;
3930 }
3931
3932 rc = pl_save_playlist(playlist, save_path, tmpbuf, sizeof(tmpbuf));
3933 if (rc < 0)
3934 {
3935 // TODO: If we fail here, we just need to reparse the old playlist file
3936 panicf("Failed to save playlist: %d", rc);
3937 goto error;
3938 }
3939
3940 /* User cancelled? */
3941 if (rc > 0)
3942 goto error;
3943
3944 rc = pl_save_update_control(playlist, tmpbuf, sizeof(tmpbuf));
3945 if (rc)
3946 {
3947 // TODO: If we fail here, then there are two possibilities depending on
3948 // whether we overwrote the old playlist file:
3949 //
3950 // - if it still exists, we could reparse it + old control file
3951 // - otherwise, we need to selectively reload the old control file
3952 // and somehow make use of the new playlist file
3953 //
3954 // The latter case poses other issues though, like what happens after we
3955 // resume, because replaying the old control file over the new playlist
3956 // won't work properly. We could simply choose to reset the control file,
3957 // seeing as by this point it only contains transient data (queued tracks).
3958 panicf("Failed to update control file: %d", rc);
3959 goto error;
3960 }
3961
3962error:
3963 playlist_write_unlock(playlist);
3964 dc_thread_start(playlist, true);
3965 cpu_boost(false);
3966 return rc;
3967}
diff --git a/apps/playlist.h b/apps/playlist.h
index 101a3c1207..0dc5148acd 100644
--- a/apps/playlist.h
+++ b/apps/playlist.h
@@ -177,8 +177,7 @@ size_t playlist_get_required_bufsz(struct playlist_info* playlist,
177 bool include_namebuf, int num_indices); 177 bool include_namebuf, int num_indices);
178int playlist_get_track_info(struct playlist_info* playlist, int index, 178int playlist_get_track_info(struct playlist_info* playlist, int index,
179 struct playlist_track_info* info); 179 struct playlist_track_info* info);
180int playlist_save(struct playlist_info* playlist, char *filename, 180int playlist_save(struct playlist_info* playlist, char *filename);
181 void* temp_buffer, size_t temp_buffer_size);
182int playlist_directory_tracksearch(const char* dirname, bool recurse, 181int playlist_directory_tracksearch(const char* dirname, bool recurse,
183 int (*callback)(char*, void*), 182 int (*callback)(char*, void*),
184 void* context); 183 void* context);