]> git.ktnx.net Git - mpd-feeder.git/blobdiff - bin/mpd-feeder
key artists by AlbumArtists
[mpd-feeder.git] / bin / mpd-feeder
index ee17eda89eb191dc44694441e3f3c00c7b28a118..d553ea3e1d885980f1d851a11da7a45d4688112f 100755 (executable)
@@ -4,11 +4,12 @@ use v5.32;
 
 use Getopt::Long ();
 use Log::Any qw($log);
-use Log::Any::Adapter Stderr => log_level => 'trace';
+use Log::Any::Adapter Stderr => log_level => 'error';
 use Object::Pad;
 use Syntax::Keyword::Try;
 
 class Options {
+    use Log::Any qw($log);
     use Time::Duration qw(duration_exact);
     use Time::Duration::Parse qw(parse_duration);
     has $log_level           :reader = 'warn';
@@ -27,12 +28,14 @@ class Options {
         Getopt::Long::GetOptions(
             'log-level=s'               => \$log_level,
             'skip-db-update!'           => \$skip_db_update,
-            'tql|target-queue-length=n' => \$target_queue_length,
-            'mpd-host=s'                => \$mpd_host,
-            'mpd-port=s'                => \$mpd_port,
-            'db-path=s'                 => \$db_path,
-            'db-user=s'                 => \$db_user,
-            'min-album-interval=s'      => sub {
+            'tql|target-queue-length=n' => sub {
+                $target_queue_length = parse_integer(pop);
+            },
+            'mpd-host=s'           => \$mpd_host,
+            'mpd-port=s'           => \$mpd_port,
+            'db-path=s'            => \$db_path,
+            'db-user=s'            => \$db_user,
+            'min-album-interval=s' => sub {
                 $min_album_interval = parse_duration(pop);
             },
             'min-sing-interval=s' => sub {
@@ -54,6 +57,8 @@ class Options {
         $value = $converter->($value) if $converter;
 
         $$target_ref = $value;
+
+        $log->trace("Option $section.$option = $value");
     }
 
     method dump {
@@ -63,7 +68,6 @@ class Options {
         say "[mpd]";
         say "host = " . ( $mpd_host // '' );
         say "port = " . ( $mpd_port // '' );
-        say "target-queue-length = $target_queue_length";
         say "";
         say "[queue]";
         say "target-length = $target_queue_length";
@@ -77,7 +81,14 @@ class Options {
         say "password = " . ( $db_password // '' );
     }
 
+    sub parse_integer($input) {
+        die "Invalid integer value '$input'" unless $input =~ /^\+?\d{1,18}$/;
+        return $input + 0;
+    }
+
     method parse_config_file($path) {
+        $log->trace("Parsing configuration file $path");
+
         use Config::INI::Reader;
         my $ini = Config::INI::Reader->read_file($path);
 
@@ -87,7 +98,9 @@ class Options {
         handle_config_option( $ini => 'mpd-feeder' => log_level => \$log_level );
 
         handle_config_option(
-            $ini => queue => 'target-length' => \$target_queue_length );
+            $ini => queue => 'target-length' => \$target_queue_length,
+            \&parse_integer
+        );
         handle_config_option(
             $ini => queue => 'min-song-interval' => \$min_song_interval,
             \&parse_duration
@@ -110,6 +123,7 @@ class Options {
 }
 
 class Feeder {
+    has $cfg_file :reader;
     has $opt :reader;
     has $db;
     has $db_generation;
@@ -121,27 +135,31 @@ use constant DEFAULT_CONFIG_FILE => '/etc/mpd-feeder/mpd-feeder.conf';
 use DBD::Pg;
 use DBI;
 use Log::Any qw($log);
+use IO::Async::Signal;
 use Net::Async::MPD;
 
     ADJUST {
-        $opt = Options->new;
+        Getopt::Long::Configure('pass_through');
+        Getopt::Long::GetOptions('cfg|config=s' => \$cfg_file);
+        Getopt::Long::Configure('no_pass_through');
 
-        {
-            my $cfg_file;
-            Getopt::Long::Configure('pass_through');
-            Getopt::Long::GetOptions('cfg|config=s' => \$cfg_file);
-            Getopt::Long::Configure('no_pass_through');
+        $cfg_file //= DEFAULT_CONFIG_FILE if -e DEFAULT_CONFIG_FILE;
 
-            $cfg_file //= DEFAULT_CONFIG_FILE if -e DEFAULT_CONFIG_FILE;
+        $self->configure;
 
-            $opt->parse_config_file($cfg_file) if $cfg_file;
-        }
+        $db_needs_update = 0 if $opt->skip_db_update;
+    }
 
-        $opt->parse_command_line;
+    method configure {
+        my $new_opt = Options->new;
 
-        $db_needs_update = 0 if $opt->skip_db_update;
+        $new_opt->parse_config_file($cfg_file) if $cfg_file;
+
+        $new_opt->parse_command_line;
+
+        Log::Any::Adapter->set( Stderr => log_level => $new_opt->log_level );
 
-        Log::Any::Adapter->set( Stderr => log_level => $opt->log_level );
+        $opt = $new_opt;
     }
 
     method connect_mpd {
@@ -152,6 +170,32 @@ use Net::Async::MPD;
         $conn{port} = $opt->mpd_port if $opt->mpd_port;
 
         $mpd = Net::Async::MPD->new(%conn);
+
+        $mpd->loop->add(
+            IO::Async::Signal->new(
+                name       => 'HUP',
+                on_receipt => sub {
+                    $log->debug("SIGHUP received. Stopping loop");
+                    $mpd->loop->stop('reload');
+                },
+            )
+        );
+
+        $mpd->loop->add(
+            IO::Async::Signal->new(
+                name       => 'USR1',
+                on_receipt => sub {
+                    $log->debug("SIGUSR1 received. Dumping configuration to STDERR");
+                    my $old = select \*STDERR;
+                    try {
+                        $opt->dump;
+                    }
+                    finally {
+                        select $old;
+                    }
+                },
+            )
+        );
     }
 
     method connect_db {
@@ -255,7 +299,7 @@ SQL
             foreach my $entry (@$rows) {
                 next unless exists $entry->{file};
                 $self->db_store_song( $entry->{file},
-                    $entry->{Artist}, $entry->{Album} );
+                    $entry->{AlbumArtist}, $entry->{Album} );
                 $song_count++;
             }
 
@@ -355,7 +399,8 @@ SQL
                 sub {
                     my $present = scalar @{ $_[0] };
 
-                    $log->notice("Playlist contains $present songs");
+                    $log->notice( "Playlist contains $present songs. Wanted: "
+                            . $opt->target_queue_length );
                     if ( $present < $opt->target_queue_length ) {
                         $self->queue_songs(
                             $opt->target_queue_length - $present, $callback );
@@ -401,7 +446,6 @@ SQL
         $f->on_fail( sub { die @_ } );
         $f->on_done(
             sub {
-                warn $_ for @_;
                 $self->db_note_song_qeued($_) for @list;
                 $callback->(@_) if $callback;
             }
@@ -412,7 +456,6 @@ SQL
         $log->trace('declaring idle mode');
         $mpd->send('idle database playlist')->on_done(
             sub {
-                warn $_ for @_;
                 my $result = shift;
 
                 if ( $result->{changed} eq 'database' ) {
@@ -442,6 +485,25 @@ SQL
 
         $self->prepare_to_wait_idle;
     }
+
+    method stop {
+        undef $mpd;
+
+        if ($db) {
+            if ($db->{ActiveKids}) {
+                $log->warn("$db->{ActiveKids} active DB statements");
+                for my $st ( @{ $db->{ChildHandles} } ) {
+                    next unless $st->{Active};
+                    while(my($k,$v) = each %$st) {
+                        $log->debug("$k = ".($v//'<NULL>'));
+                    }
+                }
+            }
+
+            $db->disconnect;
+            undef $db;
+        }
+    }
 }
 
 my $feeder = Feeder->new();
@@ -505,6 +567,18 @@ if (@ARGV) {
     }
 }
 
-$feeder->queue_songs( undef, sub { $feeder->run } );
+for ( ;; ) {
+    $feeder->queue_songs( undef, sub { $feeder->run } );
+
+    $log->debug("Entering event loop. PID=$$");
+
+    my $result = $feeder->mpd->loop->run;
+    $log->trace( "Got loop result of " . ( $result // 'undef' ) );
 
-$feeder->mpd->loop->run;
+    if ('reload' eq $result) {
+        $log->notice("disconnecting");
+        $feeder->stop;
+
+        exec( "$0", '--config', $feeder->cfg_file, '--skip-db-update' );
+    }
+}