App-MPDJ
view release on metacpan or search on metacpan
lib/App/MPDJ.pm view on Meta::CPAN
package App::MPDJ;
use strict;
use warnings;
use 5.010;
our $VERSION = '1.09';
use Net::MPD;
use Proc::Daemon;
use Log::Dispatch;
use AppConfig;
sub new {
my ($class, @options) = @_;
my $self = bless {
last_call => 0,
config_errors => [],
@options
}, $class;
}
sub mpd { shift->{mpd} }
sub log { shift->{log} }
sub config { shift->{config} }
sub parse_options {
my ($self, @args) = @_;
$self->{config} = AppConfig->new({
ERROR => sub { push @{ $self->{config_errors} }, \@_; },
CASE => 1,
},
'conf|f=s' => {
VALIDATE => sub { -e shift }
},
'before|b=i' => { DEFAULT => 2 },
'after|a=i' => { DEFAULT => 2 },
'calls-path=s' => { DEFAULT => 'calls' },
'calls-freq=i' => { DEFAULT => 3600 },
'daemon|D!' => { DEFAULT => 1 },
'mpd=s' => { DEFAULT => 'localhost' },
'music-path=s' => { DEFAULT => 'music' },
'syslog|s=s' => { DEFAULT => '' },
'conlog|l=s' => { DEFAULT => '' },
'help|h' => { ACTION => \&help, },
'version|V' => { ACTION => \&version, });
$self->_getopt(@args); # to get --conf option, if any
my @configs =
$self->config->get('conf') || ('/etc/mpdj.conf', "$ENV{HOME}/.mpdjrc");
foreach my $config (@configs) {
if (-e $config) {
say "Loading config ($config)" if $self->config->get('conlog');
$self->config->file($config);
} else {
say "Config file skipped ($config)" if $self->config->get('conlog');
}
}
$self->_getopt(@args); # to override config file
}
sub _getopt {
my ($self, @args) = @_;
$self->config->getopt([@args]); # do not consume @args
if (@{ $self->{config_errors} }) {
foreach my $err (@{ $self->{config_errors} }) {
printf STDERR @$err;
print STDERR "\n";
}
$self->help;
}
}
sub connect {
my ($self) = @_;
$self->{mpd} = Net::MPD->connect($self->config->get('mpd'));
}
sub execute {
my ($self) = @_;
local @SIG{qw( INT TERM HUP )} = sub {
$self->log->notice('Exiting');
exit 0;
};
my @loggers;
push @loggers,
([ 'Screen', min_level => $self->config->get('conlog'), newline => 1 ])
if $self->config->get('conlog');
push @loggers,
([ 'Syslog', min_level => $self->config->get('syslog'), ident => 'mpdj' ])
if $self->config->get('syslog');
$self->{log} = Log::Dispatch->new(outputs => \@loggers);
if ($self->config->get('daemon')) {
$self->log->notice('Forking to background');
Proc::Daemon::Init;
}
$self->connect;
$self->configure;
$self->mpd->subscribe('mpdj');
$self->update_cache;
while (1) {
$self->log->debug('Waiting');
my @changes =
$self->mpd->idle(qw(database player playlist message options));
lib/App/MPDJ.pm view on Meta::CPAN
if ($count > 0) {
$self->log->info("Deleting $count old songs");
$self->mpd->delete("0:$count");
}
}
sub add_new_songs {
my ($self) = @_;
my $song = $self->mpd->song || 0;
my $count =
$self->config->get('after') + $song - $self->mpd->playlist_length + 1;
if ($count > 0) {
$self->log->info("Adding $count new songs");
$self->add_song for 1 .. $count;
}
}
sub add_song {
my ($self) = @_;
$self->add_random_item_from_category('music');
}
sub add_call {
my ($self) = @_;
$self->log->info('Injecting call');
$self->add_random_item_from_category('calls', 'immediate');
my $now = time;
$self->{last_call} = $now - $now % $self->config->get('calls-freq');
$self->log->info('Set last call to ' . $self->{last_call});
}
sub add_random_item_from_category {
my ($self, $category, $next) = @_;
my @items = @{ $self->{$category} };
my $index = int rand scalar @items;
my $item = $items[$index];
my $uri = $item->{uri};
my $song = $self->mpd->song || 0;
my $pos = $next ? $song + 1 : $self->mpd->playlist_length;
$self->log->info('Adding ' . $uri . ' at position ' . $pos);
$self->mpd->add_id($uri, $pos);
}
sub time_for_call {
my ($self) = @_;
return unless $self->config->get('calls-freq');
return time - $self->{last_call} > $self->config->get('calls-freq');
}
sub version {
say "mpdj (App::MPDJ) version $VERSION";
exit;
}
sub help {
print <<'HELP';
Usage: mpdj [options]
Options:
--mpd MPD connection string (password@host:port)
-s,--syslog Turns on syslog output (debug, info, notice, warn[ing], error, etc)
-l,--conlog Turns on console output (same choices as --syslog)
--no-daemon Turn off daemonizing
-b,--before Number of songs to keep in playlist before current song
-a,--after Number of songs to keep in playlist after current song
-c,--calls-freq Frequency to inject call signs in seconds
--calls-path Path to call sign files
--music-path Path to music files
-f,--conf Config file to use
-V,--version Show version information and exit
-h,--help Show this help and exit
HELP
exit;
}
sub database_changed {
my ($self) = @_;
$self->update_cache;
}
sub player_changed {
my ($self) = @_;
$self->add_call() if $self->time_for_call();
$self->add_new_songs();
$self->remove_old_songs();
}
sub playlist_changed {
my ($self) = @_;
$self->player_changed();
}
sub message_changed {
my $self = shift;
my @messages = $self->mpd->read_messages();
foreach my $message (@messages) {
my $function = 'handle_message_' . $message->{channel};
$self->$function($message->{message});
}
}
sub options_changed {
my $self = shift;
$self->log->notice('Resetting configuration');
( run in 1.478 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )