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