]> git.draconx.ca Git - mpdhacks.git/commitdiff
mpdreload: Handle playlist load errors better.
authorNick Bowler <nbowler@draconx.ca>
Tue, 5 Jan 2021 03:24:24 +0000 (22:24 -0500)
committerNick Bowler <nbowler@draconx.ca>
Tue, 5 Jan 2021 03:32:33 +0000 (22:32 -0500)
When using the "load" command to load tracks from a stored playlist,
there is no guarantee that MPD will actually successfully load them.
This can occur, for example, if a playlist is created and then some
of the files in it are removed from the database.

Such failures are not reported directly by the protocol.  The "load"
command will succeed but you just get less tracks added to the play
queue.  This gets mpdreload confused as it no longer has an accurate
picture of the exact queue positions.

Solve this by querying the new queue length after tracks are loaded
from the playlist.  This allows mpdreload to determine the number of
failures and adjust the expected positions accordingly.

mpdreload.pl

index 66bb39d2ccffd48f5fd9f3f8557e2e0e28edd037..8cf010dc250a0f9f8a2f144be2a055bb0c253b04 100755 (executable)
@@ -26,6 +26,8 @@ use MPDHacks;
 
 my $sock;
 
+my $pl_current_length;
+
 # Returns a hash reference mapping filenames to an array reference listing
 # the queue IDs for that file in the current play queue.
 sub get_tracks_in_play_queue {
@@ -48,6 +50,8 @@ sub get_tracks_in_play_queue {
                }
        }
 
+       $pl_current_length = $pos+1;
+
        return (\%matches, \%idmap);
 }
 
@@ -126,46 +130,74 @@ MPD::run("tagtypes", "clear");
 my ($current, $idmap) = get_tracks_in_play_queue();
 my $target = get_playlist_files($ARGV[0]);
 
-my $end_position = (keys %$current);
-my $num_added = 0;
 my $add_start;
 
-sub load_tracks($) {
-       my ($seq) = @_;
+sub load_tracks($$) {
+       my ($seq, $dst) = @_;
+       my ($newlen, $count);
 
        my $start = $add_start // $seq;
+       my $add_position = $pl_current_length;
        my $end = $seq+1;
 
-       my $add_position = $end_position;
+       $dst //= $start;
+
        MPD::exec("load", $ARGV[0], "$start:$end");
-       $end_position += $end - $start;
-       MPD::exec("move", "$add_position:$end_position", "$start")
-               if ($add_position != $start);
+       MPD::exec("status");
+       MPD::exec("command_list_end");
+
+       while (<$sock>) {
+               last if (/^OK/);
+               die($_) if (/^ACK/);
+
+               if (/^(\w+): (.*)$/) {
+                       if ($1 eq "playlistlength") {
+                               $newlen = int($2);
+                       }
+               }
+       }
+
+       $count = $newlen - $pl_current_length;
+
+       MPD::exec("command_list_begin");
+       if ($newlen > $pl_current_length) {
+               MPD::exec("move", "$add_position:$newlen", $dst)
+                       if ($add_position != $dst);
+       }
 
+       $pl_current_length = $newlen;
        undef $add_start;
+
+       return $count;
 }
 
 MPD::exec("command_list_begin");
+my ($num_added, $num_failed) = (0, 0);
 for (my $i = 0; $i < @$target; $i++) {
        my $f = $target->[$i];
        my $id = shift @{ $current->{$f} };
 
-       load_tracks($i - 1) if (defined $id and defined $add_start);
+       if (defined $id and defined $add_start) {
+               my $n = $i - $add_start;
+               my $m = load_tracks($i-1, $add_start - $num_failed);
+
+               $num_added += $m;
+               $num_failed += $n - $m;
+       }
 
        if (defined $id) {
                # Try not to move tracks already in the right place.
-               MPD::exec("moveid", $id, $i)
-                       if ($i != $idmap->{$id} + $num_added);
+               MPD::exec("moveid", $id, $i - $num_failed)
+                       if ($i - $num_failed != $idmap->{$id} + $num_added);
        } else {
                $add_start //= $i;
-               $num_added++;
        }
 }
 
 # Now all unwanted tracks from the original playqueue have been moved to the
 # end and can be deleted all at once.
 
-my $pos = $add_start // @$target;
+my $pos = ($add_start // @$target) - $num_failed;
 MPD::exec("delete", $pos . ":") if map { @$_ } values %$current;
 MPD::exec("load", $ARGV[0], "$add_start:") if defined $add_start;
 MPD::run("command_list_end");