View | Details | Raw Unified | Return to bug 13937
Collapse All | Expand All

(-)a/Koha/Z3950Responder.pm (-18 / +92 lines)
Lines 24-34 use Modern::Perl; Link Here
24
use C4::Biblio qw( GetMarcFromKohaField );
24
use C4::Biblio qw( GetMarcFromKohaField );
25
use C4::Koha qw( GetAuthorisedValues );
25
use C4::Koha qw( GetAuthorisedValues );
26
26
27
use Koha;
28
use Koha::Z3950Responder::Session;
29
30
use Net::Z3950::SimpleServer;
27
use Net::Z3950::SimpleServer;
31
28
29
=head1 NAME
30
31
Koha::Z3950Responder - Main class for interfacing with Net::Z3950::SimpleServer
32
33
=head1 SYNOPSIS
34
35
    use Koha::Z3950Responder;
36
37
    my $z = Koha::Z3950Responder->new( {
38
        add_item_status_subfield => 1,
39
        add_status_multi_subfield => 1,
40
        debug => 0,
41
        num_to_prefetch => 20,
42
        config_dir => '/home/koha/etc',
43
        yaz_options => [ ],
44
    } );
45
46
    $z->start();
47
48
=head1 DESCRIPTION
49
50
A daemon class that interfaces with Net::Z3950::SimpleServer to provider Z39.50/SRU
51
service. Uses a Session class for the actual functionality.
52
53
=head1 METHODS
54
55
=head2 INSTANCE METHODS
56
57
=head3 new
58
59
    $self->new({
60
        add_item_status_subfield => 1
61
    });
62
63
=cut
64
32
sub new {
65
sub new {
33
    my ( $class, $config ) = @_;
66
    my ( $class, $config ) = @_;
34
67
Lines 66-75 sub new { Link Here
66
        unshift @{ $self->{yaz_options} }, '-v', 'none,fatal';
99
        unshift @{ $self->{yaz_options} }, '-v', 'none,fatal';
67
    }
100
    }
68
101
102
    # Set main config for SRU support
103
    unshift @{ $self->{yaz_options} }, '-f', $self->{config_dir} . 'config.xml' if $self->{config_dir};
104
105
    # Set num to prefetch if not passed
106
    $self->{num_to_prefetch} //= 20;
107
69
    $self->{server} = Net::Z3950::SimpleServer->new(
108
    $self->{server} = Net::Z3950::SimpleServer->new(
70
        INIT => sub { $self->init_handler(@_) },
109
        INIT => sub { $self->init_handler(@_) },
71
        SEARCH => sub { $self->search_handler(@_) },
110
        SEARCH => sub { $self->search_handler(@_) },
72
        PRESENT => sub { $self->present_handler(@_) },
73
        FETCH => sub { $self->fetch_handler(@_) },
111
        FETCH => sub { $self->fetch_handler(@_) },
74
        CLOSE => sub { $self->close_handler(@_) },
112
        CLOSE => sub { $self->close_handler(@_) },
75
    );
113
    );
Lines 77-101 sub new { Link Here
77
    return bless( $self, $class );
115
    return bless( $self, $class );
78
}
116
}
79
117
118
=head3 start
119
120
    $z->start();
121
122
Start the daemon and begin serving requests. Does not return unless initialization fails or a
123
fatal error occurs.
124
125
=cut
126
80
sub start {
127
sub start {
81
    my ( $self ) = @_;
128
    my ( $self ) = @_;
82
129
83
    $self->{server}->launch_server( 'Koha::Z3950Responder', @{ $self->{yaz_options} } )
130
    $self->{server}->launch_server( 'Koha::Z3950Responder', @{ $self->{yaz_options} } )
84
}
131
}
85
132
86
# The rest of these methods are SimpleServer callbacks bound to this Z3950Responder object. It's
133
=head2 CALLBACKS
87
# worth noting that these callbacks don't return anything; they both receive and return data in the
134
88
# $args hashref.
135
These methods are SimpleServer callbacks bound to this Z3950Responder object.
136
It's worth noting that these callbacks don't return anything; they both
137
receive and return data in the $args hashref.
138
139
=head3 search_handler
140
141
Callback that is called when a new connection is initialized
142
143
=cut
89
144
90
sub init_handler {
145
sub init_handler {
91
    # Called when the client first connects.
146
    # Called when the client first connects.
92
    my ( $self, $args ) = @_;
147
    my ( $self, $args ) = @_;
93
148
94
    # This holds all of the per-connection state.
149
    # This holds all of the per-connection state.
95
    my $session = Koha::Z3950Responder::Session->new({
150
    my $session;
96
        server => $self,
151
    if (C4::Context->preference('SearchEngine') eq 'Zebra') {
97
        peer => $args->{PEER_NAME},
152
        use Koha::Z3950Responder::ZebraSession;
98
    });
153
        $session = Koha::Z3950Responder::ZebraSession->new({
154
            server => $self,
155
            peer => $args->{PEER_NAME},
156
        });
157
    } else {
158
        use Koha::Z3950Responder::GenericSession;
159
        $session = Koha::Z3950Responder::GenericSession->new({
160
            server => $self,
161
            peer => $args->{PEER_NAME}
162
        });
163
    }
99
164
100
    $args->{HANDLE} = $session;
165
    $args->{HANDLE} = $session;
101
166
Lines 103-129 sub init_handler { Link Here
103
    $args->{IMP_VER} = Koha::version;
168
    $args->{IMP_VER} = Koha::version;
104
}
169
}
105
170
171
=head3 search_handler
172
173
Callback that is called when a new search is performed
174
175
=cut
176
106
sub search_handler {
177
sub search_handler {
107
    # Called when search is first sent.
108
    my ( $self, $args ) = @_;
178
    my ( $self, $args ) = @_;
109
179
110
    $args->{HANDLE}->search_handler($args);
180
    $args->{HANDLE}->search_handler($args);
111
}
181
}
112
182
113
sub present_handler {
183
=head3 fetch_handler
114
    # Called when a set of records is requested.
115
    my ( $self, $args ) = @_;
116
184
117
    $args->{HANDLE}->present_handler($args);
185
Callback that is called when records are requested
118
}
186
187
=cut
119
188
120
sub fetch_handler {
189
sub fetch_handler {
121
    # Called when a given record is requested.
122
    my ( $self, $args ) = @_;
190
    my ( $self, $args ) = @_;
123
191
124
    $args->{HANDLE}->fetch_handler( $args );
192
    $args->{HANDLE}->fetch_handler( $args );
125
}
193
}
126
194
195
=head3 close_handler
196
197
Callback that is called when a session is terminated
198
199
=cut
200
127
sub close_handler {
201
sub close_handler {
128
    my ( $self, $args ) = @_;
202
    my ( $self, $args ) = @_;
129
203
(-)a/Koha/Z3950Responder/GenericSession.pm (+113 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
package Koha::Z3950Responder::GenericSession;
4
5
# Copyright The National Library of Finland 2018
6
#
7
# This file is part of Koha.
8
#
9
# Koha is free software; you can redistribute it and/or modify it under the
10
# terms of the GNU General Public License as published by the Free Software
11
# Foundation; either version 3 of the License, or (at your option) any later
12
# version.
13
#
14
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License along
19
# with Koha; if not, write to the Free Software Foundation, Inc.,
20
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22
use Modern::Perl;
23
24
use base qw( Koha::Z3950Responder::Session );
25
26
use Koha::Logger;
27
use Koha::SearchEngine::Search;
28
use Koha::SearchEngine::QueryBuilder;
29
use Koha::Z3950Responder::RPN;
30
31
=head1 NAME
32
33
Koha::Z3950Responder::genericSession
34
35
=head1 SYNOPSIS
36
37
Backend-agnostic session class that uses C<Koha::Session> as the base class. Utilizes
38
C<Koha::SearchEngine> for the actual functionality.
39
40
=head2 INSTANCE METHODS
41
42
=head3 start_search
43
44
    my ($resultset, $hits) = $self->start_search( $args, $self->{server}->{num_to_prefetch} );
45
46
Perform a search using C<Koha::SearchEngine>'s QueryBuilder and Search.
47
48
=cut
49
50
sub start_search {
51
    my ( $self, $args, $num_to_prefetch ) = @_;
52
53
    if (!defined $self->{'attribute_mappings'}) {
54
        require YAML;
55
        $self->{'attribute_mappings'} = YAML::LoadFile($self->{server}->{config_dir} . 'attribute_mappings.yaml');
56
    }
57
58
    my $database = $args->{DATABASES}->[0];
59
    my $builder = Koha::SearchEngine::QueryBuilder->new({ index => $database });
60
    my $searcher = Koha::SearchEngine::Search->new({ index => $database });
61
62
    my $built_query;
63
    my $query = $args->{RPN}->{'query'}->to_koha($self->{'attribute_mappings'}->{$database});
64
    $self->log_debug("    parsed search: $query");
65
    my @operands = $query;
66
    (undef, $built_query) = $builder->build_query_compat( undef, \@operands, undef, undef, undef, 0);
67
68
    my ($error, $marcresults, $hits ) = $searcher->simple_search_compat($built_query, 0, $num_to_prefetch);
69
    if (defined $error) {
70
        $self->set_error($args, $self->ERR_SEARCH_FAILED, 'Search failed');
71
        return;
72
    }
73
74
    my $resultset = {
75
        query => $built_query,
76
        database => $database,
77
        cached_offset => 0,
78
        cached_results => $marcresults,
79
        hits => $hits
80
    };
81
82
    return ($resultset, $hits);
83
}
84
85
=head3 fetch_record
86
87
    my $record = $self->fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} );
88
89
Fetch a record from SearchEngine. Caches records in session to avoid too many fetches.
90
91
=cut
92
93
sub fetch_record {
94
    my ( $self, $resultset, $args, $index, $num_to_prefetch ) = @_;
95
96
    # Fetch more records if necessary
97
    my $offset = $args->{OFFSET} - 1;
98
    if ($offset < $resultset->{cached_offset} || $offset >= $resultset->{cached_offset} + $num_to_prefetch) {
99
        $self->log_debug("    fetch uncached, fetching $num_to_prefetch records starting at $offset");
100
        my $searcher = Koha::SearchEngine::Search->new({ index => $resultset->{'database'} });
101
        my ($error, $marcresults, $num_hits ) = $searcher->simple_search_compat($resultset->{'query'}, $offset, $num_to_prefetch);
102
        if (defined $error) {
103
            $self->set_error($args, $self->ERR_TEMPORARY_ERROR, 'Fetch failed');
104
            return;
105
        }
106
107
        $resultset->{cached_offset} = $offset;
108
        $resultset->{cached_results} = $marcresults;
109
    }
110
    return $resultset->{cached_results}[$offset - $resultset->{cached_offset}];
111
}
112
113
1;
(-)a/Koha/Z3950Responder/RPN.pm (+117 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright The National Library of Finland 2018
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under thes
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
=head1 NAME
23
24
Koha::Z3950Responder::RPN
25
26
=head1 SYNOPSIS
27
28
Overrides for the C<Net::Z3950::RPN> classes adding a C<to_koha> method that
29
converts the query to a syntax that C<Koha::SearchEngine> understands.
30
31
=head1 DESCRIPTION
32
33
The method used here is described in C<samples/render-search.pl> of
34
C<Net::Z3950::SimpleServer>.
35
36
=cut
37
38
package Net::Z3950::RPN::Term;
39
sub to_koha {
40
    my ($self, $mappings) = @_;
41
42
    my $attrs = $self->{'attributes'};
43
    my $fields = $mappings->{use}{default} // '_all';
44
    my $split = 0;
45
    my $quote = '';
46
    my $prefix = '';
47
    my $suffix = '';
48
    my $term = $self->{'term'};
49
50
    if ($attrs) {
51
        foreach my $attr (@$attrs) {
52
            if ($attr->{'attributeType'} == 1) { # use
53
                my $use = $attr->{'attributeValue'};
54
                $fields = $mappings->{use}{$use} if defined $mappings->{use}{$use};
55
            } elsif ($attr->{'attributeType'} == 4) { # structure
56
                $split = 1 if ($attr->{'attributeValue'} == 2);
57
                $quote = '"' if ($attr->{'attributeValue'} == 1);
58
            } elsif ($attr->{'attributeType'} == 5) { # truncation
59
                my $truncation = $attr->{'attributeValue'};
60
                $prefix = '*' if ($truncation == 2 || $truncation == 3);
61
                $suffix = '*' if ($truncation == 1 || $truncation == 3);
62
            }
63
        }
64
    }
65
66
    $fields = [$fields] unless ref($fields) eq 'ARRAY';
67
68
    if ($split) {
69
        my @terms;
70
        foreach my $word (split(/\s/, $term)) {
71
            $word =~ s/^[\,\.;:\\\/\"\'\-\=]+//g;
72
            $word =~ s/[\,\.;:\\\/\"\'\-\=]+$//g;
73
            next if (!$word);
74
            my @words;
75
            foreach my $field (@{$fields}) {
76
                push(@words, "$field:($prefix$word$suffix)");
77
            }
78
            push (@terms, join(' OR ', @words));
79
        }
80
        return '(' . join(' AND ', @terms) . ')';
81
    }
82
83
    my @terms;
84
    foreach my $field (@{$fields}) {
85
        push(@terms, "$field:($prefix$term$suffix)");
86
    }
87
    return '(' . join(' OR ', @terms) . ')';
88
}
89
90
package Net::Z3950::RPN::And;
91
sub to_koha
92
{
93
    my ($self, $mappings) = @_;
94
95
    return '(' . $self->[0]->to_koha($mappings) . ' AND ' .
96
                 $self->[1]->to_koha($mappings) . ')';
97
}
98
99
package Net::Z3950::RPN::Or;
100
sub to_koha
101
{
102
    my ($self, $mappings) = @_;
103
104
    return '(' . $self->[0]->to_koha($mappings) . ' OR ' .
105
                 $self->[1]->to_koha($mappings) . ')';
106
}
107
108
package Net::Z3950::RPN::AndNot;
109
sub to_koha
110
{
111
    my ($self, $mappings) = @_;
112
113
    return '(' . $self->[0]->to_koha($mappings) . ' NOT ' .
114
                 $self->[1]->to_koha($mappings) . ')';
115
}
116
117
1;
(-)a/Koha/Z3950Responder/Session.pm (-155 / +189 lines)
Lines 28-34 use C4::Reserves qw( GetReserveStatus ); Link Here
28
use C4::Search qw();
28
use C4::Search qw();
29
use Koha::Logger;
29
use Koha::Logger;
30
30
31
use ZOOM;
31
=head1 NAME
32
33
Koha::Z3950Responder::Session
34
35
=head1 SYNOPSIS
36
37
An abstract class where backend-specific session modules are derived from.
38
Z3950Responder creates one of the child classes depending on the SearchEngine
39
preference.
40
41
=head1 DESCRIPTION
42
43
This class contains common functions for handling searching for and fetching
44
of records. It can optionally add item status information to the returned
45
records. The backend-specific abstract methods need to be implemented in a
46
child class.
47
48
=head2 CONSTANTS
49
50
OIDs and diagnostic codes used in Z39.50
51
52
=cut
32
53
33
use constant {
54
use constant {
34
    UNIMARC_OID => '1.2.840.10003.5.1',
55
    UNIMARC_OID => '1.2.840.10003.5.1',
Lines 41-50 use constant { Link Here
41
    ERR_PRESENT_OUT_OF_RANGE => 13,
62
    ERR_PRESENT_OUT_OF_RANGE => 13,
42
    ERR_RECORD_TOO_LARGE => 16,
63
    ERR_RECORD_TOO_LARGE => 16,
43
    ERR_NO_SUCH_RESULTSET => 30,
64
    ERR_NO_SUCH_RESULTSET => 30,
44
    ERR_SYNTAX_UNSUPPORTED => 230,
65
    ERR_SEARCH_FAILED => 125,
66
    ERR_SYNTAX_UNSUPPORTED => 239,
45
    ERR_DB_DOES_NOT_EXIST => 235,
67
    ERR_DB_DOES_NOT_EXIST => 235,
46
};
68
};
47
69
70
=head1 FUNCTIONS
71
72
=head2 INSTANCE METHODS
73
74
=head3 new
75
76
    my $session = $self->new({
77
        server => $z3950responder,
78
        peer => 'PEER NAME'
79
    });
80
81
Instantiate a Session
82
83
=cut
84
48
sub new {
85
sub new {
49
    my ( $class, $args ) = @_;
86
    my ( $class, $args ) = @_;
50
87
Lines 58-246 sub new { Link Here
58
        $self->{logger}->debug_to_screen();
95
        $self->{logger}->debug_to_screen();
59
    }
96
    }
60
97
61
    $self->_log_info("connected");
98
    $self->log_info('connected');
62
99
63
    return $self;
100
    return $self;
64
}
101
}
65
102
66
sub _log_debug {
103
=head3 search_handler
67
    my ( $self, $msg ) = @_;
68
    $self->{logger}->debug("[$self->{peer}] $msg");
69
}
70
71
sub _log_info {
72
    my ( $self, $msg ) = @_;
73
    $self->{logger}->info("[$self->{peer}] $msg");
74
}
75
76
sub _log_error {
77
    my ( $self, $msg ) = @_;
78
    $self->{logger}->error("[$self->{peer}] $msg");
79
}
80
81
sub _set_error {
82
    my ( $self, $args, $code, $msg ) = @_;
83
84
    ( $args->{ERR_CODE}, $args->{ERR_STR} ) = ( $code, $msg );
85
86
    $self->_log_error("    returning error $code: $msg");
87
}
88
89
sub _set_error_from_zoom {
90
    my ( $self, $args, $exception ) = @_;
91
92
    $self->_set_error( $args, ERR_TEMPORARY_ERROR, 'Cannot connect to upstream server' );
93
    $self->_log_error(
94
        "Zebra upstream error: " .
95
        $exception->message() . " (" .
96
        $exception->code() . ") " .
97
        ( $exception->addinfo() // '' ) . " " .
98
        $exception->diagset()
99
    );
100
}
101
102
# This code originally went through C4::Search::getRecords, but had to use so many escape hatches
103
# that it was easier to directly connect to Zebra.
104
sub _start_search {
105
    my ( $self, $args, $in_retry ) = @_;
106
107
    my $database = $args->{DATABASES}->[0];
108
    my ( $connection, $results );
109
104
110
    eval {
105
    Callback that is called when a new search is performed
111
        $connection = C4::Context->Zconn(
112
            # We're depending on the caller to have done some validation.
113
            $database eq 'biblios' ? 'biblioserver' : 'authorityserver',
114
            0 # No, no async, doesn't really help much for single-server searching
115
        );
116
106
117
        $results = $connection->search_pqf( $args->{QUERY} );
107
Calls C<start_search> for backend-specific retrieval logic
118
108
119
        $self->_log_debug('    retry successful') if ($in_retry);
109
=cut
120
    };
121
    if ($@) {
122
        die $@ if ( ref($@) ne 'ZOOM::Exception' );
123
124
        if ( $@->diagset() eq 'ZOOM' && $@->code() == 10004 && !$in_retry ) {
125
            $self->_log_debug('    upstream server lost connection, retrying');
126
            return $self->_start_search( $args, 1 );
127
        }
128
129
        $self->_set_error_from_zoom( $args, $@ );
130
        $connection = undef;
131
    }
132
133
    return ( $connection, $results, $results ? $results->size() : -1 );
134
}
135
136
sub _check_fetch {
137
    my ( $self, $resultset, $args, $offset, $num_records ) = @_;
138
139
    if ( !defined( $resultset ) ) {
140
        $self->_set_error( $args, ERR_NO_SUCH_RESULTSET, 'No such resultset' );
141
        return 0;
142
    }
143
144
    if ( $offset < 0 || $offset + $num_records > $resultset->{hits} )  {
145
        $self->_set_error( $args, ERR_PRESENT_OUT_OF_RANGE, 'Present request out of range' );
146
        return 0;
147
    }
148
149
    return 1;
150
}
151
152
sub _fetch_record {
153
    my ( $self, $resultset, $args, $index, $num_to_prefetch ) = @_;
154
155
    my $record;
156
157
    eval {
158
        if ( !$resultset->{results}->record_immediate( $index ) ) {
159
            my $start = $num_to_prefetch ? int( $index / $num_to_prefetch ) * $num_to_prefetch : $index;
160
161
            if ( $start + $num_to_prefetch >= $resultset->{results}->size() ) {
162
                $num_to_prefetch = $resultset->{results}->size() - $start;
163
            }
164
165
            $self->_log_debug("    fetch uncached, fetching $num_to_prefetch records starting at $start");
166
167
            $resultset->{results}->records( $start, $num_to_prefetch, 0 );
168
        }
169
170
        $record = $resultset->{results}->record_immediate( $index )->raw();
171
    };
172
    if ($@) {
173
        die $@ if ( ref($@) ne 'ZOOM::Exception' );
174
        $self->_set_error_from_zoom( $args, $@ );
175
        return;
176
    } else {
177
        return $record;
178
    }
179
}
180
110
181
sub search_handler {
111
sub search_handler {
182
    # Called when search is first sent.
183
    my ( $self, $args ) = @_;
112
    my ( $self, $args ) = @_;
184
113
185
    my $database = $args->{DATABASES}->[0];
114
    my $database = $args->{DATABASES}->[0];
186
115
187
    if ( $database !~ /^(biblios|authorities)$/ ) {
116
    if ( $database ne $Koha::SearchEngine::BIBLIOS_INDEX && $database ne $Koha::SearchEngine::AUTHORITIES_INDEX ) {
188
        $self->_set_error( $args, ERR_DB_DOES_NOT_EXIST, 'No such database' );
117
        $self->set_error( $args, $self->ERR_DB_DOES_NOT_EXIST, 'No such database' );
189
        return;
118
        return;
190
    }
119
    }
191
120
192
    my $query = $args->{QUERY};
121
    my $query = $args->{QUERY};
193
    $self->_log_info("received search for '$query', (RS $args->{SETNAME})");
122
    $self->log_info("received search for '$query', (RS $args->{SETNAME})");
194
195
    my ( $connection, $results, $num_hits ) = $self->_start_search( $args );
196
    return unless $connection;
197
198
    $args->{HITS} = $num_hits;
199
    my $resultset = $self->{resultsets}->{ $args->{SETNAME} } = {
200
        database => $database,
201
        connection => $connection,
202
        results => $results,
203
        query => $args->{QUERY},
204
        hits => $args->{HITS},
205
    };
206
}
207
123
208
sub present_handler {
124
    my ($resultset, $hits) = $self->start_search( $args, $self->{server}->{num_to_prefetch} );
209
    # Called when a set of records is requested.
125
    return unless $resultset;
210
    my ( $self, $args ) = @_;
211
126
212
    $self->_log_debug("received present for $args->{SETNAME}, $args->{START}+$args->{NUMBER}");
127
    $args->{HITS} = $hits;
128
    $self->{resultsets}->{ $args->{SETNAME} } = $resultset;
129
}
213
130
214
    my $resultset = $self->{resultsets}->{ $args->{SETNAME} };
131
=head3 fetch_handler
215
    # The offset comes across 1-indexed.
216
    my $offset = $args->{START} - 1;
217
132
218
    return unless $self->_check_fetch( $resultset, $args, $offset, $args->{NUMBER} );
133
    Callback that is called when records are requested
219
134
220
}
135
Calls C<fetch_record> for backend-specific retrieval logic
136
137
=cut
221
138
222
sub fetch_handler {
139
sub fetch_handler {
223
    # Called when a given record is requested.
224
    my ( $self, $args ) = @_;
140
    my ( $self, $args ) = @_;
225
    my $session = $args->{HANDLE};
141
142
    $self->log_debug("received fetch for RS $args->{SETNAME}, record $args->{OFFSET}");
143
226
    my $server = $self->{server};
144
    my $server = $self->{server};
227
145
228
    $self->_log_debug("received fetch for $args->{SETNAME}, record $args->{OFFSET}");
229
    my $form_oid = $args->{REQ_FORM} // '';
146
    my $form_oid = $args->{REQ_FORM} // '';
230
    my $composition = $args->{COMP} // '';
147
    my $composition = $args->{COMP} // '';
231
    $self->_log_debug("    form OID $form_oid, composition $composition");
148
    $self->log_debug("    form OID '$form_oid', composition '$composition'");
232
149
233
    my $resultset = $session->{resultsets}->{ $args->{SETNAME} };
150
    my $resultset = $self->{resultsets}->{ $args->{SETNAME} };
234
    # The offset comes across 1-indexed.
151
    # The offset comes across 1-indexed.
235
    my $offset = $args->{OFFSET} - 1;
152
    my $offset = $args->{OFFSET} - 1;
236
153
237
    return unless $self->_check_fetch( $resultset, $args, $offset, 1 );
154
    return unless $self->check_fetch( $resultset, $args, $offset, 1 );
238
155
239
    $args->{LAST} = 1 if ( $offset == $resultset->{hits} - 1 );
156
    $args->{LAST} = 1 if ( $offset == $resultset->{hits} - 1 );
240
157
241
    my $record = $self->_fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} );
158
    my $record = $self->fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} );
242
    return unless $record;
159
    return unless $record;
243
160
161
    # Note that new_record_from_zebra is badly named and works also with Elasticsearch
244
    $record = C4::Search::new_record_from_zebra(
162
    $record = C4::Search::new_record_from_zebra(
245
        $resultset->{database} eq 'biblios' ? 'biblioserver' : 'authorityserver',
163
        $resultset->{database} eq 'biblios' ? 'biblioserver' : 'authorityserver',
246
        $record
164
        $record
Lines 254-269 sub fetch_handler { Link Here
254
        }
172
        }
255
    }
173
    }
256
174
257
    if ( $form_oid eq MARCXML_OID && $composition eq 'marcxml' ) {
175
    if ( $form_oid eq $self->MARCXML_OID && $composition eq 'marcxml' ) {
258
        $args->{RECORD} = $record->as_xml_record();
176
        $args->{RECORD} = $record->as_xml_record();
259
    } elsif ( ( $form_oid eq USMARC_OID || $form_oid eq UNIMARC_OID ) && ( !$composition || $composition eq 'F' ) ) {
177
    } elsif ( ( $form_oid eq $self->USMARC_OID || $form_oid eq $self->UNIMARC_OID ) && ( !$composition || $composition eq 'F' ) ) {
260
        $args->{RECORD} = $record->as_usmarc();
178
        $args->{RECORD} = $record->as_usmarc();
261
    } else {
179
    } else {
262
        $self->_set_error( $args, ERR_SYNTAX_UNSUPPORTED, "Unsupported syntax/composition $form_oid/$composition" );
180
        $self->set_error( $args, $self->ERR_SYNTAX_UNSUPPORTED, "Unsupported syntax/composition $form_oid/$composition" );
263
        return;
181
        return;
264
    }
182
    }
265
}
183
}
266
184
185
=head3 close_handler
186
187
Callback that is called when a session is terminated
188
189
=cut
190
191
sub close_handler {
192
    my ( $self, $args ) = @_;
193
194
    # Override in a child class to add functionality
195
}
196
197
=head3 start_search
198
199
    my ($resultset, $hits) = $self->_start_search( $args, $self->{server}->{num_to_prefetch} );
200
201
A backend-specific method for starting a new search
202
203
=cut
204
205
sub start_search {
206
    die('Abstract method');
207
}
208
209
=head3 check_fetch
210
211
    $self->check_fetch($resultset, $args, $offset, $num_records);
212
213
Check that the fetch request parameters are within bounds of the result set.
214
215
=cut
216
217
sub check_fetch {
218
    my ( $self, $resultset, $args, $offset, $num_records ) = @_;
219
220
    if ( !defined( $resultset ) ) {
221
        $self->set_error( $args, ERR_NO_SUCH_RESULTSET, 'No such resultset' );
222
        return 0;
223
    }
224
225
    if ( $offset < 0 || $offset + $num_records > $resultset->{hits} )  {
226
        $self->set_error( $args, ERR_PRESENT_OUT_OF_RANGE, 'Present request out of range' );
227
        return 0;
228
    }
229
230
    return 1;
231
}
232
233
=head3 fetch_record
234
235
    my $record = $self->_fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} );
236
237
A backend-specific method for fetching a record
238
239
=cut
240
241
sub fetch_record {
242
    die('Abstract method');
243
}
244
245
=head3 add_item_statuses
246
247
    $self->add_item_status( $field );
248
249
Add item status to the given field
250
251
=cut
252
267
sub add_item_status {
253
sub add_item_status {
268
    my ( $self, $field ) = @_;
254
    my ( $self, $field ) = @_;
269
255
Lines 318-329 sub add_item_status { Link Here
318
    }
304
    }
319
}
305
}
320
306
321
sub close_handler {
322
    my ( $self, $args ) = @_;
323
307
324
    foreach my $resultset ( values %{ $self->{resultsets} } ) {
308
=head3 log_debug
325
        $resultset->{results}->destroy();
309
326
    }
310
    $self->log_debug('Message');
311
312
Output a debug message
313
314
=cut
315
316
sub log_debug {
317
    my ( $self, $msg ) = @_;
318
    $self->{logger}->debug("[$self->{peer}] $msg");
319
}
320
321
=head3 log_info
322
323
    $self->log_info('Message');
324
325
Output an info message
326
327
=cut
328
329
sub log_info {
330
    my ( $self, $msg ) = @_;
331
    $self->{logger}->info("[$self->{peer}] $msg");
332
}
333
334
=head3 log_error
335
336
    $self->log_error('Message');
337
338
Output an error message
339
340
=cut
341
342
sub log_error {
343
    my ( $self, $msg ) = @_;
344
    $self->{logger}->error("[$self->{peer}] $msg");
345
}
346
347
=head3 set_error
348
349
    $self->set_error($args, $self->ERR_SEARCH_FAILED, 'Backend connection failed' );
350
351
Set and log an error code and diagnostic message to be returned to the client
352
353
=cut
354
355
sub set_error {
356
    my ( $self, $args, $code, $msg ) = @_;
357
358
    ( $args->{ERR_CODE}, $args->{ERR_STR} ) = ( $code, $msg );
359
360
    $self->log_error("    returning error $code: $msg");
327
}
361
}
328
362
329
1;
363
1;
(-)a/Koha/Z3950Responder/ZebraSession.pm (+163 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
package Koha::Z3950Responder::ZebraSession;
4
5
# Copyright ByWater Solutions 2016
6
#
7
# This file is part of Koha.
8
#
9
# Koha is free software; you can redistribute it and/or modify it under the
10
# terms of the GNU General Public License as published by the Free Software
11
# Foundation; either version 3 of the License, or (at your option) any later
12
# version.
13
#
14
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License along
19
# with Koha; if not, write to the Free Software Foundation, Inc.,
20
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22
use Modern::Perl;
23
24
use base qw( Koha::Z3950Responder::Session );
25
26
use Koha::Logger;
27
28
use ZOOM;
29
30
=head1 NAME
31
32
Koha::Z3950Responder::ZebraSession
33
34
=head1 SYNOPSIS
35
36
Zebra-specific session class that uses C<Koha::Session> as the base class.
37
38
=head1 FUNCTIONS
39
40
=head2 INSTANCE METHODS
41
42
=head3 start_search
43
44
    my ($resultset, $hits) = $self->_start_search( $args, $self->{server}->{num_to_prefetch} );
45
46
Connect to Zebra and do the search
47
48
=cut
49
50
sub start_search {
51
    my ( $self, $args, $num_to_prefetch, $in_retry ) = @_;
52
53
    my $database = $args->{DATABASES}->[0];
54
    my ( $connection, $results );
55
56
    eval {
57
        $connection = C4::Context->Zconn(
58
            # We're depending on the caller to have done some validation.
59
            $database eq 'biblios' ? 'biblioserver' : 'authorityserver',
60
            0 # No, no async, doesn't really help much for single-server searching
61
        );
62
63
        $results = $connection->search_pqf( $args->{QUERY} );
64
65
        $self->log_debug('    retry successful') if ($in_retry);
66
    };
67
    if ($@) {
68
        die $@ if ( ref($@) ne 'ZOOM::Exception' );
69
70
        if ( $@->diagset() eq 'ZOOM' && $@->code() == 10004 && !$in_retry ) {
71
            $self->log_debug('    upstream server lost connection, retrying');
72
            return $self->_start_search( $args, $num_to_prefetch, 1 );
73
        }
74
75
        $self->_set_error_from_zoom( $args, $@ );
76
        $connection = undef;
77
    }
78
79
    my $hits = $results ? $results->size() : -1;
80
    my $resultset = {
81
        database => $database,
82
        connection => $connection,
83
        results => $results,
84
        query => $args->{QUERY},
85
        hits => $hits
86
    };
87
88
    return ( $resultset, $hits );
89
}
90
91
=head3 new
92
93
    my $record = $self->_fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} );
94
95
Fetch a record from Zebra. Caches records in session to avoid too many fetches.
96
97
=cut
98
99
sub fetch_record {
100
    my ( $self, $resultset, $args, $index, $num_to_prefetch ) = @_;
101
102
    my $record;
103
104
    eval {
105
        if ( !$resultset->{results}->record_immediate( $index ) ) {
106
            my $start = $num_to_prefetch ? int( $index / $num_to_prefetch ) * $num_to_prefetch : $index;
107
108
            if ( $start + $num_to_prefetch >= $resultset->{results}->size() ) {
109
                $num_to_prefetch = $resultset->{results}->size() - $start;
110
            }
111
112
            $self->log_debug("    fetch uncached, fetching $num_to_prefetch records starting at $start");
113
114
            $resultset->{results}->records( $start, $num_to_prefetch, 0 );
115
        }
116
117
        $record = $resultset->{results}->record_immediate( $index )->raw();
118
    };
119
    if ($@) {
120
        die $@ if ( ref($@) ne 'ZOOM::Exception' );
121
        $self->_set_error_from_zoom( $args, $@ );
122
        return;
123
    } else {
124
        return $record;
125
    }
126
}
127
128
=head3 close_handler
129
130
Callback that is called when a session is terminated
131
132
=cut
133
134
sub close_handler {
135
    my ( $self, $args ) = @_;
136
137
    foreach my $resultset ( values %{ $self->{resultsets} } ) {
138
        $resultset->{results}->destroy();
139
    }
140
}
141
142
=head3 _set_error_from_zoom
143
144
    $self->_set_error_from_zoom( $args, $@ );
145
146
Log and set error code and diagnostic message from a ZOOM exception
147
148
=cut
149
150
sub _set_error_from_zoom {
151
    my ( $self, $args, $exception ) = @_;
152
153
    $self->set_error( $args, $self->ERR_TEMPORARY_ERROR, 'Cannot connect to upstream server' );
154
    $self->log_error(
155
        "Zebra upstream error: " .
156
        $exception->message() . " (" .
157
        $exception->code() . ") " .
158
        ( $exception->addinfo() // '' ) . " " .
159
        $exception->diagset()
160
    );
161
}
162
163
1;
(-)a/etc/z3950/attribute_mappings.yaml (+46 lines)
Line 0 Link Here
1
---
2
# Mappings from Z39.50 USE attributes to Koha search fields
3
authorities:
4
    # BIB-1 use attributes to index fields
5
    use:
6
        1: Personal-name
7
        2: Heading
8
        3: Heading
9
        9: LC-card-number
10
        12: Local-number
11
        default: _all
12
biblios:
13
    # BIB-1 use attributes to index fields
14
    use:
15
        1: author
16
        2: author
17
        3: author
18
        4: title
19
        5: se
20
        7: isbn
21
        8: issn
22
        9: LC-card-number
23
        10: bnb-card-number
24
        11: bgf-number
25
        12: Local-number
26
        20: local-classification
27
        21: subject
28
        30:
29
            - acqdate
30
            - copydate
31
            - pubdate
32
        31: pubdate
33
        32: acqdate
34
        52: control-number
35
        1003: author
36
        1007: identifier-standard
37
        1011: date-entered-on-file
38
        1012: date-time-last-modified
39
        1018: publisher
40
        1019: record-source
41
        1021: bib-level
42
        1028: barcode
43
        1031: itype
44
        1033: Host-Item-Number
45
        1045: control-number
46
        default: _all
(-)a/etc/z3950/config.xml (+12 lines)
Line 0 Link Here
1
<yazgfs>
2
  <server>
3
    <cql2rpn>pqf.properties</cql2rpn>
4
    <explain xmlns="http://explain.z3950.org/dtd/2.0/">
5
      <retrievalinfo>
6
        <retrieval syntax="usmarc" name="marc21"/>
7
        <retrieval syntax="unimarc" name="unimarc"/>
8
        <retrieval syntax="xml" name="marcxml" identifier="info:srw/schema/1/marcxml-v1.1"/>
9
      </retrievalinfo>
10
    </explain>
11
  </server>
12
</yazgfs>
(-)a/etc/z3950/pqf.properties (+162 lines)
Line 0 Link Here
1
#
2
# Propeties file to drive org.z3950.zing.cql.CQLNode's toPQF()
3
# back-end and the YAZ CQL-to-PQF converter.  This specifies the
4
# interpretation of various CQL indexes, relations, etc. in terms
5
# of Type-1 query attributes.
6
#
7
# This configuration file generates queries using BIB-1 attributes.
8
# See http://www.loc.gov/z3950/agency/zing/cql/dc-indexes.html
9
# for the Maintenance Agency's work-in-progress mapping of Dublin Core
10
# indexes to Attribute Architecture (util, XD and BIB-2)
11
# attributes.
12
13
# Identifiers for prefixes used in this file. (index.*)
14
set.cql     = info:srw/cql-context-set/1/cql-v1.1
15
set.rec     = info:srw/cql-context-set/2/rec-1.0
16
set.dc      = info:srw/cql-context-set/1/dc-v1.1
17
set.bath    = http://zing.z3950.org/cql/bath/2.0/
18
19
# default set (in query)
20
set     = info:srw/cql-context-set/1/dc-v1.1
21
22
# The default access point and result-set references
23
index.cql.serverChoice = 1=1016
24
    # srw.serverChoice is deprecated in favour of cql.serverChoice
25
    # BIB-1 "any"
26
27
index.rec.id                = 1=12
28
index.dc.identifier         = 1=1007
29
index.dc.title              = 1=4
30
index.dc.subject            = 1=21
31
index.dc.creator            = 1=1003
32
index.dc.author             = 1=1003
33
index.dc.itemtype           = 1=1031
34
index.dc.barcode            = 1=1028
35
index.dc.branch             = 1=1033
36
index.dc.isbn               = 1=7
37
index.dc.issn               = 1=8
38
index.dc.any                = 1=1016
39
index.dc.note               = 1=63
40
41
# personal name experimental
42
index.dc.pname  = 1=1
43
    ### Unofficial synonym for "creator"
44
index.dc.editor             = 1=1020
45
index.dc.publisher          = 1=1018
46
index.dc.description        = 1=62
47
    # "abstract"
48
index.dc.date               = 1=30
49
index.dc.resourceType       = 1=1031
50
    # guesswork: "Material-type"
51
index.dc.format             = 1=1034
52
    # guesswork: "Content-type"
53
index.dc.resourceIdentifier = 1=12
54
    # "Local number"
55
#index.dc.source                = 1=1019
56
    # "Record-source"
57
index.dc.language           = 1=54
58
    # "Code--language"
59
60
index.dc.Place-publication  = 1=59
61
    # "Place-publication"
62
63
#index.dc.relation           = 1=?
64
    ### No idea how to represent this
65
#index.dc.coverage           = 1=?
66
    ### No idea how to represent this
67
#index.dc.rights             = 1=?
68
    ### No idea how to represent this
69
70
# Relation attributes are selected according to the CQL relation by
71
# looking up the "relation.<relation>" property:
72
#
73
relation.<                  = 2=1
74
relation.le                 = 2=2
75
relation.eq                 = 2=3
76
relation.exact              = 2=3
77
relation.ge                 = 2=4
78
relation.>                  = 2=5
79
relation.<>                 = 2=6
80
81
### These two are not really right:
82
relation.all                = 2=3
83
relation.any                = 2=3
84
85
# BIB-1 doesn't have a server choice relation, so we just make the
86
# choice here, and use equality (which is clearly correct).
87
relation.scr                = 2=3
88
89
# Relation modifiers.
90
#
91
relationModifier.relevant   = 2=102
92
relationModifier.fuzzy      = 5=103
93
    ### 100 is "phonetic", which is not quite the same thing
94
relationModifier.stem       = 2=101
95
relationModifier.phonetic   = 2=100
96
97
# Position attributes may be specified for anchored terms (those
98
# beginning with "^", which is stripped) and unanchored (those not
99
# beginning with "^").  This may change when we get a BIB-1 truncation
100
# attribute that says "do what CQL does".
101
#
102
position.first              = 3=1 6=1
103
    # "first in field"
104
position.any                = 3=3 6=1
105
    # "any position in field"
106
position.last               = 3=4 6=1
107
    # not a standard BIB-1 attribute
108
position.firstAndLast       = 3=3 6=3
109
    # search term is anchored to be complete field
110
111
# Structure attributes may be specified for individual relations; a
112
# default structure attribute my be specified by the pseudo-relation
113
# "*", to be used whenever a relation not listed here occurs.
114
#
115
structure.exact             = 4=108
116
    # string
117
structure.all               = 4=2
118
structure.any               = 4=2
119
structure.*                 = 4=1
120
    # phrase
121
122
# Truncation attributes used to implement CQL wildcard patterns.  The
123
# simpler forms, left, right- and both-truncation will be used for the
124
# simplest patterns, so that we produce PQF queries that conform more
125
# closely to the Bath Profile.  However, when a more complex pattern
126
# such as "foo*bar" is used, we fall back on Z39.58-style masking.
127
#
128
truncation.right            = 5=1
129
truncation.left             = 5=2
130
truncation.both             = 5=3
131
truncation.none             = 5=100
132
truncation.z3958            = 5=104
133
134
# Finally, any additional attributes that should always be included
135
# with each term can be specified in the "always" property.
136
#
137
always                      = 6=1
138
# 6=1: completeness = incomplete subfield
139
140
141
# Bath Profile support, added Thu Dec 18 13:06:20 GMT 2003
142
# See the Bath Profile for SRW at
143
#   http://zing.z3950.org/cql/bath.html
144
# including the Bath Context Set defined within that document.
145
#
146
# In this file, we only map index-names to BIB-1 use attributes, doing
147
# so in accordance with the specifications of the Z39.50 Bath Profile,
148
# and leaving the relations, wildcards, etc. to fend for themselves.
149
150
index.bath.keyTitle         = 1=33
151
index.bath.possessingInstitution    = 1=1044
152
index.bath.name             = 1=1002
153
index.bath.personalName     = 1=1
154
index.bath.corporateName    = 1=2
155
index.bath.conferenceName   = 1=3
156
index.bath.uniformTitle     = 1=6
157
index.bath.isbn             = 1=7
158
index.bath.issn             = 1=8
159
index.bath.geographicName   = 1=58
160
index.bath.notes            = 1=63
161
index.bath.topicalSubject   = 1=1079
162
index.bath.genreForm        = 1=1075
(-)a/misc/bin/koha-zebra-ctl-oma.sh (-88 lines)
Lines 1-88 Link Here
1
#!/bin/bash
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
### BEGIN INIT INFO
19
# Provides:          koha-zebra-daemon
20
# Required-Start:    $syslog $remote_fs
21
# Required-Stop:     $syslog $remote_fs
22
# Default-Start:     2 3 4 5
23
# Default-Stop:      0 1 6
24
# Short-Description: Zebra server daemon for Koha indexing
25
### END INIT INFO
26
27
USER=ere
28
GROUP=users
29
DBNAME=koha3
30
NAME=koha-zebra-ctl.koha3
31
LOGDIR=/home/ere/koha-dev/var/log
32
ERRLOG=$LOGDIR/koha-zebradaemon.err
33
STDOUT=$LOGDIR/koha-zebradaemon.log
34
OUTPUT=$LOGDIR/koha-zebradaemon-output.log
35
KOHA_CONF=/home/ere/koha-dev/etc/koha-conf.xml
36
RUNDIR=/home/ere/koha-dev/var/run
37
LOCKDIR=/home/ere/koha-dev/var/lock
38
# you may need to change this depending on where zebrasrv is installed
39
ZEBRASRV=/usr/bin/zebrasrv
40
ZEBRAOPTIONS="-v none,fatal,warn"
41
42
test -f $ZEBRASRV || exit 0
43
44
OTHERUSER=''
45
if [[ $EUID -eq 0 ]]; then
46
    OTHERUSER="--user=$USER.$GROUP"
47
fi
48
49
case "$1" in
50
    start)
51
      echo "Starting Zebra Server"
52
53
      # create run and lock directories if needed;
54
      # /var/run and /var/lock are completely cleared at boot
55
      # on some platforms
56
      if [[ ! -d $RUNDIR ]]; then
57
        umask 022
58
        mkdir -p $RUNDIR
59
        if [[ $EUID -eq 0 ]]; then
60
            chown $USER:$GROUP $RUNDIR
61
        fi
62
      fi
63
      if [[ ! -d $LOCKDIR ]]; then
64
        umask 022
65
        mkdir -p $LOCKDIR
66
        mkdir -p $LOCKDIR/biblios
67
        mkdir -p $LOCKDIR/authorities
68
        mkdir -p $LOCKDIR/rebuild
69
        if [[ $EUID -eq 0 ]]; then
70
            chown -R $USER:$GROUP $LOCKDIR
71
        fi
72
      fi
73
74
      daemon --name=$NAME --errlog=$ERRLOG --stdout=$STDOUT --output=$OUTPUT --verbose=1 --respawn --delay=30 $OTHERUSER -- $ZEBRASRV $ZEBRAOPTIONS -f $KOHA_CONF
75
      ;;
76
    stop)
77
      echo "Stopping Zebra Server"
78
      daemon --name=$NAME --errlog=$ERRLOG --stdout=$STDOUT --output=$OUTPUT --verbose=1 --respawn --delay=30 $OTHERUSER --stop -- $ZEBRASRV -f $KOHA_CONF
79
      ;;
80
    restart)
81
      echo "Restarting the Zebra Server"
82
      daemon --name=$NAME --errlog=$ERRLOG --stdout=$STDOUT --output=$OUTPUT --verbose=1 --respawn --delay=30 $OTHERUSER --restart -- $ZEBRASRV -f $KOHA_CONF
83
      ;;
84
    *)
85
      echo "Usage: /etc/init.d/$NAME {start|stop|restart}"
86
      exit 1
87
      ;;
88
esac
(-)a/misc/z3950_responder.pl (-7 / +22 lines)
Lines 20-29 Link Here
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Carp;
22
use Carp;
23
use File::Basename;
23
use Getopt::Long qw(:config no_ignore_case);
24
use Getopt::Long qw(:config no_ignore_case);
24
use Pod::Usage;
25
use Pod::Usage;
25
26
26
use C4::Context;
27
use Koha::Config;
27
use Koha::Z3950Responder;
28
use Koha::Z3950Responder;
28
29
29
=head1 SYNOPSIS
30
=head1 SYNOPSIS
Lines 31-37 use Koha::Z3950Responder; Link Here
31
   z3950_responder.pl [-h|--help] [--man] [-a <pdufile>] [-v <loglevel>] [-l <logfile>] [-u <user>]
32
   z3950_responder.pl [-h|--help] [--man] [-a <pdufile>] [-v <loglevel>] [-l <logfile>] [-u <user>]
32
                      [-c <config>] [-t <minutes>] [-k <kilobytes>] [-d <daemon>] [-p <pidfile>]
33
                      [-c <config>] [-t <minutes>] [-k <kilobytes>] [-d <daemon>] [-p <pidfile>]
33
                      [-C certfile] [-zKiDST1] [-m <time-format>] [-w <directory>] [--debug]
34
                      [-C certfile] [-zKiDST1] [-m <time-format>] [-w <directory>] [--debug]
34
                      [--add-item-status=SUBFIELD] [--prefetch=NUM_RECORDS]
35
                      [--add-item-status=SUBFIELD] [--prefetch=NUM_RECORDS] [--config-dir=<directory>]
35
                      [<listener-addr>... ]
36
                      [<listener-addr>... ]
36
37
37
=head1 OPTIONS
38
=head1 OPTIONS
Lines 51-57 Displays manual page and exits. Link Here
51
52
52
=item B<--debug>
53
=item B<--debug>
53
54
54
Turns on debug logging to the screen, and turns on single-process mode.
55
Turns on debug logging to the screen and the single-process mode.
55
56
56
=item B<--add-item-status=SUBFIELD>
57
=item B<--add-item-status=SUBFIELD>
57
58
Lines 64-70 status string. Link Here
64
65
65
=item B<--prefetch=NUM_RECORDS>
66
=item B<--prefetch=NUM_RECORDS>
66
67
67
Number of records to prefetch from Zebra. Defaults to 20.
68
Number of records to prefetch. Defaults to 20.
69
70
=item B<--config-dir=directory>
71
72
Directory where to find configuration files required for proper operation. Defaults to z3950 under
73
the Koha config directory.
68
74
69
=back
75
=back
70
76
Lines 101-106 my $debug = 0; Link Here
101
my $help;
107
my $help;
102
my $man;
108
my $man;
103
my $prefetch = 20;
109
my $prefetch = 20;
110
my $config_dir = '';
111
104
my @yaz_options;
112
my @yaz_options;
105
113
106
sub add_yaz_option {
114
sub add_yaz_option {
Lines 122-127 GetOptions( Link Here
122
    '--add-item-status=s' => \$add_item_status_subfield,
130
    '--add-item-status=s' => \$add_item_status_subfield,
123
    '--add-status-multi-subfield' => \$add_status_multi_subfield,
131
    '--add-status-multi-subfield' => \$add_status_multi_subfield,
124
    '--prefetch=i' => \$prefetch,
132
    '--prefetch=i' => \$prefetch,
133
    '--config-dir=s' => \$config_dir,
125
    # Pass through YAZ options.
134
    # Pass through YAZ options.
126
    'a=s' => \&add_yaz_option,
135
    'a=s' => \&add_yaz_option,
127
    'v=s' => \&add_yaz_option,
136
    'v=s' => \&add_yaz_option,
Lines 152-166 if (!@ARGV || $ARGV[-1] =~ /^-/) { Link Here
152
    push(@ARGV, '@:2100');
161
    push(@ARGV, '@:2100');
153
}
162
}
154
163
155
# Create and start the server.
164
# If config_dir is not defined, default to z3950 under the Koha config directory
165
if (!$config_dir) {
166
    (undef, $config_dir) = fileparse(Koha::Config->guess_koha_conf);
167
    $config_dir .= 'z3950/';
168
} else {
169
    $config_dir .= '/' if ($config_dir !~ /\/$/);
170
}
156
171
157
die "This tool only works with Zebra" if C4::Context->preference('SearchEngine') ne 'Zebra';
172
# Create and start the server.
158
173
159
my $z = Koha::Z3950Responder->new( {
174
my $z = Koha::Z3950Responder->new( {
160
    add_item_status_subfield => $add_item_status_subfield,
175
    add_item_status_subfield => $add_item_status_subfield,
161
    add_status_multi_subfield => $add_status_multi_subfield,
176
    add_status_multi_subfield => $add_status_multi_subfield,
162
    debug => $debug,
177
    debug => $debug,
163
    num_to_prefetch => $prefetch,
178
    num_to_prefetch => $prefetch,
179
    config_dir => $config_dir,
164
    yaz_options => [ @yaz_options, @ARGV ],
180
    yaz_options => [ @yaz_options, @ARGV ],
165
} );
181
} );
166
182
167
- 

Return to bug 13937