Partial documentation.
[irspy-moved-to-github.git] / lib / ZOOM / Pod.pm
index b700857..9ddbd51 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: Pod.pm,v 1.1 2006-05-05 22:17:02 mike Exp $
+# $Id: Pod.pm,v 1.6 2006-05-10 16:44:57 mike Exp $
 
 package ZOOM::Pod;
 
@@ -7,39 +7,140 @@ use warnings;
 
 use ZOOM;
 
+BEGIN {
+    # Just register the name
+    ZOOM::Log::mask_str("pod");
+    ZOOM::Log::mask_str("pod_unhandled");
+}
+
+=head1 NAME
+
+ZOOM::Pod - Perl extension for handling pods of concurrent ZOOM connections
+
 =head1 SYNOPSIS
 
- $conn1 = new ZOOM::Connection("bagel.indexdata.com/gils");
- $conn2 = new ZOOM::Connection("z3950.loc.gov:7090/Voyager");
- $pod = new ZOOM::Pod($conn1, $conn2);
- $pod->callback(ZOOM::Event::RECV_SEARCH, \&show_result);
- $pod->search_pqf("mineral");
- $pod->wait();
+ use ZOOM::Pod;
+
+ $pod = new ZOOM::Pod("bagel.indexdata.com/gils",
+                      "bagel.indexdata.com/marc");
+ $pod->callback(ZOOM::Event::RECV_SEARCH, \&completed_search);
+ $pod->callback(ZOOM::Event::RECV_RECORD, \&got_record);
+ $pod->search_pqf("the");
+ $err = $pod->wait();
+ die "$pod->wait() failed with error $err" if $err;
+
+ sub completed_search {
+     ($conn, undef, $rs) = @_;
+     print $conn->option("host"), ": found ", $rs->size(), " records\n";
+     $rs->records(0, 1, 0); # Queues a request for the record
+     return 0;
+ }
 
- sub show_result {
-     ($conn, $rs, $event) = @_;
-     print "$conn: found ", $rs->size(), " records\n";
+ sub got_record {
+     ($conn, undef, $rs) = @_;
+     $rec = $rs->record(0);
+     print $conn->option("host"), ": got $rec = '", $rec->render(), "'\n";
+     return 0;
  }
 
+=head1 DESCRIPTION
+
+C<ZOOM:Pod> provides an API that simplifies asynchronous programming
+using ZOOM.  A pod is a collection of asynchronous connections that
+are run simultaneously to achieve broadcast searching and retrieval.
+When a pod is created, a set of connections (or target-strings to
+connect to) are specified.  Thereafter, they are treated as a unit,
+and methods for searching, option-setting, etc. that are invoked on
+the pod are delegated to each of its members.
+
+The key method on a pod is C<wait()>, which enters a loop accepting
+and dispatching events occurring on any of the connections in the pod.
+Unless interrupted,the loop runs until there are no more events left,
+i.e. no searches are outstanding and no requested records have still
+to be received.
+
+Event dispatching is done by means of callback functions, which can be
+registered for each event.  A registered callback is invoked whenever
+a corresponding event occurs.  A special callback can be nominated to
+handle errors.
+
+=head1 METHODS
+
+=head2 new()
+
+ $pod = new ZOOM::Pod($conn1, $conn2, $conn3);
+ $pod = new ZOOM::Pod("bagel.indexdata.com/gils",
+                      "bagel.indexdata.com/marc");
+
+
+Creates a new pod containing one or more connections.  Each connection
+may be specified either by an existing C<ZOOM::Connection> object,
+which I<must> be asynchronous; or by a ZOOM target string, in which
+case the pod module will make the connection object itself.
+
+Returns the new pod.
+
 =cut
 
 sub new {
     my $class = shift();
     my(@conn) = @_;
 
+    die "$class with no connections" if @conn == 0;
+    my @state; # Hashrefs with application state associated with connections
     foreach my $conn (@conn) {
        if (!ref $conn) {
            $conn = new ZOOM::Connection($conn, 0, async => 1);
+           # The $conn object is always made, even if no there's no
+           # server.  Such errors are caught later, by the _check()
+           # call in wait(). 
        }
+       push @state, {};
     }
 
     return bless {
        conn => \@conn,
+       state => \@state,
        rs => [],
        callback => {},
     }, $class;
 }
 
+=head2 option()
+
+ $oldElemSet = $pod->option("elementSetName");
+ $pod->option(elementSetName => "b");
+
+Sets a specified option in all the connections in a pod.  Returns the
+old value that the option had in first of the connections in the pod:
+be aware that this value was not necessarily shared by all the members
+of the pod ... but that is true often enough to be useful.
+
+=cut
+
+sub option {
+    my $this = shift();
+    my($key, $value) = @_;
+
+    my $old = $this->{conn}->[0]->option($key);
+    foreach my $conn (@{ $this->{conn} }) {
+       $conn->option($key, $value);
+    }
+
+    return $old;
+}
+
+=head2 callback()
+
+I<###>
+
+It is passed the connection that the event happened on, a state
+hash-reference associated with the connection, the connection's
+result-set and the event-type (so that a single function can handle
+events of multiple types, switching on the code where necessary).
+
+=cut
+
 sub callback {
     my $this = shift();
     my($event, $sub) = @_;
@@ -51,6 +152,17 @@ sub callback {
     return $old;
 }
 
+=head2 search_pqf()
+
+I<###>
+
+B<WARNING!>
+An important simplifying assumption is that each connection can only
+have one search active on it at a time - this allows the pod to
+maintain a one-to-one mapping between connections and result-sets.  
+
+=cut
+
 sub search_pqf {
     my $this = shift();
     my($pqf) = @_;
@@ -60,19 +172,40 @@ sub search_pqf {
     }
 }
 
+=head2 wait()
+
+I<###>
+
+=cut
+
 sub wait {
     my $this = shift();
     my $res = 0;
 
-    print "in wait\n";
     while ((my $i = ZOOM::event($this->{conn})) != 0) {
        my $conn = $this->{conn}->[$i-1];
        my $ev = $conn->last_event();
-       print("connection ", $i-1, ": ", ZOOM::event_str($ev), "\n");
+       my $evstr = ZOOM::event_str($ev);
+       ZOOM::Log::log("pod", "connection ", $i-1, ": $evstr");
+
+       eval {
+           $conn->_check();
+       }; if ($@) {
+           my $sub = $this->{callback}->{exception};
+           die $@ if !defined $sub;
+           $res = &$sub($conn, $this->{state}->[$i-1],
+                        $this->{rs}->[$i-1], $@);
+           last if $res != 0;
+           next;
+       }
+
        my $sub = $this->{callback}->{$ev};
        if (defined $sub) {
-           $res = &$sub($conn, $this->{rs}->[$i-1], $ev);
+           $res = &$sub($conn, $this->{state}->[$i-1],
+                        $this->{rs}->[$i-1], $ev);
            last if $res != 0;
+       } else {
+           ZOOM::Log::log("pod_unhandled", "unhandled event $ev ($evstr)");
        }
     }
 
@@ -80,4 +213,27 @@ sub wait {
 }
 
 
+=head1 SEE ALSO
+
+The underlying
+C<ZOOM>
+module (part of the
+C<Net::Z3950::ZOOM>
+distribution).
+
+=head1 AUTHOR
+
+Mike Taylor, E<lt>mike@indexdata.comE<gt>
+
+=head1 COPYRIGHT AND LICENCE
+
+Copyright (C) 2006 by Index Data.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.8.4 or,
+at your option, any later version of Perl 5 you may have available.
+
+=cut
+
+
 1;