1 #!/usr/local/bin/perl -w
4 use lib "__INSTALLDIR__/lib";
11 use Time::HiRes qw/time/;
13 use POSIX qw/strftime/;
15 use constant BPC_FTYPE_DIR => 5;
16 use constant EST_CHUNK => 100000;
23 my $pidfile = new File::Pid;
25 if (my $pid = $pidfile->running ) {
26 die "$0 already running: $pid\n";
27 } elsif ($pidfile->pid ne $$) {
29 $pidfile = new File::Pid;
32 print STDERR "$0 using pid ",$pidfile->pid," file ",$pidfile->file,"\n";
34 my $t_fmt = '%Y-%m-%d %H:%M:%S';
37 my $bpc = BackupPC::Lib->new || die;
38 my %Conf = $bpc->Conf();
39 my $TopDir = $bpc->TopDir();
42 my $dsn = $Conf{SearchDSN} || die "Need SearchDSN in config.pl\n";
43 my $user = $Conf{SearchUser} || '';
45 my $use_hest = $Conf{HyperEstraierIndex};
46 my ($index_path, $index_node_url) = getHyperEstraier_url($use_hest);
48 my $dbh = DBI->connect($dsn, $user, "", { RaiseError => 1, AutoCommit => 0 });
52 if ( !getopts("cdm:v:i", \%opt ) ) {
54 usage: $0 [-c|-d] [-m num] [-v|-v level] [-i]
57 -c create database on first use
58 -d delete database before import
59 -m num import just num increments for one host
60 -v num set verbosity (debug) level (default $debug)
61 -i update HyperEstraier full text index
67 print "Debug level at $opt{v}\n";
74 my $t = shift || return;
76 my ($ss,$mm,$hh) = gmtime($t);
77 $out .= "${hh}h" if ($hh);
78 $out .= sprintf("%02d:%02d", $mm,$ss);
83 return strftime($t_fmt,localtime());
92 print "\nCaught a SIG$sig--syncing database and shutting down\n";
99 $SIG{'INT'} = \&signal;
100 $SIG{'QUIT'} = \&signal;
104 my ($host_id, $share_id, $num) = @_;
107 print STDERR "HyperEstraier support not enabled in configuration\n";
111 print curr_time," updating HyperEstraier:";
118 print " opening index $use_hest";
120 $hest_db = HyperEstraier::Database->new();
121 $hest_db->open($index_path, $HyperEstraier::Database::DBWRITER | $HyperEstraier::Database::DBCREAT);
123 } elsif ($index_node_url) {
124 $hest_node ||= HyperEstraier::Node->new($index_node_url);
125 $hest_node->set_auth('admin', 'admin');
126 print " via node URL";
128 die "don't know how to use HyperEstraier Index $use_hest";
130 print " increment is " . EST_CHUNK . " files:";
138 if ($host_id && $share_id && $num) {
145 @data = ( $host_id, $share_id, $num );
148 my $limit = sprintf('LIMIT '.EST_CHUNK.' OFFSET %d', $offset);
150 my $sth = $dbh->prepare(qq{
154 shares.name AS sname,
155 -- shares.share AS sharename,
156 files.backupnum AS backupnum,
157 -- files.name AS filename,
158 files.path AS filepath,
162 files.shareid AS shareid,
163 backups.date AS backup_date
165 INNER JOIN shares ON files.shareID=shares.ID
166 INNER JOIN hosts ON hosts.ID = shares.hostID
167 INNER JOIN backups ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
172 $sth->execute(@data);
173 $results = $sth->rows;
176 print " - no new files\n";
181 my $t = shift || return;
182 my $iso = BackupPC::Lib::timeStamp($t);
187 while (my $row = $sth->fetchrow_hashref()) {
189 my $fid = $row->{'fid'} || die "no fid?";
190 my $uri = 'file:///' . $fid;
192 my $id = ($hest_db || $hest_node)->uri_to_id($uri);
193 next unless ($id == -1);
195 # create a document object
196 my $doc = HyperEstraier::Document->new;
198 # add attributes to the document object
199 $doc->add_attr('@uri', $uri);
201 foreach my $c (@{ $sth->{NAME} }) {
202 $doc->add_attr($c, $row->{$c}) if ($row->{$c});
205 #$doc->add_attr('@cdate', fmt_date($row->{'date'}));
207 # add the body text to the document object
208 my $path = $row->{'filepath'};
209 $doc->add_text($path);
210 $path =~ s/(.)/$1 /g;
211 $doc->add_hidden_text($path);
213 print STDERR $doc->dump_draft,"\n" if ($debug > 1);
215 # register the document object to the database
217 $hest_db->put_doc($doc, $HyperEstraier::Database::PDCLEAN);
218 } elsif ($hest_node) {
219 $hest_node->put_doc($doc);
227 $hest_db->sync() if ($index_path);
229 $offset += EST_CHUNK;
231 } while ($results == EST_CHUNK);
238 my $dur = (time() - $t) || 1;
239 printf(" [%.2f/s dur: %s]\n",
249 if (($opt{i} || ($index_path && ! -e $index_path)) && !$opt{c}) {
251 print "force update of HyperEstraier index ";
252 print "importing existing data" unless (-e $index_path);
253 print "by -i flag" if ($opt{i});
261 my $index = shift || return;
262 my ($table,$col,$unique) = split(/_/, $index);
265 $dbh->do(qq{ create $unique index $index on $table($col) });
268 print "creating tables...\n";
272 ID SERIAL PRIMARY KEY,
273 name VARCHAR(30) NOT NULL,
279 create table shares (
280 ID SERIAL PRIMARY KEY,
281 hostID INTEGER NOT NULL references hosts(id),
282 name VARCHAR(30) NOT NULL,
283 share VARCHAR(200) NOT NULL,
284 localpath VARCHAR(200)
289 create table backups (
290 hostID INTEGER NOT NULL references hosts(id),
291 num INTEGER NOT NULL,
292 date integer NOT NULL,
293 type CHAR(4) not null,
294 shareID integer not null references shares(id),
295 size integer not null,
296 PRIMARY KEY(hostID, num, shareID)
300 #do_index('backups_hostid,num_unique');
304 ID SERIAL PRIMARY KEY,
305 num INTEGER NOT NULL,
306 name VARCHAR(255) NOT NULL,
313 ID SERIAL PRIMARY KEY,
314 shareID INTEGER NOT NULL references shares(id),
315 backupNum INTEGER NOT NULL,
316 name VARCHAR(255) NOT NULL,
317 path VARCHAR(255) NOT NULL,
318 date integer NOT NULL,
319 type INTEGER NOT NULL,
320 size INTEGER NOT NULL,
321 dvdid INTEGER references dvds(id)
325 print "creating indexes:";
327 foreach my $index (qw(
348 ## delete data before inseting ##
351 foreach my $table (qw(files dvds backups shares hosts)) {
353 $dbh->do(qq{ DELETE FROM $table });
360 ## insert new values ##
363 $hosts = $bpc->HostInfoRead();
369 $sth->{insert_hosts} = $dbh->prepare(qq{
370 INSERT INTO hosts (name, IP) VALUES (?,?)
373 $sth->{hosts_by_name} = $dbh->prepare(qq{
374 SELECT ID FROM hosts WHERE name=?
377 $sth->{backups_count} = $dbh->prepare(qq{
380 WHERE hostID=? AND num=? AND shareid=?
383 $sth->{insert_backups} = $dbh->prepare(qq{
384 INSERT INTO backups (hostID, num, date, type, shareid, size)
388 $sth->{insert_files} = $dbh->prepare(qq{
390 (shareID, backupNum, name, path, date, type, size)
391 VALUES (?,?,?,?,?,?,?)
394 foreach my $host_key (keys %{$hosts}) {
396 my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
398 $sth->{hosts_by_name}->execute($hosts->{$host_key}->{'host'});
400 unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
401 $sth->{insert_hosts}->execute(
402 $hosts->{$host_key}->{'host'},
403 $hosts->{$host_key}->{'ip'}
406 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
409 print "host ".$hosts->{$host_key}->{'host'}.": ";
411 # get backups for a host
412 my @backups = $bpc->BackupInfoRead($hostname);
413 my $incs = scalar @backups;
414 print "$incs increments\n";
419 foreach my $backup (@backups) {
422 last if ($opt{m} && $inc_nr > $opt{m});
424 my $backupNum = $backup->{'num'};
425 my @backupShares = ();
427 printf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n",
428 $hosts->{$host_key}->{'host'},
429 $inc_nr, $incs, $backupNum,
430 $backup->{type} || '?',
431 $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
432 strftime($t_fmt,localtime($backup->{startTime})),
433 fmt_time($backup->{endTime} - $backup->{startTime})
436 my $files = BackupPC::View->new($bpc, $hostname, \@backups, 1);
437 foreach my $share ($files->shareList($backupNum)) {
441 $shareID = getShareID($share, $hostID, $hostname);
443 $sth->{backups_count}->execute($hostID, $backupNum, $shareID);
444 my ($count) = $sth->{backups_count}->fetchrow_array();
445 # skip if allready in database!
446 next if ($count > 0);
449 print curr_time," ", $share;
451 my ($f, $nf, $d, $nd, $size) = recurseDir($bpc, $hostname, $files, $backupNum, $share, "", $shareID);
453 $sth->{insert_backups}->execute(
456 $backup->{'endTime'},
465 my $dur = (time() - $t) || 1;
466 printf(" %d/%d files %d/%d dirs %0.2f MB [%.2f/s dur: %s]\n",
468 ($size / 1024 / 1024),
473 hest_update($hostID, $shareID, $backupNum) if ($nf + $nd > 0);
482 print "total duration: ",fmt_time(time() - $start_t),"\n";
488 my ($share, $hostID, $hostname) = @_;
490 $sth->{share_id} ||= $dbh->prepare(qq{
491 SELECT ID FROM shares WHERE hostID=? AND name=?
494 $sth->{share_id}->execute($hostID,$share);
496 my ($id) = $sth->{share_id}->fetchrow_array();
498 return $id if (defined($id));
500 $sth->{insert_share} ||= $dbh->prepare(qq{
502 (hostID,name,share,localpath)
506 my $drop_down = $hostname . '/' . $share;
507 $drop_down =~ s#//+#/#g;
509 $sth->{insert_share}->execute($hostID,$share, $drop_down ,undef);
510 return $dbh->last_insert_id(undef,undef,'shares',undef);
518 my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
520 return $beenThere->{$key} if (defined($beenThere->{$key}));
522 $sth->{file_in_db} ||= $dbh->prepare(qq{
524 WHERE shareID = ? and
531 my @param = ($shareID,$path,$date,$size);
532 $sth->{file_in_db}->execute(@param);
533 my $rows = $sth->{file_in_db}->rows;
534 print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
536 $beenThere->{$key}++;
538 $sth->{'insert_files'}->execute(@data) unless ($rows);
542 ####################################################
543 # recursing through filesystem structure and #
544 # and returning flattened files list #
545 ####################################################
546 sub recurseDir($$$$$$$$) {
548 my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
550 print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
552 my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
557 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
558 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
560 # first, add all the entries in current directory
561 foreach my $path_key (keys %{$filesInBackup}) {
562 print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
567 $filesInBackup->{$path_key}->{'relPath'},
568 $filesInBackup->{$path_key}->{'mtime'},
569 $filesInBackup->{$path_key}->{'type'},
570 $filesInBackup->{$path_key}->{'size'}
573 my $key = join(" ", (
577 $filesInBackup->{$path_key}->{'mtime'},
578 $filesInBackup->{$path_key}->{'size'}
582 if (! defined($beenThere->{$key}) && ! ($found = found_in_db($key, @data)) ) {
583 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
585 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
586 $new_dirs++ unless ($found);
587 print STDERR " dir\n" if ($debug >= 2);
589 $new_files++ unless ($found);
590 print STDERR " file\n" if ($debug >= 2);
592 $size += $filesInBackup->{$path_key}->{'size'} || 0;
595 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
598 my $full_path = $dir . '/' . $path_key;
599 push @stack, $full_path;
600 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
602 # my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
614 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
616 while ( my $dir = shift @stack ) {
617 my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
618 print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
627 return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);