summaryrefslogtreecommitdiff
path: root/apps/playlist.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/playlist.c')
-rw-r--r--apps/playlist.c551
1 files changed, 289 insertions, 262 deletions
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}