ZOOM::Exception class now carries a diagnostic-set: it can be passed
[ZOOM-Perl-moved-to-github.git] / lib / ZOOM.pm
1 # $Id: ZOOM.pm,v 1.19 2005-11-16 16:48:11 mike Exp $
2
3 use strict;
4 use warnings;
5 use Net::Z3950::ZOOM;
6
7
8 package ZOOM;
9
10
11 # Member naming convention: hash-element names which begin with an
12 # underscore represent underlying ZOOM-C object descriptors; those
13 # which lack them represent Perl's ZOOM objects.  (The same convention
14 # is used in naming local variables where appropriate.)
15 #
16 # So, for example, the ZOOM::Connection class has an {_conn} element,
17 # which is a pointer to the ZOOM-C Connection object; but the
18 # ZOOM::ResultSet class has a {conn} element, which is a reference to
19 # the Perl-level Connection object by which it was created.  (It may
20 # be that we find we have no need for these references, but for now
21 # they are retained.)
22 #
23 # To get at the underlying ZOOM-C connection object of a result-set
24 # (if you ever needed to do such a thing, which you probably don't)
25 # you'd use $rs->{conn}->_conn().
26
27 # ----------------------------------------------------------------------------
28
29 # The "Error" package contains constants returned as error-codes.
30 package ZOOM::Error;
31 sub NONE { Net::Z3950::ZOOM::ERROR_NONE }
32 sub CONNECT { Net::Z3950::ZOOM::ERROR_CONNECT }
33 sub MEMORY { Net::Z3950::ZOOM::ERROR_MEMORY }
34 sub ENCODE { Net::Z3950::ZOOM::ERROR_ENCODE }
35 sub DECODE { Net::Z3950::ZOOM::ERROR_DECODE }
36 sub CONNECTION_LOST { Net::Z3950::ZOOM::ERROR_CONNECTION_LOST }
37 sub INIT { Net::Z3950::ZOOM::ERROR_INIT }
38 sub INTERNAL { Net::Z3950::ZOOM::ERROR_INTERNAL }
39 sub TIMEOUT { Net::Z3950::ZOOM::ERROR_TIMEOUT }
40 sub UNSUPPORTED_PROTOCOL { Net::Z3950::ZOOM::ERROR_UNSUPPORTED_PROTOCOL }
41 sub UNSUPPORTED_QUERY { Net::Z3950::ZOOM::ERROR_UNSUPPORTED_QUERY }
42 sub INVALID_QUERY { Net::Z3950::ZOOM::ERROR_INVALID_QUERY }
43 # The following are added specifically for this OO interface
44 sub CREATE_QUERY { 20001 }
45 sub QUERY_CQL { 20002 }
46 sub QUERY_PQF { 20003 }
47 sub SORTBY { 20004 }
48 sub CLONE { 20005 }
49 sub PACKAGE { 20006 }
50
51 # The "Event" package contains constants returned by last_event()
52 package ZOOM::Event;
53 sub NONE { Net::Z3950::ZOOM::EVENT_NONE }
54 sub CONNECT { Net::Z3950::ZOOM::EVENT_CONNECT }
55 sub SEND_DATA { Net::Z3950::ZOOM::EVENT_SEND_DATA }
56 sub RECV_DATA { Net::Z3950::ZOOM::EVENT_RECV_DATA }
57 sub TIMEOUT { Net::Z3950::ZOOM::EVENT_TIMEOUT }
58 sub UNKNOWN { Net::Z3950::ZOOM::EVENT_UNKNOWN }
59 sub SEND_APDU { Net::Z3950::ZOOM::EVENT_SEND_APDU }
60 sub RECV_APDU { Net::Z3950::ZOOM::EVENT_RECV_APDU }
61 sub RECV_RECORD { Net::Z3950::ZOOM::EVENT_RECV_RECORD }
62 sub RECV_SEARCH { Net::Z3950::ZOOM::EVENT_RECV_SEARCH }
63
64 # ----------------------------------------------------------------------------
65
66 package ZOOM;
67
68 sub diag_str {
69     my($code) = @_;
70
71     # Special cases for error specific to the OO layer
72     if ($code == ZOOM::Error::CREATE_QUERY) {
73         return "can't create query object";
74     } elsif ($code == ZOOM::Error::QUERY_CQL) {
75         return "can't set CQL query";
76     } elsif ($code == ZOOM::Error::QUERY_PQF) {
77         return "can't set prefix query";
78     } elsif ($code == ZOOM::Error::SORTBY) {
79         return "can't set sort-specification";
80     } elsif ($code == ZOOM::Error::CLONE) {
81         return "can't clone record";
82     } elsif ($code == ZOOM::Error::PACKAGE) {
83         return "can't create package";
84     }
85
86     return Net::Z3950::ZOOM::diag_str($code);
87 }
88
89 sub _oops {
90     my($code, $addinfo, $diagset) = @_;
91
92     die new ZOOM::Exception($code, diag_str($code), $addinfo, $diagset);
93 }
94
95 # ----------------------------------------------------------------------------
96
97 package ZOOM::Exception;
98
99 sub new {
100     my $class = shift();
101     my($code, $message, $addinfo, $diagset) = @_;
102
103     return bless {
104         code => $code,
105         message => $message,
106         addinfo => $addinfo,
107         diagset => $diagset || "ZOOM",
108     }, $class;
109 }
110
111 sub code {
112     my $this = shift();
113     return $this->{code};
114 }
115
116 sub message {
117     my $this = shift();
118     return $this->{message};
119 }
120
121 sub addinfo {
122     my $this = shift();
123     return $this->{addinfo};
124 }
125
126 sub diagset {
127     my $this = shift();
128     return $this->{diagset};
129 }
130
131 sub render {
132     my $this = shift();
133     my $res = "ZOOM error " . $this->code() . ' "' . $this->message() . '"';
134     $res .= ' (addinfo: "' . $this->addinfo() . '")' if $this->addinfo();
135     return $res;
136 }
137
138 # This means that untrapped exceptions render nicely.
139 use overload '""' => \&render;
140
141 # ----------------------------------------------------------------------------
142
143 package ZOOM::Options;
144
145 sub new {
146     my $class = shift();
147     my($p1, $p2) = @_;
148
149     my $opts;
150     if (@_ == 0) {
151         $opts = Net::Z3950::ZOOM::options_create();
152     } elsif (@_ == 1) {
153         $opts = Net::Z3950::ZOOM::options_create_with_parent($p1->_opts());
154     } elsif (@_ == 2) {
155         $opts = Net::Z3950::ZOOM::options_create_with_parent2($p1->_opts(),
156                                                               $p2->_opts());
157     } else {
158         die "can't make $class object with more than 2 parents";
159     }
160
161     return bless {
162         _opts => $opts,
163     }, $class;
164 }
165
166 # PRIVATE to this class and ZOOM::Connection::create() and
167 # ZOOM::Connection::package()
168 #
169 sub _opts {
170     my $this = shift();
171
172     my $_opts = $this->{_opts};
173     die "{_opts} undefined: has this Options block been destroy()ed?"
174         if !defined $_opts;
175
176     return $_opts;
177 }
178
179 sub option {
180     my $this = shift();
181     my($key, $value) = @_;
182
183     my $oldval = Net::Z3950::ZOOM::options_get($this->_opts(), $key);
184     Net::Z3950::ZOOM::options_set($this->_opts(), $key, $value)
185         if defined $value;
186
187     return $oldval;
188 }
189
190 sub option_binary {
191     my $this = shift();
192     my($key, $value) = @_;
193
194     my $dummylen = 0;
195     my $oldval = Net::Z3950::ZOOM::options_getl($this->_opts(),
196                                                 $key, $dummylen);
197     Net::Z3950::ZOOM::options_setl($this->_opts(), $key,
198                                    $value, length($value))
199         if defined $value;
200
201     return $oldval;
202 }
203
204 # This is a bit stupid, since the scalar values that Perl returns from
205 # option() can be used as a boolean; but it's just possible that some
206 # applications will rely on ZOOM_options_get_bool()'s idiosyncratic
207 # interpretation of what constitutes truth.
208 #
209 sub bool {
210     my $this = shift();
211     my($key, $default) = @_;
212
213     return Net::Z3950::ZOOM::options_get_bool($this->_opts(), $key, $default);
214 }
215
216 # .. and the next two are even more stupid
217 sub int {
218     my $this = shift();
219     my($key, $default) = @_;
220
221     return Net::Z3950::ZOOM::options_get_int($this->_opts(), $key, $default);
222 }
223
224 sub set_int {
225     my $this = shift();
226     my($key, $value) = @_;
227
228     Net::Z3950::ZOOM::options_set_int($this->_opts(), $key, $value);
229 }
230
231 #   ### Feel guilty.  Feel very, very guilty.  I've not been able to
232 #       get the callback memory-management right in "ZOOM.xs", with
233 #       the result that the values of $function and $udata passed into
234 #       this function, which are on the stack, have sometimes been
235 #       freed by the time they're used by __ZOOM_option_callback(),
236 #       with hilarious results.  To avoid this, I copy the values into
237 #       module-scoped globals, and pass _those_ into the extension
238 #       function.  To avoid overwriting those globals by subsequent
239 #       calls, I keep all the old ones, pushed onto the @_function and
240 #       @_udata arrays, which means that THIS FUNCTION LEAKS MEMORY
241 #       LIKE IT'S GOING OUT OF FASHION.  Not nice.  One day, I should
242 #       fix this, but for now there's more important fish to fry.
243 #
244 my(@_function, @_udata);
245 sub set_callback {
246     my $o1 = shift();
247     my($function, $udata) = @_;
248
249     push @_function, $function;
250     push @_udata, $udata;
251     Net::Z3950::ZOOM::options_set_callback($o1->_opts(),
252                                            $_function[-1], $_udata[-1]);
253 }
254
255 sub destroy {
256     my $this = shift();
257
258     Net::Z3950::ZOOM::options_destroy($this->_opts());
259     $this->{_opts} = undef;
260 }
261
262
263 # ----------------------------------------------------------------------------
264
265 package ZOOM::Connection;
266
267 sub new {
268     my $class = shift();
269     my($host, $port) = @_;
270
271     my $_conn = Net::Z3950::ZOOM::connection_new($host, $port || 0);
272     my $conn = bless {
273         host => $host,
274         port => $port,
275         _conn => $_conn,
276     };
277     $conn->_check();
278     return $conn;
279 }
280
281 # PRIVATE to this class
282 sub _conn {
283     my $this = shift();
284
285     my $_conn = $this->{_conn};
286     die "{_conn} undefined: has this Connection been destroy()ed?"
287         if !defined $_conn;
288
289     return $_conn;
290 }
291
292 sub _check {
293     my $this = shift();
294
295     my($errcode, $errmsg, $addinfo, $diagset) = (undef, "x", "x", "x");
296     $errcode = Net::Z3950::ZOOM::connection_error_x($this->_conn(), $errmsg,
297                                                     $addinfo, $diagset);
298     die new ZOOM::Exception($errcode, $errmsg, $addinfo, $diagset)
299         if $errcode;
300 }
301
302 sub create {
303     my $class = shift();
304     my($options) = @_;
305
306     my $_conn = Net::Z3950::ZOOM::connection_create($options->_opts());
307     return bless {
308         host => undef,
309         port => undef,
310         _conn => $_conn,
311     };
312 }
313
314 sub error_x {
315     my $this = shift();
316
317     my($errcode, $errmsg, $addinfo, $diagset) = (undef, "dummy", "dummy", "d");
318     $errcode = Net::Z3950::ZOOM::connection_error_x($this->_conn(), $errmsg,
319                                                     $addinfo, $diagset);
320     return ($errcode, $errmsg, $addinfo, $diagset);
321 }
322
323 sub errcode {
324     my $this = shift();
325     return Net::Z3950::ZOOM::connection_errcode($this->_conn());
326 }
327
328 sub errmsg {
329     my $this = shift();
330     return Net::Z3950::ZOOM::connection_errmsg($this->_conn());
331 }
332
333 sub addinfo {
334     my $this = shift();
335     return Net::Z3950::ZOOM::connection_addinfo($this->_conn());
336 }
337
338 sub diagset {
339     my $this = shift();
340     return Net::Z3950::ZOOM::connection_diagset($this->_conn());
341 }
342
343 sub connect {
344     my $this = shift();
345     my($host, $port) = @_;
346
347     Net::Z3950::ZOOM::connection_connect($this->_conn(), $host, $port);
348     $this->_check();
349     # No return value
350 }
351
352 sub option {
353     my $this = shift();
354     my($key, $value) = @_;
355
356     my $oldval = Net::Z3950::ZOOM::connection_option_get($this->_conn(), $key);
357     Net::Z3950::ZOOM::connection_option_set($this->_conn(), $key, $value)
358         if defined $value;
359
360     return $oldval;
361 }
362
363 sub option_binary {
364     my $this = shift();
365     my($key, $value) = @_;
366
367     my $dummylen = 0;
368     my $oldval = Net::Z3950::ZOOM::connection_option_getl($this->_conn(),
369                                                           $key, $dummylen);
370     Net::Z3950::ZOOM::connection_option_setl($this->_conn(), $key,
371                                              $value, length($value))
372         if defined $value;
373
374     return $oldval;
375 }
376
377 sub search {
378     my $this = shift();
379     my($query) = @_;
380
381     my $_rs = Net::Z3950::ZOOM::connection_search($this->_conn(),
382                                                   $query->_query());
383     $this->_check();
384     return _new ZOOM::ResultSet($this, $query, $_rs);
385 }
386
387 sub search_pqf {
388     my $this = shift();
389     my($pqf) = @_;
390
391     my $_rs = Net::Z3950::ZOOM::connection_search_pqf($this->_conn(), $pqf);
392     $this->_check();
393     return _new ZOOM::ResultSet($this, $pqf, $_rs);
394 }
395
396 sub scan {
397     my $this = shift();
398     my($startterm) = @_;
399
400     my $_ss = Net::Z3950::ZOOM::connection_scan($this->_conn(), $startterm);
401     $this->_check();
402     return _new ZOOM::ScanSet($this, $startterm, $_ss);
403 }
404
405 sub package {
406     my $this = shift();
407     my($options) = @_;
408
409     my $_o = defined $options ? $options->_opts() :
410         Net::Z3950::ZOOM::options_create();
411     my $_p = Net::Z3950::ZOOM::connection_package($this->_conn(), $_o)
412         or ZOOM::_oops(ZOOM::Error::PACKAGE);
413
414     return _new ZOOM::Package($this, $options, $_p);
415 }
416
417 sub destroy {
418     my $this = shift();
419
420     Net::Z3950::ZOOM::connection_destroy($this->_conn());
421     $this->{_conn} = undef;
422 }
423
424
425 # ----------------------------------------------------------------------------
426
427 package ZOOM::Query;
428
429 sub new {
430     my $class = shift();
431     die "You can't create $class objects: it's a virtual base class";
432 }
433
434 # PRIVATE to this class and ZOOM::Connection::search()
435 sub _query {
436     my $this = shift();
437
438     my $_query = $this->{_query};
439     die "{_query} undefined: has this Query been destroy()ed?"
440         if !defined $_query;
441
442     return $_query;
443 }
444
445 sub sortby {
446     my $this = shift();
447     my($sortby) = @_;
448
449     Net::Z3950::ZOOM::query_sortby($this->_query(), $sortby) == 0
450         or ZOOM::_oops(ZOOM::Error::SORTBY, $sortby);
451 }
452
453 sub destroy {
454     my $this = shift();
455
456     Net::Z3950::ZOOM::query_destroy($this->_query());
457     $this->{_query} = undef;
458 }
459
460
461 package ZOOM::Query::CQL;
462 our @ISA = qw(ZOOM::Query);
463
464 sub new {
465     my $class = shift();
466     my($string) = @_;
467
468     my $q = Net::Z3950::ZOOM::query_create()
469         or ZOOM::_oops(ZOOM::Error::CREATE_QUERY);
470     Net::Z3950::ZOOM::query_cql($q, $string) == 0
471         or ZOOM::_oops(ZOOM::Error::QUERY_CQL, $string);
472
473     return bless {
474         _query => $q,
475     }, $class;
476 }
477
478
479 package ZOOM::Query::PQF;
480 our @ISA = qw(ZOOM::Query);
481
482 sub new {
483     my $class = shift();
484     my($string) = @_;
485
486     my $q = Net::Z3950::ZOOM::query_create()
487         or ZOOM::_oops(ZOOM::Error::CREATE_QUERY);
488     Net::Z3950::ZOOM::query_prefix($q, $string) == 0
489         or ZOOM::_oops(ZOOM::Error::QUERY_PQF, $string);
490
491     return bless {
492         _query => $q,
493     }, $class;
494 }
495
496
497 # ----------------------------------------------------------------------------
498
499 package ZOOM::ResultSet;
500
501 sub new {
502     my $class = shift();
503     die "You can't create $class objects directly";
504 }
505
506 # PRIVATE to ZOOM::Connection::search() and ZOOM::Connection::search_pqf()
507 sub _new {
508     my $class = shift();
509     my($conn, $query, $_rs) = @_;
510
511     return bless {
512         conn => $conn,
513         query => $query,        # This is not currently used, which is
514                                 # just as well since it could be
515                                 # either a string (when the RS is
516                                 # created with search_pqf()) or a
517                                 # ZOOM::Query object (when it's
518                                 # created with search())
519         _rs => $_rs,
520     }, $class;
521 }
522
523 # PRIVATE to this class
524 sub _rs {
525     my $this = shift();
526
527     my $_rs = $this->{_rs};
528     die "{_rs} undefined: has this ResultSet been destroy()ed?"
529         if !defined $_rs;
530
531     return $_rs;
532 }
533
534 sub option {
535     my $this = shift();
536     my($key, $value) = @_;
537
538     my $oldval = Net::Z3950::ZOOM::resultset_option_get($this->_rs(), $key);
539     Net::Z3950::ZOOM::resultset_option_set($this->_rs(), $key, $value)
540         if defined $value;
541
542     return $oldval;
543 }
544
545 sub size {
546     my $this = shift();
547
548     return Net::Z3950::ZOOM::resultset_size($this->_rs());
549 }
550
551 sub record {
552     my $this = shift();
553     my($which) = @_;
554
555     my $_rec = Net::Z3950::ZOOM::resultset_record($this->_rs(), $which);
556     ### Check for error -- but how?
557     return undef if !defined $_rec;
558
559     # For some reason, I have to use the explicit "->" syntax in order
560     # to invoke the ZOOM::Record constructor here, even though I don't
561     # have to do the same for _new ZOOM::ResultSet above.  Weird.
562     return ZOOM::Record->_new($this, $which, $_rec);
563 }
564
565 sub record_immediate {
566     my $this = shift();
567     my($which) = @_;
568
569     my $_rec = Net::Z3950::ZOOM::resultset_record_immediate($this->_rs(),
570                                                             $which);
571     ### Check for error -- but how?
572     return undef if !defined $_rec;
573
574     return ZOOM::Record->_new($this, $which, $_rec);
575 }
576
577 sub cache_reset {
578     my $this = shift();
579
580     Net::Z3950::ZOOM::resultset_cache_reset($this->_rs());
581 }
582
583 sub records {
584     my $this = shift();
585     my($start, $count, $return_records) = @_;
586
587     my $raw = Net::Z3950::ZOOM::resultset_records($this->_rs(), $start, $count,
588                                                   $return_records);
589     ### Why don't we throw an exception if $raw is undefined?
590     return undef if !defined $raw;
591
592     # We need to package up the returned records in ZOOM::Record objects
593     my @res = ();
594     for my $i (0 .. @$raw-1) {
595         my $_rec = $raw->[$i];
596         if (!defined $_rec) {
597             push @res, undef;
598         } else {
599             push @res, ZOOM::Record->_new($this, $start+$i, $_rec);
600         }
601     }
602
603     return \@res;
604 }
605
606 sub sort {
607     my $this = shift();
608     my($sort_type, $sort_spec) = @_;
609
610     return Net::Z3950::ZOOM::resultset_sort1($this->_rs(),
611                                              $sort_type, $sort_spec);
612 }
613
614 sub destroy {
615     my $this = shift();
616
617     Net::Z3950::ZOOM::resultset_destroy($this->_rs());
618     $this->{_rs} = undef;
619 }
620
621
622 # ----------------------------------------------------------------------------
623
624 package ZOOM::Record;
625
626 sub new {
627     my $class = shift();
628     die "You can't create $class objects directly";
629 }
630
631 # PRIVATE to ZOOM::ResultSet::record(),
632 # ZOOM::ResultSet::record_immediate(), ZOOM::ResultSet::records() and
633 # ZOOM::Record::clone()
634 #
635 sub _new {
636     my $class = shift();
637     my($rs, $which, $_rec) = @_;
638
639     return bless {
640         rs => $rs,
641         which => $which,
642         _rec => $_rec,
643     }, $class;
644 }
645
646 # PRIVATE to this class
647 sub _rec {
648     my $this = shift();
649
650     my $_rec = $this->{_rec};
651     die "{_rec} undefined: has this Record been destroy()ed?"
652         if !defined $_rec;
653
654     return $_rec;
655 }
656
657 sub render {
658     my $this = shift();
659
660     my $len = 0;
661     my $string = Net::Z3950::ZOOM::record_get($this->_rec(), "render", $len);
662     # I don't think we need '$len' at all.  ### Probably the Perl-to-C
663     # glue code should use the value of `len' as well as the opaque
664     # data-pointer returned, to ensure that the SV contains all of the
665     # returned data and does not stop at the first NUL character in
666     # binary data.  Carefully check the ZOOM_record_get() documentation.
667     return $string;
668 }
669
670 sub raw {
671     my $this = shift();
672
673     my $len = 0;
674     my $string = Net::Z3950::ZOOM::record_get($this->_rec(), "raw", $len);
675     # See comment about $len in render()
676     return $string;
677 }
678
679 sub clone {
680     my $this = shift();
681
682     my $raw = Net::Z3950::ZOOM::record_clone($this->_rec())
683         or ZOOM::_oops(ZOOM::Error::CLONE);
684
685     # Arg 1 (rs) is undefined as the new record doesn't belong to an RS
686     return _new ZOOM::Record(undef, undef, $raw);
687 }
688
689 sub destroy {
690     my $this = shift();
691
692     Net::Z3950::ZOOM::record_destroy($this->_rec());
693     $this->{_rec} = undef;
694 }
695
696
697 # ----------------------------------------------------------------------------
698
699 package ZOOM::ScanSet;
700
701 sub new {
702     my $class = shift();
703     die "You can't create $class objects directly";
704 }
705
706 # PRIVATE to ZOOM::Connection::scan(),
707 sub _new {
708     my $class = shift();
709     my($conn, $startterm, $_ss) = @_;
710
711     return bless {
712         conn => $conn,
713         startterm => $startterm,
714         _ss => $_ss,
715     }, $class;
716 }
717
718 # PRIVATE to this class
719 sub _ss {
720     my $this = shift();
721
722     my $_ss = $this->{_ss};
723     die "{_ss} undefined: has this ScanSet been destroy()ed?"
724         if !defined $_ss;
725
726     return $_ss;
727 }
728
729 sub option {
730     my $this = shift();
731     my($key, $value) = @_;
732
733     my $oldval = Net::Z3950::ZOOM::scanset_option_get($this->_ss(), $key);
734     Net::Z3950::ZOOM::scanset_option_set($this->_ss(), $key, $value)
735         if defined $value;
736
737     return $oldval;
738 }
739
740 sub size {
741     my $this = shift();
742
743     return Net::Z3950::ZOOM::scanset_size($this->_ss());
744 }
745
746 sub term {
747     my $this = shift();
748     my($which) = @_;
749
750     my($occ, $len) = (0, 0);
751     my $term = Net::Z3950::ZOOM::scanset_term($this->_ss(), $which,
752                                               $occ, $len);
753     ### Throw exception?
754     return undef if !defined $term;
755     die "length of term '$term' differs from returned len=$len"
756         if length($term) != $len;
757
758     return ($term, $occ);
759 }
760
761 sub display_term {
762     my $this = shift();
763     my($which) = @_;
764
765     my($occ, $len) = (0, 0);
766     my $term = Net::Z3950::ZOOM::scanset_display_term($this->_ss(), $which,
767                                                       $occ, $len);
768     ### Throw exception?
769     return undef if !defined $term;
770     die "length of display term '$term' differs from returned len=$len"
771         if length($term) != $len;
772
773     return ($term, $occ);
774 }
775
776 sub destroy {
777     my $this = shift();
778
779     Net::Z3950::ZOOM::scanset_destroy($this->_ss());
780     $this->{_ss} = undef;
781 }
782
783
784 # ----------------------------------------------------------------------------
785
786 package ZOOM::Package;
787
788 sub new {
789     my $class = shift();
790     die "You can't create $class objects directly";
791 }
792
793 # PRIVATE to ZOOM::Connection::package(),
794 sub _new {
795     my $class = shift();
796     my($conn, $options, $_p) = @_;
797
798     return bless {
799         conn => $conn,
800         options => $options,
801         _p => $_p,
802     }, $class;
803 }
804
805 # PRIVATE to this class
806 sub _p {
807     my $this = shift();
808
809     my $_p = $this->{_p};
810     die "{_p} undefined: has this Package been destroy()ed?"
811         if !defined $_p;
812
813     return $_p;
814 }
815
816 sub option {
817     my $this = shift();
818     my($key, $value) = @_;
819
820     my $oldval = Net::Z3950::ZOOM::package_option_get($this->_p(), $key);
821     Net::Z3950::ZOOM::package_option_set($this->_p(), $key, $value)
822         if defined $value;
823
824     return $oldval;
825 }
826
827 sub send {
828     my $this = shift();
829     my($type) = @_;
830
831     Net::Z3950::ZOOM::package_send($this->_p(), $type);
832     $this->{conn}->_check();
833 }
834
835 sub destroy {
836     my $this = shift();
837
838     Net::Z3950::ZOOM::package_destroy($this->_p());
839     $this->{_p} = undef;
840 }
841
842
843 1;