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 )