File Coverage

blib/lib/MP3/Daemon.pm
Criterion Covered Total %
statement 18 109 16.5
branch 0 38 0.0
condition 0 7 0.0
subroutine 6 16 37.5
pod 6 8 75.0
total 30 178 16.8


line stmt bran cond sub pod time code
1             package MP3::Daemon;
2              
3 1     1   8115 use strict;
  1         3  
  1         36  
4 1     1   5 use vars qw($VERSION);
  1         3  
  1         38  
5              
6             # controls mpg123
7 1     1   1552 use Audio::Play::MPG123;
  1         30357  
  1         71  
8              
9             # unixy stuff
10 1     1   8960 use POSIX qw(setsid);
  1         14669  
  1         7  
11              
12             # client/server communication
13 1     1   2430 use IO::Socket;
  1         29243  
  1         6  
14 1     1   2392 use IO::Select;
  1         3197  
  1         1401  
15              
16             $VERSION = '0.63';
17              
18             # constructor that does NOT daemonize itself
19             #_______________________________________
20             sub new {
21 0 0   0 1   my $class = shift; (@_ & 1) && die "Odd number of parameters\n";
  0            
22 0           my %opt = @_;
23 0   0       my $path = $opt{socket_path} || die("socket_path => REQUIRED!");
24 0           my $self = {
25             player => undef, # instance of Audio:Play:MPG123
26             server => undef, # instance of IO::Socket::UNIX
27             client => *STDOUT, # nice for debugging
28             socket_path => $path,
29             idle => undef, # coderef to execute while idle
30             at_exit => [ ] # array of coderefs to execute when done
31             };
32 0           bless ($self => $class);
33              
34             # clean-up handlers
35 0           foreach (@{$self->{at_exit}}) { $self->atExit($_); }
  0            
  0            
36              
37             # server socket
38 0 0         $self->{server} = IO::Socket::UNIX->new (
39             Type => SOCK_STREAM,
40             Local => $self->{socket_path},
41             Listen => SOMAXCONN,
42             ) or die($!);
43 0           chmod(0600, $self->{socket_path});
44              
45             # player
46 0           eval {
47 0   0       $self->{player} = Audio::Play::MPG123->new || die($!);
48             };
49 0 0         if ($@) {
50 0           unlink($self->{socket_path});
51 0           die($@);
52             }
53 0           return $self;
54             }
55              
56             # constructor that daemonizes itself
57             #_______________________________________
58             sub spawn {
59 0     0 1   my $class = shift;
60              
61 0 0         defined(my $pid = fork) or die "Can't fork: $!";
62 0 0         unless ($pid) {
63 0 0         chdir '/' or die "Can't chdir to /: $!";
64 0 0         open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
65 0 0         open STDOUT, '>/dev/null'
66             or die "Can't write to /dev/null: $!";
67 0 0         setsid or die "Can't start a new session: $!";
68 0 0         open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
69             } else {
70             # terrible hack to avoid race condition
71 0           select(undef, undef, undef, 0.25);
72 0           return $pid;
73             }
74              
75 0           my $self = $class->new(@_);
76 0           $self->main;
77             }
78              
79             # return a socket connected to the daemon
80             #_______________________________________
81             sub client {
82 0     0 1   my $class = shift;
83 0           my $socket_path = shift;
84 0 0         my $client = IO::Socket::UNIX->new (
85             Type => SOCK_STREAM,
86             Peer => $socket_path,
87             ) or die($!);
88 0           $client->autoflush(1);
89 0           return $client;
90             }
91              
92             # add clean-up handlers
93             #_______________________________________
94             sub atExit {
95 0     0 1   my $self = shift;
96 0           foreach (@_) {
97 0 0         unshift(@{$self->{at_exit}}, $_) if (ref eq "CODE");
  0            
98             }
99             }
100              
101             # destructor
102             #_______________________________________
103             sub DESTROY {
104 0     0     my $self = shift;
105 0           my $sub;
106 0           foreach $sub (@{$self->{at_exit}}) { $sub->($self) }
  0            
  0            
107             }
108              
109             # read from $fh until you get a blank line
110             #_______________________________________
111             sub readCommand {
112 0     0 0   my $self = shift;
113 0           my $fh = $self->{client};
114 0           my @line;
115              
116 0           while (<$fh>) {
117 0           chomp;
118 0 0         /^$/ && last;
119 0           push(@line, $_);
120             }
121 0           return @line;
122             }
123              
124             # delete the socket before exiting
125             #_______________________________________
126             sub setupSignals {
127 0     0 0   my $self = shift;
128             $SIG{INT} =
129             $SIG{HUP} =
130             $SIG{PIPE} =
131             $SIG{TERM} =
132             $SIG{__DIE__} = sub {
133 0     0     unlink($self->{socket_path});
134 0           exit 1;
135 0           };
136             }
137              
138             # repeatedly executed during the idle loop
139             #_______________________________________
140             sub idle {
141 0     0 1   my $self = shift;
142 0 0         if (@_) {
143 0           $self->{idle} = shift;
144             } else {
145 0 0 0       if (defined $self->{idle} && ref($self->{idle}) eq "CODE") {
146 0           $self->{idle}->($self);
147             }
148             }
149             }
150              
151             # the event loop
152             #_______________________________________
153             sub main {
154 0     0 1   my $self = shift;
155 0           my $server = $self->{server};
156 0           my $player = $self->{player};
157 0           my $mpg123 = $player->IN;
158 0           my $client;
159              
160 0           $self->setupSignals;
161              
162 0           my $s = IO::Select->new;
163 0           $s->add($server);
164 0           $s->add($mpg123);
165 0           my (@ready, $fh);
166              
167 0           while (@ready = $s->can_read) {
168              
169 0           foreach $fh (@ready) {
170              
171             # [( handle request from client )]
172              
173 0 0         if ($fh == $server) {
174 0           $client = $server->accept();
175 0           $self->{client} = $client;
176 0           $client->autoflush(1);
177 0           my @args = $self->readCommand;
178 0           my $do_this = "_" . shift(@args);
179              
180 0 0         if ($self->can($do_this)) {
181             # print STDERR "method => $do_this\n";
182             # print STDERR map { "| $_\n" } @args;
183 0           eval { $self->$do_this(@args) };
  0            
184 0 0         if ($@) { warn($@); }
  0            
185             } else {
186 0           print $client "$do_this is not supported.\n";
187             }
188 0           close($client);
189 0           $self->{client} = *STDOUT; # nice for debugging
190              
191             # idle ._,-'~`._,-'~`-._,-'~`-._,-'~`-.
192              
193             } else {
194 0           $player->poll(0);
195 0           $self->idle();
196 0           my $s = $player->state();
197 0 0         $self->next() if ($s == 0);
198 0           print "(" . $player->{frame}[2] . ") [$s] \n";
199             }
200             }
201             }
202             }
203              
204             1;
205              
206             __END__