b72ab2c16d0399696378722ca90682efe0db0b7d
[koha-equinox.git] / misc / migration_tools / rebuild_zebra.pl
1 #!/usr/bin/perl
2
3 use strict;
4 #use warnings; FIXME - Bug 2505
5
6 use C4::Context;
7 use Getopt::Long;
8 use File::Temp qw/ tempdir /;
9 use File::Path;
10 use C4::Biblio;
11 use C4::AuthoritiesMarc;
12 use C4::Items;
13 use Koha::RecordProcessor;
14
15
16 # script that checks zebradir structure & create directories & mandatory files if needed
17 #
18 #
19
20 $|=1; # flushes output
21 # If the cron job starts us in an unreadable dir, we will break without
22 # this.
23 chdir $ENV{HOME} if (!(-r '.'));
24 my $directory;
25 my $nosanitize;
26 my $skip_export;
27 my $keep_export;
28 my $skip_index;
29 my $reset;
30 my $biblios;
31 my $authorities;
32 my $noxml;
33 my $noshadow;
34 my $do_munge;
35 my $want_help;
36 my $as_xml;
37 my $process_zebraqueue;
38 my $do_not_clear_zebraqueue;
39 my $length;
40 my $where;
41 my $offset;
42 my $verbose_logging = 0;
43 my $zebraidx_log_opt = " -v none,fatal,warn ";
44 my $result = GetOptions(
45     'd:s'           => \$directory,
46     'r|reset'       => \$reset,
47     's'             => \$skip_export,
48     'k'             => \$keep_export,
49     'I|skip-index'    => \$skip_index,
50     'nosanitize'    => \$nosanitize,
51     'b'             => \$biblios,
52     'noxml'         => \$noxml,
53     'w'             => \$noshadow,
54     'munge-config'  => \$do_munge,
55     'a'             => \$authorities,
56     'h|help'        => \$want_help,
57         'x'                             => \$as_xml,
58     'y'             => \$do_not_clear_zebraqueue,
59     'z'             => \$process_zebraqueue,
60     'where:s'        => \$where,
61     'length:i'        => \$length,
62     'offset:i'      => \$offset,
63     'v+'             => \$verbose_logging,
64 );
65
66
67 if (not $result or $want_help) {
68     print_usage();
69     exit 0;
70 }
71
72 if (not $biblios and not $authorities) {
73     my $msg = "Must specify -b or -a to reindex bibs or authorities\n";
74     $msg   .= "Please do '$0 --help' to see usage.\n";
75     die $msg;
76 }
77
78 if ( !$as_xml and $nosanitize ) {
79     my $msg = "Cannot specify both -no_xml and -nosanitize\n";
80     $msg   .= "Please do '$0 --help' to see usage.\n";
81     die $msg;
82 }
83
84 if ($process_zebraqueue and ($skip_export or $reset)) {
85     my $msg = "Cannot specify -r or -s if -z is specified\n";
86     $msg   .= "Please do '$0 --help' to see usage.\n";
87     die $msg;
88 }
89
90 if ($process_zebraqueue and $do_not_clear_zebraqueue) {
91     my $msg = "Cannot specify both -y and -z\n";
92     $msg   .= "Please do '$0 --help' to see usage.\n";
93     die $msg;
94 }
95
96 if ($reset) {
97     $noshadow = 1;
98 }
99
100 if ($noshadow) {
101     $noshadow = ' -n ';
102 }
103
104 #  -v is for verbose, which seems backwards here because of how logging is set
105 #    on the CLI of zebraidx.  It works this way.  The default is to not log much
106 if ($verbose_logging >= 2) {
107     $zebraidx_log_opt = '-v none,fatal,warn,all';
108 }
109
110 my $use_tempdir = 0;
111 unless ($directory) {
112     $use_tempdir = 1;
113     $directory = tempdir(CLEANUP => ($keep_export ? 0 : 1));
114
115
116
117 my $biblioserverdir = C4::Context->zebraconfig('biblioserver')->{directory};
118 my $authorityserverdir = C4::Context->zebraconfig('authorityserver')->{directory};
119
120 my $kohadir = C4::Context->config('intranetdir');
121 my $bib_index_mode = C4::Context->config('zebra_bib_index_mode') || 'grs1';
122 my $auth_index_mode = C4::Context->config('zebra_auth_index_mode') || 'dom';
123
124 my $dbh = C4::Context->dbh;
125 my ($biblionumbertagfield,$biblionumbertagsubfield) = &GetMarcFromKohaField("biblio.biblionumber","");
126 my ($biblioitemnumbertagfield,$biblioitemnumbertagsubfield) = &GetMarcFromKohaField("biblioitems.biblioitemnumber","");
127
128 if ( $verbose_logging ) {
129     print "Zebra configuration information\n";
130     print "================================\n";
131     print "Zebra biblio directory      = $biblioserverdir\n";
132     print "Zebra authorities directory = $authorityserverdir\n";
133     print "Koha directory              = $kohadir\n";
134     print "BIBLIONUMBER in :     $biblionumbertagfield\$$biblionumbertagsubfield\n";
135     print "BIBLIOITEMNUMBER in : $biblioitemnumbertagfield\$$biblioitemnumbertagsubfield\n";
136     print "================================\n";
137 }
138
139 if ($do_munge) {
140     munge_config();
141 }
142
143 if ($authorities) {
144     index_records('authority', $directory, $skip_export, $skip_index, $process_zebraqueue, $as_xml, $noxml, $nosanitize, $do_not_clear_zebraqueue, $verbose_logging, $zebraidx_log_opt, $authorityserverdir);
145 } else {
146     print "skipping authorities\n" if ( $verbose_logging );
147 }
148
149 if ($biblios) {
150     index_records('biblio', $directory, $skip_export, $skip_index, $process_zebraqueue, $as_xml, $noxml, $nosanitize, $do_not_clear_zebraqueue, $verbose_logging, $zebraidx_log_opt, $biblioserverdir);
151 } else {
152     print "skipping biblios\n" if ( $verbose_logging );
153 }
154
155
156 if ( $verbose_logging ) {
157     print "====================\n";
158     print "CLEANING\n";
159     print "====================\n";
160 }
161 if ($keep_export) {
162     print "NOTHING cleaned : the export $directory has been kept.\n";
163     print "You can re-run this script with the -s ";
164     if ($use_tempdir) {
165         print " and -d $directory parameters";
166     } else {
167         print "parameter";
168     }
169     print "\n";
170     print "if you just want to rebuild zebra after changing the record.abs\n";
171     print "or another zebra config file\n";
172 } else {
173     unless ($use_tempdir) {
174         # if we're using a temporary directory
175         # created by File::Temp, it will be removed
176         # automatically.
177         rmtree($directory, 0, 1);
178         print "directory $directory deleted\n";
179     }
180 }
181
182 # This checks to see if the zebra directories exist under the provided path.
183 # If they don't, then zebra is likely to spit the dummy. This returns true
184 # if the directories had to be created, false otherwise.
185 sub check_zebra_dirs {
186         my ($base) = shift() . '/';
187         my $needed_repairing = 0;
188         my @dirs = ( '', 'key', 'register', 'shadow', 'tmp' );
189         foreach my $dir (@dirs) {
190                 my $bdir = $base . $dir;
191         if (! -d $bdir) {
192                 $needed_repairing = 1;
193                 mkdir $bdir || die "Unable to create '$bdir': $!\n";
194                 print "$0: needed to create '$bdir'\n";
195         }
196     }
197     return $needed_repairing;
198 }       # ----------  end of subroutine check_zebra_dirs  ----------
199
200 sub index_records {
201     my ($record_type, $directory, $skip_export, $skip_index, $process_zebraqueue, $as_xml, $noxml, $nosanitize, $do_not_clear_zebraqueue, $verbose_logging, $zebraidx_log_opt, $server_dir) = @_;
202
203     my $num_records_exported = 0;
204     my $records_deleted;
205     my $need_reset = check_zebra_dirs($server_dir);
206     if ($need_reset) {
207         print "$0: found broken zebra server directories: forcing a rebuild\n";
208         $reset = 1;
209     }
210     if ($skip_export && $verbose_logging) {
211         print "====================\n";
212         print "SKIPPING $record_type export\n";
213         print "====================\n";
214     } else {
215         if ( $verbose_logging ) {
216             print "====================\n";
217             print "exporting $record_type\n";
218             print "====================\n";
219         }
220         mkdir "$directory" unless (-d $directory);
221         mkdir "$directory/$record_type" unless (-d "$directory/$record_type");
222         if ($process_zebraqueue) {
223             my $entries = select_zebraqueue_records($record_type, 'deleted');
224             mkdir "$directory/del_$record_type" unless (-d "$directory/del_$record_type");
225             $records_deleted = generate_deleted_marc_records($record_type, $entries, "$directory/del_$record_type", $as_xml);
226             mark_zebraqueue_batch_done($entries);
227             $entries = select_zebraqueue_records($record_type, 'updated');
228             mkdir "$directory/upd_$record_type" unless (-d "$directory/upd_$record_type");
229             $num_records_exported = export_marc_records_from_list($record_type, 
230                                                                   $entries, "$directory/upd_$record_type", $as_xml, $noxml, $records_deleted);
231             mark_zebraqueue_batch_done($entries);
232         } else {
233             my $sth = select_all_records($record_type);
234             $num_records_exported = export_marc_records_from_sth($record_type, $sth, "$directory/$record_type", $as_xml, $noxml, $nosanitize);
235             unless ($do_not_clear_zebraqueue) {
236                 mark_all_zebraqueue_done($record_type);
237             }
238         }
239     }
240
241     #
242     # and reindexing everything
243     #
244     if ($skip_index) {
245         if ($verbose_logging) {
246             print "====================\n";
247             print "SKIPPING $record_type indexing\n";
248             print "====================\n";
249         }
250     } else {
251         if ( $verbose_logging ) {
252             print "====================\n";
253             print "REINDEXING zebra\n";
254             print "====================\n";
255         }
256         my $record_fmt = ($as_xml) ? 'marcxml' : 'iso2709' ;
257         if ($process_zebraqueue) {
258             do_indexing($record_type, 'adelete', "$directory/del_$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt)
259                 if %$records_deleted;
260             do_indexing($record_type, 'update', "$directory/upd_$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt)
261                 if $num_records_exported;
262         } else {
263             do_indexing($record_type, 'update', "$directory/$record_type", $reset, $noshadow, $record_fmt, $zebraidx_log_opt)
264                 if ($num_records_exported or $skip_export);
265         }
266     }
267 }
268
269
270 sub select_zebraqueue_records {
271     my ($record_type, $update_type) = @_;
272
273     my $server = ($record_type eq 'biblio') ? 'biblioserver' : 'authorityserver';
274     my $op = ($update_type eq 'deleted') ? 'recordDelete' : 'specialUpdate';
275
276     my $sth = $dbh->prepare("SELECT id, biblio_auth_number 
277                              FROM zebraqueue
278                              WHERE server = ?
279                              AND   operation = ?
280                              AND   done = 0
281                              ORDER BY id DESC");
282     $sth->execute($server, $op);
283     my $entries = $sth->fetchall_arrayref({});
284 }
285
286 sub mark_all_zebraqueue_done {
287     my ($record_type) = @_;
288
289     my $server = ($record_type eq 'biblio') ? 'biblioserver' : 'authorityserver';
290
291     my $sth = $dbh->prepare("UPDATE zebraqueue SET done = 1
292                              WHERE server = ?
293                              AND done = 0");
294     $sth->execute($server);
295 }
296
297 sub mark_zebraqueue_batch_done {
298     my ($entries) = @_;
299
300     $dbh->{AutoCommit} = 0;
301     my $sth = $dbh->prepare("UPDATE zebraqueue SET done = 1 WHERE id = ?");
302     $dbh->commit();
303     foreach my $id (map { $_->{id} } @$entries) {
304         $sth->execute($id);
305     }
306     $dbh->{AutoCommit} = 1;
307 }
308
309 sub select_all_records {
310     my $record_type = shift;
311     return ($record_type eq 'biblio') ? select_all_biblios() : select_all_authorities();
312 }
313
314 sub select_all_authorities {
315     my $strsth=qq{SELECT authid FROM auth_header};
316     $strsth.=qq{ WHERE $where } if ($where);
317     $strsth.=qq{ LIMIT $length } if ($length && !$offset);
318     $strsth.=qq{ LIMIT $offset,$length } if ($length && $offset);
319     my $sth = $dbh->prepare($strsth);
320     $sth->execute();
321     return $sth;
322 }
323
324 sub select_all_biblios {
325     my $strsth = qq{ SELECT biblionumber FROM biblioitems };
326     $strsth.=qq{ WHERE $where } if ($where);
327     $strsth.=qq{ LIMIT $length } if ($length && !$offset);
328     $strsth.=qq{ LIMIT $offset,$length } if ($offset);
329     my $sth = $dbh->prepare($strsth);
330     $sth->execute();
331     return $sth;
332 }
333
334 sub include_xml_wrapper {
335     my $as_xml = shift;
336     my $record_type = shift;
337
338     return 0 unless $as_xml;
339     return 1 if $record_type eq 'biblio' and $bib_index_mode eq 'dom';
340     return 1 if $record_type eq 'authority' and $auth_index_mode eq 'dom';
341     return 0;
342
343 }
344
345 sub export_marc_records_from_sth {
346     my ($record_type, $sth, $directory, $as_xml, $noxml, $nosanitize) = @_;
347
348     my $num_exported = 0;
349     open my $fh, '>:encoding(UTF-8) ', "$directory/exported_records" or die $!;
350     if (include_xml_wrapper($as_xml, $record_type)) {
351         # include XML declaration and root element
352         print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>';
353     }
354     my $i = 0;
355     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",'');
356     while (my ($record_number) = $sth->fetchrow_array) {
357         print "." if ( $verbose_logging );
358         print "\r$i" unless ($i++ %100 or !$verbose_logging);
359         if ( $nosanitize ) {
360             my $marcxml = $record_type eq 'biblio'
361                           ? GetXmlBiblio( $record_number )
362                           : GetAuthorityXML( $record_number );
363             if ($record_type eq 'biblio'){
364                 my @items = GetItemsInfo($record_number);
365                 if (@items){
366                     my $record = MARC::Record->new;
367                     $record->encoding('UTF-8');
368                     my @itemsrecord;
369                     foreach my $item (@items){
370                         my $record = Item2Marc($item, $record_number);                        
371                         push @itemsrecord, $record->field($itemtag);
372                     }
373                     $record->insert_fields_ordered(@itemsrecord);
374                     my $itemsxml = $record->as_xml_record();
375                     $marcxml =
376                         substr($marcxml, 0, length($marcxml)-10) .
377                         substr($itemsxml, index($itemsxml, "</leader>\n", 0) + 10);
378                 }
379             }
380             if ( $marcxml ) {
381                 print {$fh} $marcxml if $marcxml;
382                 $num_exported++;
383             }
384             next;
385         }
386         my ($marc) = get_corrected_marc_record($record_type, $record_number, $noxml);
387         if (defined $marc) {
388             eval {
389                 my $rec;
390                 if ($as_xml) {
391                     $rec = $marc->as_xml_record(C4::Context->preference('marcflavour'));
392                     $rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!;
393                 } else {
394                     $rec = $marc->as_usmarc();
395                 }
396                 print {$fh} $rec;
397                 $num_exported++;
398             };
399             if ($@) {
400               warn "Error exporting record $record_number ($record_type) ".($noxml ? "not XML" : "XML");
401             }
402         }
403     }
404     print "\nRecords exported: $num_exported\n" if ( $verbose_logging );
405     print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type));
406     close $fh;
407     return $num_exported;
408 }
409
410 sub export_marc_records_from_list {
411     my ($record_type, $entries, $directory, $as_xml, $noxml, $records_deleted) = @_;
412
413     my $num_exported = 0;
414     open my $fh, '>:encoding(UTF-8)', "$directory/exported_records" or die $!;
415     if (include_xml_wrapper($as_xml, $record_type)) {
416         # include XML declaration and root element
417         print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>';
418     }
419     my $i = 0;
420
421     # Skip any deleted records. We check for this anyway, but this reduces error spam
422     my %found = %$records_deleted;
423     foreach my $record_number ( map { $_->{biblio_auth_number} }
424                                 grep { !$found{ $_->{biblio_auth_number} }++ }
425                                 @$entries ) {
426         print "." if ( $verbose_logging );
427         print "\r$i" unless ($i++ %100 or !$verbose_logging);
428         my ($marc) = get_corrected_marc_record($record_type, $record_number, $noxml);
429         if (defined $marc) {
430             eval {
431                 my $rec;
432                 if ($as_xml) {
433                     $rec = $marc->as_xml_record(C4::Context->preference('marcflavour'));
434                     $rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!;
435                 } else {
436                     $rec = $marc->as_usmarc();
437                 }
438                 print {$fh} $rec;
439                 $num_exported++;
440             };
441             if ($@) {
442               warn "Error exporting record $record_number ($record_type) ".($noxml ? "not XML" : "XML");
443             }
444             $num_exported++;
445         }
446     }
447     print "\nRecords exported: $num_exported\n" if ( $verbose_logging );
448     print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type));
449     close $fh;
450     return $num_exported;
451 }
452
453 sub generate_deleted_marc_records {
454     my ($record_type, $entries, $directory, $as_xml) = @_;
455
456     my $records_deleted = {};
457     open my $fh, '>:encoding(UTF-8)', "$directory/exported_records" or die $!;
458     if (include_xml_wrapper($as_xml, $record_type)) {
459         # include XML declaration and root element
460         print {$fh} '<?xml version="1.0" encoding="UTF-8"?><collection>';
461     }
462     my $i = 0;
463     foreach my $record_number (map { $_->{biblio_auth_number} } @$entries ) {
464         print "\r$i" unless ($i++ %100 or !$verbose_logging);
465         print "." if ( $verbose_logging );
466
467         my $marc = MARC::Record->new();
468         if ($record_type eq 'biblio') {
469             fix_biblio_ids($marc, $record_number, $record_number);
470         } else {
471             fix_authority_id($marc, $record_number);
472         }
473         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
474             fix_unimarc_100($marc);
475         }
476
477         my $rec;
478         if ($as_xml) {
479             $rec = $marc->as_xml_record(C4::Context->preference('marcflavour'));
480             $rec =~ s!<\?xml version="1.0" encoding="UTF-8"\?>\n!!;
481         } else {
482             $rec = $marc->as_usmarc();
483         }
484         print {$fh} $rec;
485
486         $records_deleted->{$record_number} = 1;
487     }
488     print "\nRecords exported: $i\n" if ( $verbose_logging );
489     print {$fh} '</collection>' if (include_xml_wrapper($as_xml, $record_type));
490     close $fh;
491     return $records_deleted;
492     
493
494 }
495
496 sub get_corrected_marc_record {
497     my ($record_type, $record_number, $noxml) = @_;
498
499     my $marc = get_raw_marc_record($record_type, $record_number, $noxml); 
500
501     if (defined $marc) {
502         fix_leader($marc);
503         if ($record_type eq 'authority') {
504             fix_authority_id($marc, $record_number);
505         } elsif ($record_type eq 'biblio' && C4::Context->preference('IncludeSeeFromInSearches')) {
506             my $normalizer = Koha::RecordProcessor->new( { filters => 'EmbedSeeFromHeadings' } );
507             $marc = $normalizer->process($marc);
508         }
509         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
510             fix_unimarc_100($marc);
511         }
512     }
513
514     return $marc;
515 }
516
517 sub get_raw_marc_record {
518     my ($record_type, $record_number, $noxml) = @_;
519   
520     my $marc; 
521     if ($record_type eq 'biblio') {
522         if ($noxml) {
523             my $fetch_sth = $dbh->prepare_cached("SELECT marc FROM biblioitems WHERE biblionumber = ?");
524             $fetch_sth->execute($record_number);
525             if (my ($blob) = $fetch_sth->fetchrow_array) {
526                 $marc = MARC::Record->new_from_usmarc($blob);
527                 unless ($marc) {
528                     warn "error creating MARC::Record from $blob";
529                 }
530             }
531             # failure to find a bib is not a problem -
532             # a delete could have been done before
533             # trying to process a record update
534
535             $fetch_sth->finish();
536             return unless $marc;
537         } else {
538             eval { $marc = GetMarcBiblio($record_number, 1); };
539             if ($@ || !$marc) {
540                 # here we do warn since catching an exception
541                 # means that the bib was found but failed
542                 # to be parsed
543                 warn "error retrieving biblio $record_number";
544                 return;
545             }
546         }
547     } else {
548         eval { $marc = GetAuthority($record_number); };
549         if ($@) {
550             warn "error retrieving authority $record_number";
551             return;
552         }
553     }
554     return $marc;
555 }
556
557 sub fix_leader {
558     # FIXME - this routine is suspect
559     # It blanks the Leader/00-05 and Leader/12-16 to
560     # force them to be recalculated correct when
561     # the $marc->as_usmarc() or $marc->as_xml() is called.
562     # But why is this necessary?  It would be a serious bug
563     # in MARC::Record (definitely) and MARC::File::XML (arguably) 
564     # if they are emitting incorrect leader values.
565     my $marc = shift;
566
567     my $leader = $marc->leader;
568     substr($leader,  0, 5) = '     ';
569     substr($leader, 10, 7) = '22     ';
570     $marc->leader(substr($leader, 0, 24));
571 }
572
573 sub fix_biblio_ids {
574     # FIXME - it is essential to ensure that the biblionumber is present,
575     #         otherwise, Zebra will choke on the record.  However, this
576     #         logic belongs in the relevant C4::Biblio APIs.
577     my $marc = shift;
578     my $biblionumber = shift;
579     my $biblioitemnumber;
580     if (@_) {
581         $biblioitemnumber = shift;
582     } else {    
583         my $sth = $dbh->prepare(
584             "SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
585         $sth->execute($biblionumber);
586         ($biblioitemnumber) = $sth->fetchrow_array;
587         $sth->finish;
588         unless ($biblioitemnumber) {
589             warn "failed to get biblioitemnumber for biblio $biblionumber";
590             return 0;
591         }
592     }
593
594     # FIXME - this is cheating on two levels
595     # 1. C4::Biblio::_koha_marc_update_bib_ids is meant to be an internal function
596     # 2. Making sure that the biblionumber and biblioitemnumber are correct and
597     #    present in the MARC::Record object ought to be part of GetMarcBiblio.
598     #
599     # On the other hand, this better for now than what rebuild_zebra.pl used to
600     # do, which was duplicate the code for inserting the biblionumber 
601     # and biblioitemnumber
602     C4::Biblio::_koha_marc_update_bib_ids($marc, '', $biblionumber, $biblioitemnumber);
603
604     return 1;
605 }
606
607 sub fix_authority_id {
608     # FIXME - as with fix_biblio_ids, the authid must be present
609     #         for Zebra's sake.  However, this really belongs
610     #         in C4::AuthoritiesMarc.
611     my ($marc, $authid) = @_;
612     unless ($marc->field('001') and $marc->field('001')->data() eq $authid){
613         $marc->delete_field($marc->field('001'));
614         $marc->insert_fields_ordered(MARC::Field->new('001',$authid));
615     }
616 }
617
618 sub fix_unimarc_100 {
619     # FIXME - again, if this is necessary, it belongs in C4::AuthoritiesMarc.
620     my $marc = shift;
621
622     my $string;
623     if ( length($marc->subfield( 100, "a" )) == 36 ) {
624         $string = $marc->subfield( 100, "a" );
625         my $f100 = $marc->field(100);
626         $marc->delete_field($f100);
627     }
628     else {
629         $string = POSIX::strftime( "%Y%m%d", localtime );
630         $string =~ s/\-//g;
631         $string = sprintf( "%-*s", 35, $string );
632     }
633     substr( $string, 22, 6, "frey50" );
634     unless ( length($marc->subfield( 100, "a" )) == 36 ) {
635         $marc->delete_field($marc->field(100));
636         $marc->insert_grouped_field(MARC::Field->new( 100, "", "", "a" => $string ));
637     }
638 }
639
640 sub do_indexing {
641     my ($record_type, $op, $record_dir, $reset_index, $noshadow, $record_format, $zebraidx_log_opt) = @_;
642
643     my $zebra_server  = ($record_type eq 'biblio') ? 'biblioserver' : 'authorityserver';
644     my $zebra_db_name = ($record_type eq 'biblio') ? 'biblios' : 'authorities';
645     my $zebra_config  = C4::Context->zebraconfig($zebra_server)->{'config'};
646     my $zebra_db_dir  = C4::Context->zebraconfig($zebra_server)->{'directory'};
647
648     system("zebraidx -c $zebra_config $zebraidx_log_opt -g $record_format -d $zebra_db_name init") if $reset_index;
649     system("zebraidx -c $zebra_config $zebraidx_log_opt $noshadow -g $record_format -d $zebra_db_name $op $record_dir");
650     system("zebraidx -c $zebra_config $zebraidx_log_opt -g $record_format -d $zebra_db_name commit") unless $noshadow;
651
652 }
653
654 sub print_usage {
655     print <<_USAGE_;
656 $0: reindex MARC bibs and/or authorities in Zebra.
657
658 Use this batch job to reindex all biblio or authority
659 records in your Koha database.  This job is useful
660 only if you are using Zebra; if you are using the 'NoZebra'
661 mode, this job should not be used.
662
663 Parameters:
664     -b                      index bibliographic records
665
666     -a                      index authority records
667
668     -z                      select only updated and deleted
669                             records marked in the zebraqueue
670                             table.  Cannot be used with -r
671                             or -s.
672
673     -r                      clear Zebra index before
674                             adding records to index. Implies -w.
675
676     -d                      Temporary directory for indexing.
677                             If not specified, one is automatically
678                             created.  The export directory
679                             is automatically deleted unless
680                             you supply the -k switch.
681
682     -k                      Do not delete export directory.
683
684     -s                      Skip export.  Used if you have
685                             already exported the records 
686                             in a previous run.
687
688     -noxml                  index from ISO MARC blob
689                             instead of MARC XML.  This
690                             option is recommended only
691                             for advanced user.
692
693     -x                      export and index as xml instead of is02709 (biblios only).
694                             use this if you might have records > 99,999 chars,
695                                                         
696     -nosanitize             export biblio/authority records directly from DB marcxml
697                             field without sanitizing records. It speed up
698                             dump process but could fail if DB contains badly
699                             encoded records. Works only with -x,
700
701     -w                      skip shadow indexing for this batch
702
703     -y                      do NOT clear zebraqueue after indexing; normally,
704                             after doing batch indexing, zebraqueue should be
705                             marked done for the affected record type(s) so that
706                             a running zebraqueue_daemon doesn't try to reindex
707                             the same records - specify -y to override this.  
708                             Cannot be used with -z.
709
710     -v                      increase the amount of logging.  Normally only 
711                             warnings and errors from the indexing are shown.
712                             Use log level 2 (-v -v) to include all Zebra logs.
713
714     --length   1234         how many biblio you want to export
715     --offset 1243           offset you want to start to
716                                 example: --offset 500 --length=500 will result in a LIMIT 500,1000 (exporting 1000 records, starting by the 500th one)
717                                 note that the numbers are NOT related to biblionumber, that's the intended behaviour.
718     --where                 let you specify a WHERE query, like itemtype='BOOK'
719                             or something like that
720
721     --munge-config          Deprecated option to try
722                             to fix Zebra config files.
723     --help or -h            show this message.
724 _USAGE_
725 }
726
727 # FIXME: the following routines are deprecated and 
728 # will be removed once it is determined whether
729 # a script to fix Zebra configuration files is 
730 # actually needed.
731 sub munge_config {
732 #
733 # creating zebra-biblios.cfg depending on system
734 #
735
736 # getting zebraidx directory
737 my $zebraidxdir;
738 foreach (qw(/usr/local/bin/zebraidx
739         /opt/bin/zebraidx
740         /usr/bin/zebraidx
741         )) {
742     if ( -f $_ ) {
743         $zebraidxdir=$_;
744     }
745 }
746
747 unless ($zebraidxdir) {
748     print qq|
749     ERROR: could not find zebraidx directory
750     ERROR: Either zebra is not installed,
751     ERROR: or it's in a directory I don't checked.
752     ERROR: do a which zebraidx and edit this file to add the result you get
753 |;
754     exit;
755 }
756 $zebraidxdir =~ s/\/bin\/.*//;
757 print "Info : zebra is in $zebraidxdir \n";
758
759 # getting modules directory
760 my $modulesdir;
761 foreach (qw(/usr/local/lib/idzebra-2.0/modules/mod-grs-xml.so
762             /usr/local/lib/idzebra/modules/mod-grs-xml.so
763             /usr/lib/idzebra/modules/mod-grs-xml.so
764             /usr/lib/idzebra-2.0/modules/mod-grs-xml.so
765         )) {
766     if ( -f $_ ) {
767         $modulesdir=$_;
768     }
769 }
770
771 unless ($modulesdir) {
772     print qq|
773     ERROR: could not find mod-grs-xml.so directory
774     ERROR: Either zebra is not properly compiled (libxml2 is not setup and you don t have mod-grs-xml.so,
775     ERROR: or it's in a directory I don't checked.
776     ERROR: find where mod-grs-xml.so is and edit this file to add the result you get
777 |;
778     exit;
779 }
780 $modulesdir =~ s/\/modules\/.*//;
781 print "Info: zebra modules dir : $modulesdir\n";
782
783 # getting tab directory
784 my $tabdir;
785 foreach (qw(/usr/local/share/idzebra/tab/explain.att
786             /usr/local/share/idzebra-2.0/tab/explain.att
787             /usr/share/idzebra/tab/explain.att
788             /usr/share/idzebra-2.0/tab/explain.att
789         )) {
790     if ( -f $_ ) {
791         $tabdir=$_;
792     }
793 }
794
795 unless ($tabdir) {
796     print qq|
797     ERROR: could not find explain.att directory
798     ERROR: Either zebra is not properly compiled,
799     ERROR: or it's in a directory I don't checked.
800     ERROR: find where explain.att is and edit this file to add the result you get
801 |;
802     exit;
803 }
804 $tabdir =~ s/\/tab\/.*//;
805 print "Info: tab dir : $tabdir\n";
806
807 #
808 # AUTHORITIES creating directory structure
809 #
810 my $created_dir_or_file = 0;
811 if ($authorities) {
812     if ( $verbose_logging ) {
813         print "====================\n";
814         print "checking directories & files for authorities\n";
815         print "====================\n";
816     }
817     unless (-d "$authorityserverdir") {
818         system("mkdir -p $authorityserverdir");
819         print "Info: created $authorityserverdir\n";
820         $created_dir_or_file++;
821     }
822     unless (-d "$authorityserverdir/lock") {
823         mkdir "$authorityserverdir/lock";
824         print "Info: created $authorityserverdir/lock\n";
825         $created_dir_or_file++;
826     }
827     unless (-d "$authorityserverdir/register") {
828         mkdir "$authorityserverdir/register";
829         print "Info: created $authorityserverdir/register\n";
830         $created_dir_or_file++;
831     }
832     unless (-d "$authorityserverdir/shadow") {
833         mkdir "$authorityserverdir/shadow";
834         print "Info: created $authorityserverdir/shadow\n";
835         $created_dir_or_file++;
836     }
837     unless (-d "$authorityserverdir/tab") {
838         mkdir "$authorityserverdir/tab";
839         print "Info: created $authorityserverdir/tab\n";
840         $created_dir_or_file++;
841     }
842     unless (-d "$authorityserverdir/key") {
843         mkdir "$authorityserverdir/key";
844         print "Info: created $authorityserverdir/key\n";
845         $created_dir_or_file++;
846     }
847     
848     unless (-d "$authorityserverdir/etc") {
849         mkdir "$authorityserverdir/etc";
850         print "Info: created $authorityserverdir/etc\n";
851         $created_dir_or_file++;
852     }
853     
854     #
855     # AUTHORITIES : copying mandatory files
856     #
857     # the record model, depending on marc flavour
858     unless (-f "$authorityserverdir/tab/record.abs") {
859         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
860             system("cp -f $kohadir/etc/zebradb/marc_defs/unimarc/authorities/record.abs $authorityserverdir/tab/record.abs");
861             print "Info: copied record.abs for UNIMARC\n";
862         } else {
863             system("cp -f $kohadir/etc/zebradb/marc_defs/marc21/authorities/record.abs $authorityserverdir/tab/record.abs");
864             print "Info: copied record.abs for USMARC\n";
865         }
866         $created_dir_or_file++;
867     }
868     unless (-f "$authorityserverdir/tab/sort-string-utf.chr") {
869         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $authorityserverdir/tab/sort-string-utf.chr");
870         print "Info: copied sort-string-utf.chr\n";
871         $created_dir_or_file++;
872     }
873     unless (-f "$authorityserverdir/tab/word-phrase-utf.chr") {
874         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $authorityserverdir/tab/word-phrase-utf.chr");
875         print "Info: copied word-phase-utf.chr\n";
876         $created_dir_or_file++;
877     }
878     unless (-f "$authorityserverdir/tab/auth1.att") {
879         system("cp -f $kohadir/etc/zebradb/authorities/etc/bib1.att $authorityserverdir/tab/auth1.att");
880         print "Info: copied auth1.att\n";
881         $created_dir_or_file++;
882     }
883     unless (-f "$authorityserverdir/tab/default.idx") {
884         system("cp -f $kohadir/etc/zebradb/etc/default.idx $authorityserverdir/tab/default.idx");
885         print "Info: copied default.idx\n";
886         $created_dir_or_file++;
887     }
888     
889     unless (-f "$authorityserverdir/etc/ccl.properties") {
890 #         system("cp -f $kohadir/etc/zebradb/ccl.properties ".C4::Context->zebraconfig('authorityserver')->{ccl2rpn});
891         system("cp -f $kohadir/etc/zebradb/ccl.properties $authorityserverdir/etc/ccl.properties");
892         print "Info: copied ccl.properties\n";
893         $created_dir_or_file++;
894     }
895     unless (-f "$authorityserverdir/etc/pqf.properties") {
896 #         system("cp -f $kohadir/etc/zebradb/pqf.properties ".C4::Context->zebraconfig('authorityserver')->{ccl2rpn});
897         system("cp -f $kohadir/etc/zebradb/pqf.properties $authorityserverdir/etc/pqf.properties");
898         print "Info: copied pqf.properties\n";
899         $created_dir_or_file++;
900     }
901     
902     #
903     # AUTHORITIES : copying mandatory files
904     #
905     unless (-f C4::Context->zebraconfig('authorityserver')->{config}) {
906     open my $zd, '>:encoding(UTF-8)' ,C4::Context->zebraconfig('authorityserver')->{config};
907     print {$zd} "
908 # generated by KOHA/misc/migration_tools/rebuild_zebra.pl 
909 profilePath:\${srcdir:-.}:$authorityserverdir/tab/:$tabdir/tab/:\${srcdir:-.}/tab/
910
911 encoding: UTF-8
912 # Files that describe the attribute sets supported.
913 attset: auth1.att
914 attset: explain.att
915 attset: gils.att
916
917 modulePath:$modulesdir/modules/
918 # Specify record type
919 iso2709.recordType:grs.marcxml.record
920 recordType:grs.xml
921 recordId: (auth1,Local-Number)
922 storeKeys:1
923 storeData:1
924
925
926 # Lock File Area
927 lockDir: $authorityserverdir/lock
928 perm.anonymous:r
929 perm.kohaadmin:rw
930 register: $authorityserverdir/register:4G
931 shadow: $authorityserverdir/shadow:4G
932
933 # Temp File area for result sets
934 setTmpDir: $authorityserverdir/tmp
935
936 # Temp File area for index program
937 keyTmpDir: $authorityserverdir/key
938
939 # Approx. Memory usage during indexing
940 memMax: 40M
941 rank:rank-1
942     ";
943         print "Info: creating zebra-authorities.cfg\n";
944         $created_dir_or_file++;
945     }
946     
947     if ($created_dir_or_file) {
948         print "Info: created : $created_dir_or_file directories & files\n";
949     } else {
950         print "Info: file & directories OK\n";
951     }
952     
953 }
954 if ($biblios) {
955     if ( $verbose_logging ) {
956         print "====================\n";
957         print "checking directories & files for biblios\n";
958         print "====================\n";
959     }
960
961     #
962     # BIBLIOS : creating directory structure
963     #
964     unless (-d "$biblioserverdir") {
965         system("mkdir -p $biblioserverdir");
966         print "Info: created $biblioserverdir\n";
967         $created_dir_or_file++;
968     }
969     unless (-d "$biblioserverdir/lock") {
970         mkdir "$biblioserverdir/lock";
971         print "Info: created $biblioserverdir/lock\n";
972         $created_dir_or_file++;
973     }
974     unless (-d "$biblioserverdir/register") {
975         mkdir "$biblioserverdir/register";
976         print "Info: created $biblioserverdir/register\n";
977         $created_dir_or_file++;
978     }
979     unless (-d "$biblioserverdir/shadow") {
980         mkdir "$biblioserverdir/shadow";
981         print "Info: created $biblioserverdir/shadow\n";
982         $created_dir_or_file++;
983     }
984     unless (-d "$biblioserverdir/tab") {
985         mkdir "$biblioserverdir/tab";
986         print "Info: created $biblioserverdir/tab\n";
987         $created_dir_or_file++;
988     }
989     unless (-d "$biblioserverdir/key") {
990         mkdir "$biblioserverdir/key";
991         print "Info: created $biblioserverdir/key\n";
992         $created_dir_or_file++;
993     }
994     unless (-d "$biblioserverdir/etc") {
995         mkdir "$biblioserverdir/etc";
996         print "Info: created $biblioserverdir/etc\n";
997         $created_dir_or_file++;
998     }
999     
1000     #
1001     # BIBLIOS : copying mandatory files
1002     #
1003     # the record model, depending on marc flavour
1004     unless (-f "$biblioserverdir/tab/record.abs") {
1005         if (C4::Context->preference("marcflavour") eq "UNIMARC") {
1006             system("cp -f $kohadir/etc/zebradb/marc_defs/unimarc/biblios/record.abs $biblioserverdir/tab/record.abs");
1007             print "Info: copied record.abs for UNIMARC\n";
1008         } else {
1009             system("cp -f $kohadir/etc/zebradb/marc_defs/marc21/biblios/record.abs $biblioserverdir/tab/record.abs");
1010             print "Info: copied record.abs for USMARC\n";
1011         }
1012         $created_dir_or_file++;
1013     }
1014     unless (-f "$biblioserverdir/tab/sort-string-utf.chr") {
1015         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $biblioserverdir/tab/sort-string-utf.chr");
1016         print "Info: copied sort-string-utf.chr\n";
1017         $created_dir_or_file++;
1018     }
1019     unless (-f "$biblioserverdir/tab/word-phrase-utf.chr") {
1020         system("cp -f $kohadir/etc/zebradb/lang_defs/fr/sort-string-utf.chr $biblioserverdir/tab/word-phrase-utf.chr");
1021         print "Info: copied word-phase-utf.chr\n";
1022         $created_dir_or_file++;
1023     }
1024     unless (-f "$biblioserverdir/tab/bib1.att") {
1025         system("cp -f $kohadir/etc/zebradb/biblios/etc/bib1.att $biblioserverdir/tab/bib1.att");
1026         print "Info: copied bib1.att\n";
1027         $created_dir_or_file++;
1028     }
1029     unless (-f "$biblioserverdir/tab/default.idx") {
1030         system("cp -f $kohadir/etc/zebradb/etc/default.idx $biblioserverdir/tab/default.idx");
1031         print "Info: copied default.idx\n";
1032         $created_dir_or_file++;
1033     }
1034     unless (-f "$biblioserverdir/etc/ccl.properties") {
1035 #         system("cp -f $kohadir/etc/zebradb/ccl.properties ".C4::Context->zebraconfig('biblioserver')->{ccl2rpn});
1036         system("cp -f $kohadir/etc/zebradb/ccl.properties $biblioserverdir/etc/ccl.properties");
1037         print "Info: copied ccl.properties\n";
1038         $created_dir_or_file++;
1039     }
1040     unless (-f "$biblioserverdir/etc/pqf.properties") {
1041 #         system("cp -f $kohadir/etc/zebradb/pqf.properties ".C4::Context->zebraconfig('biblioserver')->{ccl2rpn});
1042         system("cp -f $kohadir/etc/zebradb/pqf.properties $biblioserverdir/etc/pqf.properties");
1043         print "Info: copied pqf.properties\n";
1044         $created_dir_or_file++;
1045     }
1046     
1047     #
1048     # BIBLIOS : copying mandatory files
1049     #
1050     unless (-f C4::Context->zebraconfig('biblioserver')->{config}) {
1051     open my $zd, '>:encoding(UTF-8)', C4::Context->zebraconfig('biblioserver')->{config};
1052     print {$zd} "
1053 # generated by KOHA/misc/migrtion_tools/rebuild_zebra.pl 
1054 profilePath:\${srcdir:-.}:$biblioserverdir/tab/:$tabdir/tab/:\${srcdir:-.}/tab/
1055
1056 encoding: UTF-8
1057 # Files that describe the attribute sets supported.
1058 attset:bib1.att
1059 attset:explain.att
1060 attset:gils.att
1061
1062 modulePath:$modulesdir/modules/
1063 # Specify record type
1064 iso2709.recordType:grs.marcxml.record
1065 recordType:grs.xml
1066 recordId: (bib1,Local-Number)
1067 storeKeys:1
1068 storeData:1
1069
1070
1071 # Lock File Area
1072 lockDir: $biblioserverdir/lock
1073 perm.anonymous:r
1074 perm.kohaadmin:rw
1075 register: $biblioserverdir/register:4G
1076 shadow: $biblioserverdir/shadow:4G
1077
1078 # Temp File area for result sets
1079 setTmpDir: $biblioserverdir/tmp
1080
1081 # Temp File area for index program
1082 keyTmpDir: $biblioserverdir/key
1083
1084 # Approx. Memory usage during indexing
1085 memMax: 40M
1086 rank:rank-1
1087     ";
1088         print "Info: creating zebra-biblios.cfg\n";
1089         $created_dir_or_file++;
1090     }
1091     
1092     if ($created_dir_or_file) {
1093         print "Info: created : $created_dir_or_file directories & files\n";
1094     } else {
1095         print "Info: file & directories OK\n";
1096     }
1097     
1098 }
1099 }