Horrible, horrible support for set_callback(). I can hear them coming
[ZOOM-Perl-moved-to-github.git] / lib / ZOOM.pm
1 # $Id: ZOOM.pm,v 1.9 2005-10-24 16:42:16 mike Exp $
2
3 use strict;
4 use warnings;
5 use Net::Z3950::ZOOM;
6
7
8 package ZOOM;
9
10 sub diag_str {
11     my($code) = @_;
12     return Net::Z3950::ZOOM::diag_str($code);
13 }
14
15
16 # Member naming convention: hash-element names which begin with an
17 # underscore represent underlying ZOOM-C object descriptors; those
18 # which lack them represent Perl's ZOOM objects.  (The same convention
19 # is used in naming local variables where appropriate.)
20 #
21 # So, for example, the ZOOM::Connection class has an {_conn} element,
22 # which is a pointer to the ZOOM-C Connection object; but the
23 # ZOOM::ResultSet class has a {conn} element, which is a reference to
24 # the Perl-level Connection object by which it was created.  (It may
25 # be that we find we have no need for these references, but for now
26 # they are retained.)
27 #
28 # To get at the underlying ZOOM-C connection object of a result-set
29 # (if you ever needed to do such a thing, which you probably don't)
30 # you'd use $rs->{conn}->_conn().
31
32 # ----------------------------------------------------------------------------
33
34 # The "Error" package contains constants returned as error-codes.
35 package ZOOM::Error;
36 sub NONE { Net::Z3950::ZOOM::ERROR_NONE }
37 sub CONNECT { Net::Z3950::ZOOM::ERROR_CONNECT }
38 sub MEMORY { Net::Z3950::ZOOM::ERROR_MEMORY }
39 sub ENCODE { Net::Z3950::ZOOM::ERROR_ENCODE }
40 sub DECODE { Net::Z3950::ZOOM::ERROR_DECODE }
41 sub CONNECTION_LOST { Net::Z3950::ZOOM::ERROR_CONNECTION_LOST }
42 sub INIT { Net::Z3950::ZOOM::ERROR_INIT }
43 sub INTERNAL { Net::Z3950::ZOOM::ERROR_INTERNAL }
44 sub TIMEOUT { Net::Z3950::ZOOM::ERROR_TIMEOUT }
45 sub UNSUPPORTED_PROTOCOL { Net::Z3950::ZOOM::ERROR_UNSUPPORTED_PROTOCOL }
46 sub UNSUPPORTED_QUERY { Net::Z3950::ZOOM::ERROR_UNSUPPORTED_QUERY }
47 sub INVALID_QUERY { Net::Z3950::ZOOM::ERROR_INVALID_QUERY }
48
49 # The "Event" package contains constants returned by last_event()
50 package ZOOM::Event;
51 sub NONE { Net::Z3950::ZOOM::EVENT_NONE }
52 sub CONNECT { Net::Z3950::ZOOM::EVENT_CONNECT }
53 sub SEND_DATA { Net::Z3950::ZOOM::EVENT_SEND_DATA }
54 sub RECV_DATA { Net::Z3950::ZOOM::EVENT_RECV_DATA }
55 sub TIMEOUT { Net::Z3950::ZOOM::EVENT_TIMEOUT }
56 sub UNKNOWN { Net::Z3950::ZOOM::EVENT_UNKNOWN }
57 sub SEND_APDU { Net::Z3950::ZOOM::EVENT_SEND_APDU }
58 sub RECV_APDU { Net::Z3950::ZOOM::EVENT_RECV_APDU }
59 sub RECV_RECORD { Net::Z3950::ZOOM::EVENT_RECV_RECORD }
60 sub RECV_SEARCH { Net::Z3950::ZOOM::EVENT_RECV_SEARCH }
61
62
63 # ----------------------------------------------------------------------------
64
65 package ZOOM::Exception;
66
67 sub new {
68     my $class = shift();
69     my($code, $message, $addinfo) = @_;
70     ### support diag-set, too
71
72     return bless {
73         code => $code,
74         message => $message,
75         addinfo => $addinfo,
76     }, $class;
77 }
78
79 sub code {
80     my $this = shift();
81     return $this->{code};
82 }
83
84 sub message {
85     my $this = shift();
86     return $this->{message};
87 }
88
89 sub addinfo {
90     my $this = shift();
91     return $this->{addinfo};
92 }
93
94
95 # ----------------------------------------------------------------------------
96
97 package ZOOM::Options;
98
99 sub new {
100     my $class = shift();
101     my($p1, $p2) = @_;
102
103     my $opts;
104     if (@_ == 0) {
105         $opts = Net::Z3950::ZOOM::options_create();
106     } elsif (@_ == 1) {
107         $opts = Net::Z3950::ZOOM::options_create_with_parent($p1->_opts());
108     } elsif (@_ == 2) {
109         $opts = Net::Z3950::ZOOM::options_create_with_parent2($p1->_opts(),
110                                                               $p2->_opts());
111     } else {
112         die "can't make $class object with more than 2 parents";
113     }
114
115     return bless {
116         _opts => $opts,
117     }, $class;
118 }
119
120 sub _opts {
121     my $this = shift();
122
123     my $_opts = $this->{_opts};
124     die "{_opts} undefined: has this Options block been destroy()ed?"
125         if !defined $_opts;
126
127     return $_opts;
128 }
129
130 sub option {
131     my $this = shift();
132     my($key, $value) = @_;
133
134     my $oldval = Net::Z3950::ZOOM::options_get($this->_opts(), $key);
135     Net::Z3950::ZOOM::options_set($this->_opts(), $key, $value)
136         if defined $value;
137
138     return $oldval;
139 }
140
141 sub option_binary {
142     my $this = shift();
143     my($key, $value) = @_;
144
145     my $dummylen = 0;
146     my $oldval = Net::Z3950::ZOOM::options_getl($this->_opts(),
147                                                 $key, $dummylen);
148     Net::Z3950::ZOOM::options_setl($this->_opts(), $key,
149                                    $value, length($value))
150         if defined $value;
151
152     return $oldval;
153 }
154
155 # This is a bit stupid, since the scalar values that Perl returns from
156 # option() can be used as a boolean; but it's just possible that some
157 # applications will rely on ZOOM_options_get_bool()'s idiosyncratic
158 # interpretation of what constitutes truth.
159 #
160 sub bool {
161     my $this = shift();
162     my($key, $default) = @_;
163
164     return Net::Z3950::ZOOM::options_get_bool($this->_opts(), $key, $default);
165 }
166
167 # .. and the next two are even more stupid
168 sub int {
169     my $this = shift();
170     my($key, $default) = @_;
171
172     return Net::Z3950::ZOOM::options_get_int($this->_opts(), $key, $default);
173 }
174
175 sub set_int {
176     my $this = shift();
177     my($key, $value) = @_;
178
179     Net::Z3950::ZOOM::options_set_int($this->_opts(), $key, $value);
180 }
181
182 #   ### Feel guilty.  Feel very, very guilty.  I've not been able to
183 #       get the callback memory-management right in "ZOOM.xs", with
184 #       the result that the values of $function and $udata passed into
185 #       this function, which are on the stack, have sometimes been
186 #       freed by the time they're used by __ZOOM_option_callback(),
187 #       with hilarious results.  To avoid this, I copy the values into
188 #       module-scoped globals, and pass _those_ into the extension
189 #       function.  To avoid overwriting those globals by subsequent
190 #       calls, I keep all the old ones, pushed onto the @_function and
191 #       @_udata arrays, which means that THIS FUNCTION LEAKS MEMORY
192 #       LIKE IT'S GOING OUT OF FASHION.  Not nice.  One day, I should
193 #       fix this, but for now there's more important fish to fry.
194 #
195 my(@_function, @_udata);
196 sub set_callback {
197     my $o1 = shift();
198     my($function, $udata) = @_;
199
200     push @_function, $function;
201     push @_udata, $udata;
202     Net::Z3950::ZOOM::options_set_callback($o1->_opts(),
203                                            $_function[-1], $_udata[-1]);
204 }
205
206 sub destroy {
207     my $this = shift();
208
209     Net::Z3950::ZOOM::options_destroy($this->_opts());
210     $this->{_opts} = undef;
211 }
212
213
214 # ----------------------------------------------------------------------------
215
216 package ZOOM::Connection;
217
218 sub new {
219     my $class = shift();
220     my($host, $port) = @_;
221
222     my $_conn = Net::Z3950::ZOOM::connection_new($host, $port);
223     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
224     $errcode = Net::Z3950::ZOOM::connection_error($_conn, $errmsg, $addinfo);
225     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
226
227     return bless {
228         host => $host,
229         port => $port,
230         _conn => $_conn,
231     };
232 }
233
234 sub create {
235     my $class = shift();
236     my($options) = @_;
237
238     my $_conn = Net::Z3950::ZOOM::connection_create($options->_opts());
239     return bless {
240         host => undef,
241         port => undef,
242         _conn => $_conn,
243     };
244 }
245
246 # PRIVATE within this class
247 sub _conn {
248     my $this = shift();
249
250     my $_conn = $this->{_conn};
251     die "{_conn} undefined: has this ResultSet been destroy()ed?"
252         if !defined $_conn;
253
254     return $_conn;
255 }
256
257 sub error_x {
258     my $this = shift();
259
260     my($errcode, $errmsg, $addinfo, $diagset) = (undef, "dummy", "dummy", "d");
261     $errcode = Net::Z3950::ZOOM::connection_error_x($this->_conn(), $errmsg,
262                                                     $addinfo, $diagset);
263     return ($errcode, $errmsg, $addinfo, $diagset);
264 }
265
266 sub errcode {
267     my $this = shift();
268     return Net::Z3950::ZOOM::connection_errcode($this->_conn());
269 }
270
271 sub errmsg {
272     my $this = shift();
273     return Net::Z3950::ZOOM::connection_errmsg($this->_conn());
274 }
275
276 sub addinfo {
277     my $this = shift();
278     return Net::Z3950::ZOOM::connection_addinfo($this->_conn());
279 }
280
281 sub connect {
282     my $this = shift();
283     my($host, $port) = @_;
284
285     Net::Z3950::ZOOM::connection_connect($this->_conn(), $host, $port);
286     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
287     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
288                                                   $errmsg, $addinfo);
289     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
290     # No return value
291 }
292
293 sub option {
294     my $this = shift();
295     my($key, $value) = @_;
296
297     my $oldval = Net::Z3950::ZOOM::connection_option_get($this->_conn(), $key);
298     Net::Z3950::ZOOM::connection_option_set($this->_conn(), $key, $value)
299         if defined $value;
300
301     return $oldval;
302 }
303
304 sub option_binary {
305     my $this = shift();
306     my($key, $value) = @_;
307
308     my $dummylen = 0;
309     my $oldval = Net::Z3950::ZOOM::connection_option_getl($this->_conn(),
310                                                           $key, $dummylen);
311     Net::Z3950::ZOOM::connection_option_setl($this->_conn(), $key,
312                                              $value, length($value))
313         if defined $value;
314
315     return $oldval;
316 }
317
318
319 sub search_pqf {
320     my $this = shift();
321     my($query) = @_;
322
323     my $_rs = Net::Z3950::ZOOM::connection_search_pqf($this->_conn(), $query);
324     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
325     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
326                                                   $errmsg, $addinfo);
327     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
328
329     return _new ZOOM::ResultSet($this, $query, $_rs);
330 }
331
332 sub destroy {
333     my $this = shift();
334
335     Net::Z3950::ZOOM::connection_destroy($this->_conn());
336     $this->{_conn} = undef;
337 }
338
339
340 # ----------------------------------------------------------------------------
341
342 package ZOOM::Query;
343
344 sub new {
345     my $class = shift();
346     die "You can't create $class objects: it's a virtual base class";
347
348 }
349
350
351 package ZOOM::Query::RPN;
352
353 sub new {
354     my $class = shift();
355
356     ### Er ...
357 }
358
359
360 # ----------------------------------------------------------------------------
361
362 package ZOOM::ResultSet;
363
364 sub new {
365     my $class = shift();
366     die "You can't create $class objects directly";
367 }
368
369 # PRIVATE to ZOOM::Connection::search()
370 sub _new {
371     my $class = shift();
372     my($conn, $query, $_rs) = @_;
373
374     return bless {
375         conn => $conn,
376         query => $query,
377         _rs => $_rs,
378     }, $class;
379 }
380
381 # PRIVATE within this class
382 sub _rs {
383     my $this = shift();
384
385     my $_rs = $this->{_rs};
386     die "{_rs} undefined: has this ResultSet been destroy()ed?"
387         if !defined $_rs;
388
389     return $_rs;
390 }
391
392 sub size {
393     my $this = shift();
394
395     return Net::Z3950::ZOOM::resultset_size($this->_rs());
396 }
397
398 sub record {
399     my $this = shift();
400     my($which) = @_;
401
402     my $_rec = Net::Z3950::ZOOM::resultset_record($this->_rs(), $which);
403     ### Check for error -- but how?
404
405     # For some reason, I have to use the explicit "->" syntax in order
406     # to invoke the ZOOM::Record constructor here, even though I don't
407     # have to do the same for _new ZOOM::ResultSet above.  Weird.
408     return ZOOM::Record->_new($this, $which, $_rec);
409 }
410
411 sub destroy {
412     my $this = shift();
413
414     Net::Z3950::ZOOM::resultset_destroy($this->_rs());
415     $this->{_rs} = undef;
416 }
417
418
419 # ----------------------------------------------------------------------------
420
421 package ZOOM::Record;
422
423 sub new {
424     my $class = shift();
425     die "You can't create $class objects directly";
426 }
427
428 # PRIVATE to ZOOM::ResultSet::record()
429 sub _new {
430     my $class = shift();
431     my($rs, $which, $_rec) = @_;
432
433     return bless {
434         rs => $rs,
435         which => $which,
436         _rec => $_rec,
437     }, $class;
438 }
439
440 # PRIVATE within this class
441 sub _rec {
442     my $this = shift();
443
444     return $this->{_rec};
445 }
446
447 sub render {
448     my $this = shift();
449
450     my $len = 0;
451     my $string = Net::Z3950::ZOOM::record_get($this->_rec(), "render", $len);
452     # I don't think we need '$len' at all.  ### Probably the Perl-to-C
453     # glue code should use the value of `len' as well as the opaque
454     # data-pointer returned, to ensure that the SV contains all of the
455     # returned data and does not stop at the first NUL character in
456     # binary data.  Carefully check the ZOOM_record_get() documentation.
457     return $string;
458 }
459
460 sub raw {
461     my $this = shift();
462
463     my $len = 0;
464     my $string = Net::Z3950::ZOOM::record_get($this->_rec(), "raw", $len);
465     # See comment about $len in render()
466     return $string;
467 }
468
469
470 1;