v0.9.1
[poe-component-client-mpd.git] / lib / POE / Component / Client / MPD.pm
blob898b89ffc02c662ede973bd432e43928b3ba8e62
2 # This file is part of POE::Component::Client::MPD.
3 # Copyright (c) 2007-2008 Jerome Quelin, all rights reserved.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the same terms as Perl itself.
10 package POE::Component::Client::MPD;
12 use 5.010;
13 use strict;
14 use warnings;
16 use Audio::MPD::Common::Stats;
17 use Audio::MPD::Common::Status;
18 use Carp;
19 use POE;
20 use POE::Component::Client::MPD::Commands;
21 use POE::Component::Client::MPD::Collection;
22 use POE::Component::Client::MPD::Connection;
23 use POE::Component::Client::MPD::Message;
24 use POE::Component::Client::MPD::Playlist;
26 use base qw{ Class::Accessor::Fast };
28 our $VERSION = '0.9.1';
31 #--
32 # CLASS METHODS
35 # -- public methods
38 # my $id = POE::Component::Client::MPD->spawn( \%params )
40 # This method will create a POE session responsible for communicating
41 # with mpd. It will return the poe id of the session newly created.
43 # You can tune the pococm by passing some arguments as a hash reference,
44 # where the hash keys are:
45 # - host: the hostname of the mpd server. If none given, defaults to
46 # MPD_HOST env var. If this var isn't set, defaults to localhost.
47 # - port: the port of the mpd server. If none given, defaults to
48 # MPD_PORT env var. If this var isn't set, defaults to 6600.
49 # - password: the password to sent to mpd to authenticate the client.
50 # If none given, defaults to C<MPD_PASSWORD> env var. If this var
51 # isn't set, defaults to empty string.
52 # - alias: an optional string to alias the newly created POE session.
53 # - status_msgs_to: session to whom to send connection status.
54 # optional (although recommended), no default.
56 sub spawn {
57 my ($type, $args) = @_;
59 my $session = POE::Session->create(
60 args => [ $args ],
61 inline_states => {
62 # private events
63 '_start' => \&_onpriv_start,
64 # protected events
65 'mpd_connect_error_fatal' => \&_onprot_mpd_connect_error,
66 'mpd_connect_error_retriable' => \&_onprot_mpd_connect_error,
67 'mpd_connected' => \&_onprot_mpd_connected,
68 'mpd_disconnected' => \&_onprot_mpd_disconnected,
69 'mpd_data' => \&_onprot_mpd_data,
70 'mpd_error' => \&_onprot_mpd_error,
71 # public events
72 'disconnect' => \&_onpub_disconnect,
73 '_default' => \&POE::Component::Client::MPD::_onpub_default,
76 return $session->ID;
80 #--
81 # METHODS
84 # -- private methods
86 sub _dispatch {
87 my ($self, $k, $h, $event, $msg) = @_;
89 # dispatch the event.
90 given ($event) {
91 # playlist commands
92 when (/^pl\.(.*)$/) {
93 my $meth = "_do_$1";
94 $h->{playlist}->$meth($k, $h, $msg);
97 # collection commands
98 when (/^coll\.(.*)$/) {
99 my $meth = "_do_$1";
100 $h->{collection}->$meth($k, $h, $msg);
103 # basic commands
104 default {
105 my $meth = "_do_$event";
106 $h->{commands}->$meth($k, $h, $msg);
113 # EVENTS HANDLERS
116 # -- public events.
119 # catch-all handler for pococm events that drive mpd.
121 sub _onpub_default {
122 my ($k, $h, $event, $params) = @_[KERNEL, HEAP, ARG0, ARG1];
124 # check if event is handled.
125 my @events_commands = qw{
126 version kill updatedb urlhandlers
127 volume output_enable output_disable
128 stats status current song songid
129 repeat fade random
130 play playid pause stop next prev seek seekid
132 my @events_playlist = qw{
133 pl.as_items pl.items_changed_since
134 pl.add pl.delete pl.deleteid pl.clear pl.crop
135 pl.shuffle pl.swap pl.swapid pl.move pl.moveid
136 pl.load pl.save pl.rm
138 my @events_collection = qw{
139 coll.all_items coll.all_items_simple coll.items_in_dir
140 coll.all_albums coll.all_artists coll.all_titles coll.all_files
141 coll.song coll.songs_with_filename_partial
142 coll.albums_by_artist coll.songs_by_artist coll.songs_by_artist_partial
143 coll.songs_from_album coll.songs_from_album_partial
144 coll.songs_with_title coll.songs_with_title_partial
146 my @ok_events = ( @events_commands, @events_playlist, @events_collection );
147 return unless $event ~~ [ @ok_events ];
149 # create the message that will hold
150 my $msg = POE::Component::Client::MPD::Message->new( {
151 _from => $_[SENDER]->ID,
152 request => $event, # /!\ $_[STATE] eq 'default'
153 params => $params,
154 #_commands => <to be set by handler>
155 #_cooking => <to be set by handler>
156 #_transform => <to be set by handler>
157 #_post => <to be set by handler>
158 } );
160 # dispatch the event so it is handled by the correct object/method.
161 $h->{mpd}->_dispatch($k, $h, $event, $msg);
166 # event: disconnect()
168 # Request the pococm to be shutdown. Leave mpd running.
170 sub _onpub_disconnect {
171 my ($k,$h) = @_[KERNEL, HEAP];
172 $k->alias_remove( $h->{alias} ) if defined $h->{alias}; # refcount--
173 $k->post( $h->{socket}, 'disconnect' ); # pococm-conn
177 # -- protected events.
180 # event: mpd_connect_error_retriable( $reason )
181 # event: mpd_connect_error_fatal( $reason )
183 # Called when pococm-conn could not connect to a mpd server. It can be
184 # either retriable, or fatal. In bth case, we just need to forward the
185 # error to our peer session.
187 sub _onprot_mpd_connect_error {
188 my ($k, $h, $reason) = @_[KERNEL, HEAP, ARG0];
190 my $peer = $h->{status_msgs_to};
191 return unless defined $peer;
192 $k->post($peer, 'mpd_connect_error_fatal', $reason);
197 # event: mpd_connected( $version )
199 # Called when pococm-conn made sure we're talking to a mpd server.
201 sub _onprot_mpd_connected {
202 my ($k, $h, $version) = @_[KERNEL, HEAP, ARG0];
203 $h->{version} = $version;
205 my $peer = $h->{status_msgs_to};
206 return unless defined $peer;
207 $k->post($peer, 'mpd_connected');
208 # FIXME: send password information to mpd
209 # FIXME: send status information to peer
215 # event: mpd_disconnected()
217 # Called when pococm-conn got disconnected by mpd.
219 sub _onprot_mpd_disconnected {
220 my ($k, $h, $version) = @_[KERNEL, HEAP, ARG0];
226 # Event: mpd_data( $msg )
228 # Received when mpd finished to send back some data.
230 sub _onprot_mpd_data {
231 my ($k, $h, $msg) = @_[KERNEL, HEAP, ARG0];
233 # transform data if needed.
234 given ($msg->_transform) {
235 when ($AS_SCALAR) {
236 my $data = $msg->_data->[0];
237 $msg->_data($data);
239 when ($AS_STATS) {
240 my %stats = @{ $msg->_data };
241 my $stats = Audio::MPD::Common::Stats->new( \%stats );
242 $msg->_data($stats);
244 when ($AS_STATUS) {
245 my %status = @{ $msg->_data };
246 my $status = Audio::MPD::Common::Status->new( \%status );
247 $msg->_data($status);
252 # check for post-callback.
253 if ( defined $msg->_post ) {
254 my $event = $msg->_post; # save postback.
255 $msg->_post( undef ); # remove postback.
256 $h->{mpd}->_dispatch($k, $h, $event, $msg);
257 return;
260 # send result.
261 $k->post($msg->_from, 'mpd_result', $msg, $msg->_data);
266 # Event: mpd_error( $msg, $errstr )
268 # Received when mpd didn't understood a command.
270 sub _onprot_mpd_error {
271 my ($k, $msg, $errstr) = @_[KERNEL, ARG0, ARG1];
273 $msg->status(0); # failure
274 $k->post( $msg->_from, 'mpd_error', $msg, $errstr );
279 # -- private events
282 # Event: _start( \%params )
284 # Called when the poe session gets initialized. Receive a reference
285 # to %params, same as spawn() received.
287 sub _onpriv_start {
288 my ($k, $h, $args) = @_[KERNEL, HEAP, ARG0];
290 # set up connection details.
291 $args = {} unless defined $args;
292 my %params = (
293 host => $ENV{MPD_HOST} // 'localhost',
294 port => $ENV{MPD_PORT} // '6600',
295 password => $ENV{MPD_PASSWORD} // '',
296 %$args, # overwrite previous defaults
297 id => $_[SESSION]->ID, # required for connection
300 # set an alias (for easier communication) if requested.
301 $h->{alias} = delete $params{alias};
302 $k->alias_set($h->{alias}) if defined $h->{alias};
304 # store args for ourself.
305 $h->{status_msgs_to} = $args->{status_msgs_to};
306 $h->{socket} = POE::Component::Client::MPD::Connection->spawn(\%params);
308 # create objects to treat dispatched events.
309 $h->{mpd} = POE::Component::Client::MPD->new;
310 $h->{commands} = POE::Component::Client::MPD::Commands->new;
311 $h->{playlist} = POE::Component::Client::MPD::Playlist->new;
312 $h->{collection} = POE::Component::Client::MPD::Collection->new;
317 =begin FIXME
320 # event: _send( $msg )
322 # Event received to request message sending over tcp to mpd server.
323 # $msg is a pococm-message partially filled.
325 sub _onpriv_send {
326 my ($k, $h, $msg) = @_[KERNEL, HEAP, ARG0];
327 if ( defined $msg->_pre_event ) {
328 $k->yield( $msg->_pre_event ); # fire wanted pre-event
329 push @{ $h->{pre_messages} }, $msg; # store message
330 return;
332 $k->post( $_[HEAP]->{_socket}, 'send', $msg );
335 =end FIXME
337 =cut
342 __END__
345 =head1 NAME
347 POE::Component::Client::MPD - a full-blown mpd client library
351 =head1 SYNOPSIS
353 use POE qw{ Component::Client::MPD };
354 POE::Component::Client::MPD->spawn( {
355 host => 'localhost',
356 port => 6600,
357 password => 's3kr3t', # mpd password
358 alias => 'mpd', # poe alias
359 } );
361 # ... later on ...
362 $_[KERNEL]->post( 'mpd', 'next' );
366 =head1 DESCRIPTION
368 POCOCM gives a clear message-passing interface (sitting on top of POE)
369 for talking to and controlling MPD (Music Player Daemon) servers. A
370 connection to the MPD server is established as soon as a new POCOCM
371 object is created.
373 Commands are then sent to the server as messages are passed.
377 =head1 PUBLIC PACKAGE METHODS
380 =head2 my $id = POCOCM->spawn( \%params )
382 This method will create a POE session responsible for communicating with mpd.
383 It will return the poe id of the session newly created.
385 You can tune the pococm by passing some arguments as a hash reference, where
386 the hash keys are:
389 =over 4
391 =item * host
393 The hostname of the mpd server. If none given, defaults to C<MPD_HOST>
394 environment variable. If this var isn't set, defaults to C<localhost>.
397 =item * port
399 The port of the mpd server. If none given, defaults to C<MPD_PORT>
400 environment variable. If this var isn't set, defaults to C<6600>.
403 =item * password
405 The password to sent to mpd to authenticate the client. If none given, defaults
406 to C<MPD_PASSWORD> environment variable. If this var isn't set, defaults to C<>.
409 =item * alias
411 An optional string to alias the newly created POE session.
414 =item * status_msgs_to
416 A session (name or id) to whom to send connection status to. Optional,
417 although recommended. No default.
420 =back
424 =head1 PUBLIC EVENTS ACCEPTED
426 POCOCM accepts two types of events: some are used to drive the mpd
427 server, others will change the pococm status.
430 =head2 MPD-related events
432 The goal of a POCOCM session is to drive a remote MPD server. This can
433 be achieved by a lot of events. Due to their sheer number, they have
434 been regrouped logically in modules.
436 However, note that to use those events, you need to send them to the
437 POCOCM session that you created with C<spawn()> (see above). Indeed, the
438 logical split is only internal: you are to use the same peer.
441 For a list of public events that update and/or query MPD, see embedded
442 pod in:
444 =over 4
446 =item *
448 C<POCOCM::Commands> for general commands
451 =item *
453 C<POCOCM::Playlist> for playlist-related commands. Those events begin
454 with C<pl.>.
457 =item *
459 C<POCOCM::Collection> for collection-related commands. Those events
460 begin with C<coll.>.
463 =back
467 =head2 POCOCM-related events
469 Those events allow to drive the POCOCM session.
472 =over 4
474 =item * disconnect()
476 Request the POCOCM to be shutdown. Leave mpd running. Generally sent
477 when one wants to exit her program.
480 =back
484 =head1 PUBLIC EVENTS FIRED
486 A POCOCM session will fire events, either to answer an incoming event,
487 or to inform about some changes regarding the remote MPD server.
490 =head2 Answer events
492 For each incoming event received by the POCOCM session, it will fire
493 back one of the following answers:
496 =over 4
498 =item * mpd_result( $msg, $answer )
500 Indicates a success. C<$msg> is a C<POCOCM::Message> object with the
501 original request, to identify the issued command (see C<POCOCM::Message>
502 pod for more information). Its C<status()> attribute is true, further
503 confirming success.
505 C<$answer> is what has been answered by the MPD server. Depending on the
506 command, it can be either:
508 =over 4
510 =item * C<undef>: commands C<play>, etc.
512 =item * an C<Audio::MPD::Common::Stats> object: command C<stats>
514 =item * an C<Audio::MPD::Common::Status> object: command C<status>
516 =item * an C<Audio::MPD::Common::Item> object: commands C<song>, etc.
518 =item * an array reference: commands C<coll.files>, etc.
520 =item * etc.
522 =back
524 Refer to the documentation of each event to know what type of answer you
525 can expect.
528 =item * mpd_error( $msg, $errstr )
530 Indicates a failure. C<$msg> is a C<POCOCM::Message> object with the
531 original request, to identify the issued command (see C<POCOCM::Message>
532 pod for more information). Its C<status()> attribute is false, further
533 confirming failure.
535 C<$errstr> is what the error message as returned been answered by the
536 MPD server.
539 =back
543 =head2 Auto-generated events
545 To be written.
548 =head1 BUGS
550 Please report any bugs or feature requests to C<bug-poe-component-client-mpd at
551 rt.cpan.org>, or through the web interface at
552 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=POE-Component-Client-MPD>.
553 I will be notified, and then you'll automatically be notified of progress on
554 your bug as I make changes.
558 =head1 SEE ALSO
560 You can find more information on the mpd project on its homepage at
561 L<http://www.musicpd.org>, or its wiki L<http://mpd.wikia.com>.
563 C<POE::Component::Client::MPD development> takes place on C<< <audio-mpd
564 at googlegroups.com> >>: feel free to join us. (use
565 L<http://groups.google.com/group/audio-mpd> to sign in). Our git
566 repository is located at
567 L<git://repo.or.cz/poe-component-client-mpd.git>, and can be browsed at
568 L<http://repo.or.cz/w/poe-component-client-mpd.git>.
571 You can also look for information on this module at:
573 =over 4
575 =item * AnnoCPAN: Annotated CPAN documentation
577 L<http://annocpan.org/dist/POE-Component-Client-MPD>
579 =item * CPAN Ratings
581 L<http://cpanratings.perl.org/d/POE-Component-Client-MPD>
583 =item * Open bugs
585 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=POE-Component-Client-MPD>
587 =back
591 =head1 AUTHOR
593 Jerome Quelin, C<< <jquelin@cpan.org> >>
597 =head1 COPYRIGHT & LICENSE
599 Copyright (c) 2007-2008 Jerome Quelin, all rights reserved.
601 This program is free software; you can redistribute it and/or modify
602 it under the same terms as Perl itself.
604 =cut