]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/support-scripts/marc_stream_importer.pl
Make GetOpts::Long and Cronscript get along
[Evergreen.git] / Open-ILS / src / support-scripts / marc_stream_importer.pl
1 #!/usr/bin/perl
2 # Copyright (C) 2008 Equinox Software, Inc.
3 # Author: Bill Erickson <erickson@esilibrary.com>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but 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
16 use strict; use warnings;
17 use Net::Server::PreFork;
18 use base qw/Net::Server::PreFork/;
19 use MARC::Record;
20 use MARC::Batch;
21 use MARC::File::XML;
22 use MARC::File::USMARC;
23
24 use Data::Dumper;
25 use File::Basename qw/fileparse/;
26 use Getopt::Long qw(:DEFAULT GetOptionsFromArray);
27 use Pod::Usage;
28
29 use OpenSRF::Utils::Logger qw/$logger/;
30 use OpenILS::Utils::Cronscript;
31 require 'oils_header.pl';
32 use vars qw/$apputils/;
33
34 my $debug = 0;
35
36 my %defaults = (
37     'buffsize=i'    => 4096,
38     'source=s'      => 1,
39     'osrf-config=s' => '/openils/conf/opensrf_core.xml',
40     'user=s'        => 'admin',
41     'password=s'    => '',
42     'nolockfile'    => 1,
43 );
44
45 $OpenILS::Utils::Cronscript::debug=1 if $debug;
46 $Getopt::Long::debug=1 if $debug > 1;
47 my $o = OpenILS::Utils::Cronscript->new(\%defaults);
48
49 my @script_args = ();
50
51 if (grep {$_ eq '--'} @ARGV) {
52     print "Splitting options into groups\n" if $debug;
53     while (@ARGV) {
54         $_ = shift @ARGV;
55         $_ eq '--' and last;    # stop at the first --
56         push @script_args, $_;
57     }
58 } else {
59     @script_args = @ARGV;
60 }
61
62 print "Calling MyGetOptions ",
63     (@script_args ? "with options: " . join(' ', @script_args) : 'without options from command line'),
64     "\n" if $debug;
65
66 my $real_opts = $o->MyGetOptions(\@script_args);
67 # GetOptionsFromArray(\@script_args, \%defaults, %defaults); # similar to
68
69 my $bufsize       = $real_opts->{buffsize};
70 my $bib_source    = $real_opts->{source};
71 my $osrf_config   = $real_opts->{'osrf-config'};
72 my $oils_username = $real_opts->{user};
73 my $oils_password = $real_opts->{password};
74 my $help          = $real_opts->{help};
75    $debug        += $real_opts->{debug};
76
77 foreach (keys %$real_opts) {
78     print("real_opt->{$_} = ", $real_opts->{$_}, "\n") if $real_opts->{debug} or $debug;
79 }
80 my $wait_time     = 5;
81 my $authtoken     = '';
82
83 # DEFAULTS for Net::Server
84 my $filename   = fileparse($0, '.pl');
85 my $conf_file  = (-r "$filename.conf") ? "$filename.conf" : undef;
86 # $conf_file is the Net::Server config for THIS script (not EG), if it exists and is readable
87
88
89 pod2usage(1) if $help;
90 unless ($oils_password) {
91     print STDERR "\nERROR: password option required for session login\n\n";
92     # pod2usage(1);
93 }
94
95 print Dumper($o) if $debug;
96
97 if ($debug) {
98     foreach my $ref (qw/bufsize bib_source osrf_config oils_username oils_password help conf_file debug/) {
99         no strict 'refs';
100         printf "%16s => %s\n", $ref, (eval("\$$ref") || '');
101     }
102 }
103
104 sub warning {
105     return <<WARNING;
106
107 WARNING:  This script provides no security layer.  Any client that has 
108 access to the server+port can inject MARC records into the system.  
109
110 WARNING
111 }
112
113 print warning();
114 print Dumper($real_opts);
115
116
117 sub xml_import {
118     return $apputils->simplereq(
119         'open-ils.cat', 
120         'open-ils.cat.biblio.record.xml.import',
121         @_
122     );
123 }
124
125 sub process_batch_data {
126     my $data = shift or $logger->error("process_batch_data called without any data");
127     $data or return;
128
129     my $handle;
130     open $handle, '<', \$data; 
131     my $batch = MARC::Batch->new('USMARC', $handle);
132     $batch->strict_off;
133
134     my $index = 0;
135     while(1) {
136
137         my $rec;
138         $index++;
139
140         eval { $rec = $batch->next; };
141
142         if ($@) {
143             $logger->error("Failed parsing MARC record $index");
144             next;
145         }
146
147         last unless $rec;
148
149         my $resp = xml_import($authtoken, $rec->as_xml_record, $bib_source);
150
151         # has the session timed out?
152         if (oils_event_equals($resp, 'NO_SESSION')) {
153             new_auth_token();
154             $resp = xml_import($authtoken, $rec->as_xml_record, $bib_source);   # try again w/ new token
155         }
156         oils_event_die($resp);
157     }
158     return $index;
159 }
160
161 sub process_request {   # The core Net::Server method
162     my $self = shift;
163     my $socket = $self->{server}->{client};
164     my $data = '';
165     my $buf;
166
167     # Reading <STDIN> blocks until the client is closed.  Instead of waiting 
168     # for that, give each inbound record $wait_time seconds to fully arrive
169     # and pull the data directly from the socket
170     eval {
171         local $SIG{ALRM} = sub { die "alarm\n" }; 
172         do {
173             alarm $wait_time;
174             last unless $socket->sysread($buf, $bufsize);
175             $data .= $buf;
176         } while(1);
177         alarm 0;
178     };
179     process_batch_data($data);
180 }
181
182
183 # the authtoken will timeout after the configured inactivity period.
184 # When that happens, get a new one.
185 sub new_auth_token {
186     $authtoken = oils_login($oils_username, $oils_password, 'staff') 
187         or die "Unable to login to Evergreen as user $oils_username";
188     return $authtoken;
189 }
190
191 ##### MAIN ######
192
193 osrf_connect($osrf_config);
194 new_auth_token();
195 print "Calling Net::Server run ", (@ARGV ? "with options: " . join(' ', @ARGV) : ''), "\n";
196 __PACKAGE__->run(conf_file => $conf_file);
197
198 __END__
199
200 =head1 NAME
201
202 marc_stream_importer.pl - Import MARC records via bare socket connection.
203
204 =head1 SYNOPSIS
205
206  ./marc_stream_importer.pl /openils/conf/opensrf_core.xml
207     --user=<eg_username>                       \
208     --pass=<eg_password> --source=<bib_source> \
209     -- --port=<port> --min_servers=2           \
210        --max_servers=20 --log_file=/openils/var/log/marc_net_importer.log &
211
212 Note the extra -- to separate options for the script wrapper from options for the
213 underlying Net::Server instance.  
214
215 Note: this script has to be run in the same directory as oils_header.pl
216
217 Run perldoc marc_stream_importer.pl for more documentation.
218
219 =head1 DESCRIPTION
220
221 This script is a Net::Server::PreFork instance for shoving records into Evergreen from a remote system.
222
223 =head2 Configuration
224
225 =head3 OCLC Connexion
226
227 To use this script with OCLC Connexion, configure the client as follows:
228
229 Under Tools -> Options -> Export (tab)
230    Create -> Choose Connection -> OK -> Leave translation at "None" 
231        -> Create -> Create -> choose TCP/IP (internet) 
232        -> Enter hostname and Port, leave 'Use Telnet Protocol' checked 
233        -> Create/OK your way out of the dialogs
234    Record Characteristics (button) -> Choose 'UTF-8 Unicode' for the Character Set
235    
236
237 OCLC and Connexion are trademark/service marks of OCLC Online Computer Library Center, Inc.
238
239 =head1 CAVEATS
240
241 WARNING: This script provides no inherent security layer.  Any client that has 
242 access to the server+port can inject MARC records into the system.  
243 Use the available options (like allow/deny) in the Net::Server config file 
244 or via the command line to restrict access as necessary.
245
246 =head1 EXAMPLES
247
248 ./marc_stream_importer.pl /openils/conf/opensrf_core.xml \
249     admin open-ils connexion --port 5555 --min_servers 2 \
250     --max_servers 20 --log_file /openils/var/log/marc_net_importer.log &
251
252 =head1 SEE ALSO
253
254 L<Net::Server::PreFork>, L<marc_stream_importer.conf>
255
256 =head1 AUTHORS
257
258     Bill Erickson <erickson@esilibrary.com>
259     Joe Atzberger <jatzberger@esilibrary.com>
260
261
262 =cut