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);
49 if ($use_hest =~ m#^http://#) {
50 $index_node_url = $use_hest;
52 $index_path = $TopDir . '/' . $index_path;
53 $index_path =~ s#//#/#g;
56 print "-- $use_hest : $index_path OR $index_node_url --\n";
59 my $dbh = DBI->connect($dsn, $user, "", { RaiseError => 1, AutoCommit => 0 });
63 if ( !getopts("cdm:v:i", \%opt ) ) {
65 usage: $0 [-c|-d] [-m num] [-v|-v level] [-i]
68 -c create database on first use
69 -d delete database before import
70 -m num import just num increments for one host
71 -v num set verbosity (debug) level (default $debug)
72 -i update HyperEstraier full text index
78 print "Debug level at $opt{v}\n";
85 my $t = shift || return;
87 my ($ss,$mm,$hh) = gmtime($t);
88 $out .= "${hh}h" if ($hh);
89 $out .= sprintf("%02d:%02d", $mm,$ss);
94 return strftime($t_fmt,localtime());
103 print "\nCaught a SIG$sig--syncing database and shutting down\n";
110 $SIG{'INT'} = \&signal;
111 $SIG{'QUIT'} = \&signal;
115 my ($host_id, $share_id, $num) = @_;
118 print STDERR "HyperEstraier support not enabled in configuration\n";
122 print curr_time," updating HyperEstraier:";
129 print " opening index $use_hest";
131 $hest_db = HyperEstraier::Database->new();
132 $hest_db->open($index_path, $HyperEstraier::Database::DBWRITER | $HyperEstraier::Database::DBCREAT);
134 } elsif ($index_node_url) {
135 $hest_node ||= HyperEstraier::Node->new($index_node_url);
136 $hest_node->set_auth('admin', 'admin');
137 print " via node URL";
139 die "don't know how to use HyperEstraier Index $use_hest";
141 print " increment is " . EST_CHUNK . " files:";
149 if ($host_id && $share_id && $num) {
156 @data = ( $host_id, $share_id, $num );
159 my $limit = sprintf('LIMIT '.EST_CHUNK.' OFFSET %d', $offset);
161 my $sth = $dbh->prepare(qq{
165 shares.name AS sname,
166 -- shares.share AS sharename,
167 files.backupnum AS backupnum,
168 -- files.name AS filename,
169 files.path AS filepath,
173 files.shareid AS shareid,
174 backups.date AS backup_date
176 INNER JOIN shares ON files.shareID=shares.ID
177 INNER JOIN hosts ON hosts.ID = shares.hostID
178 INNER JOIN backups ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
183 $sth->execute(@data);
184 $results = $sth->rows;
187 print " - no new files\n";
192 my $t = shift || return;
193 my $iso = BackupPC::Lib::timeStamp($t);
198 while (my $row = $sth->fetchrow_hashref()) {
200 my $fid = $row->{'fid'} || die "no fid?";
201 my $uri = 'file:///' . $fid;
203 my $id = ($hest_db || $hest_node)->uri_to_id($uri);
204 next unless ($id == -1);
206 # create a document object
207 my $doc = HyperEstraier::Document->new;
209 # add attributes to the document object
210 $doc->add_attr('@uri', $uri);
212 foreach my $c (@{ $sth->{NAME} }) {
213 $doc->add_attr($c, $row->{$c}) if ($row->{$c});
216 #$doc->add_attr('@cdate', fmt_date($row->{'date'}));
218 # add the body text to the document object
219 my $path = $row->{'filepath'};
220 $doc->add_text($path);
221 $path =~ s/(.)/$1 /g;
222 $doc->add_hidden_text($path);
224 print STDERR $doc->dump_draft,"\n" if ($debug > 1);
226 # register the document object to the database
228 $hest_db->put_doc($doc, $HyperEstraier::Database::PDCLEAN);
229 } elsif ($hest_node) {
230 $hest_node->put_doc($doc);
238 $hest_db->sync() if ($index_path);
240 $offset += EST_CHUNK;
242 } while ($results == EST_CHUNK);
249 my $dur = (time() - $t) || 1;
250 printf(" [%.2f/s dur: %s]\n",
260 if (($opt{i} || ($index_path && ! -e $index_path)) && !$opt{c}) {
262 print "force update of HyperEstraier index ";
263 print "importing existing data" unless (-e $index_path);
264 print "by -i flag" if ($opt{i});
272 my $index = shift || return;
273 my ($table,$col,$unique) = split(/_/, $index);
276 $dbh->do(qq{ create $unique index $index on $table($col) });
279 print "creating tables...\n";
283 ID SERIAL PRIMARY KEY,
284 name VARCHAR(30) NOT NULL,
290 create table shares (
291 ID SERIAL PRIMARY KEY,
292 hostID INTEGER NOT NULL references hosts(id),
293 name VARCHAR(30) NOT NULL,
294 share VARCHAR(200) NOT NULL,
295 localpath VARCHAR(200)
300 create table backups (
301 hostID INTEGER NOT NULL references hosts(id),
302 num INTEGER NOT NULL,
303 date integer NOT NULL,
304 type CHAR(4) not null,
305 shareID integer not null references shares(id),
306 size integer not null,
307 PRIMARY KEY(hostID, num, shareID)
311 #do_index('backups_hostid,num_unique');
315 ID SERIAL PRIMARY KEY,
316 num INTEGER NOT NULL,
317 name VARCHAR(255) NOT NULL,
324 ID SERIAL PRIMARY KEY,
325 shareID INTEGER NOT NULL references shares(id),
326 backupNum INTEGER NOT NULL,
327 name VARCHAR(255) NOT NULL,
328 path VARCHAR(255) NOT NULL,
329 date integer NOT NULL,
330 type INTEGER NOT NULL,
331 size INTEGER NOT NULL,
332 dvdid INTEGER references dvds(id)
336 print "creating indexes:";
338 foreach my $index (qw(
359 ## delete data before inseting ##
362 foreach my $table (qw(files dvds backups shares hosts)) {
364 $dbh->do(qq{ DELETE FROM $table });
371 ## insert new values ##
374 $hosts = $bpc->HostInfoRead();
380 $sth->{insert_hosts} = $dbh->prepare(qq{
381 INSERT INTO hosts (name, IP) VALUES (?,?)
384 $sth->{hosts_by_name} = $dbh->prepare(qq{
385 SELECT ID FROM hosts WHERE name=?
388 $sth->{backups_count} = $dbh->prepare(qq{
391 WHERE hostID=? AND num=? AND shareid=?
394 $sth->{insert_backups} = $dbh->prepare(qq{
395 INSERT INTO backups (hostID, num, date, type, shareid, size)
399 $sth->{insert_files} = $dbh->prepare(qq{
401 (shareID, backupNum, name, path, date, type, size)
402 VALUES (?,?,?,?,?,?,?)
405 foreach my $host_key (keys %{$hosts}) {
407 my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
409 $sth->{hosts_by_name}->execute($hosts->{$host_key}->{'host'});
411 unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
412 $sth->{insert_hosts}->execute(
413 $hosts->{$host_key}->{'host'},
414 $hosts->{$host_key}->{'ip'}
417 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
420 print "host ".$hosts->{$host_key}->{'host'}.": ";
422 # get backups for a host
423 my @backups = $bpc->BackupInfoRead($hostname);
424 my $incs = scalar @backups;
425 print "$incs increments\n";
430 foreach my $backup (@backups) {
433 last if ($opt{m} && $inc_nr > $opt{m});
435 my $backupNum = $backup->{'num'};
436 my @backupShares = ();
438 printf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n",
439 $hosts->{$host_key}->{'host'},
440 $inc_nr, $incs, $backupNum,
441 $backup->{type} || '?',
442 $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
443 strftime($t_fmt,localtime($backup->{startTime})),
444 fmt_time($backup->{endTime} - $backup->{startTime})
447 my $files = BackupPC::View->new($bpc, $hostname, \@backups, 1);
448 foreach my $share ($files->shareList($backupNum)) {
452 $shareID = getShareID($share, $hostID, $hostname);
454 $sth->{backups_count}->execute($hostID, $backupNum, $shareID);
455 my ($count) = $sth->{backups_count}->fetchrow_array();
456 # skip if allready in database!
457 next if ($count > 0);
460 print curr_time," ", $share;
462 my ($f, $nf, $d, $nd, $size) = recurseDir($bpc, $hostname, $files, $backupNum, $share, "", $shareID);
464 $sth->{insert_backups}->execute(
467 $backup->{'endTime'},
476 my $dur = (time() - $t) || 1;
477 printf(" %d/%d files %d/%d dirs %0.2f MB [%.2f/s dur: %s]\n",
479 ($size / 1024 / 1024),
484 hest_update($hostID, $shareID, $backupNum) if ($nf + $nd > 0);
493 print "total duration: ",fmt_time(time() - $start_t),"\n";
499 my ($share, $hostID, $hostname) = @_;
501 $sth->{share_id} ||= $dbh->prepare(qq{
502 SELECT ID FROM shares WHERE hostID=? AND name=?
505 $sth->{share_id}->execute($hostID,$share);
507 my ($id) = $sth->{share_id}->fetchrow_array();
509 return $id if (defined($id));
511 $sth->{insert_share} ||= $dbh->prepare(qq{
513 (hostID,name,share,localpath)
517 my $drop_down = $hostname . '/' . $share;
518 $drop_down =~ s#//+#/#g;
520 $sth->{insert_share}->execute($hostID,$share, $drop_down ,undef);
521 return $dbh->last_insert_id(undef,undef,'shares',undef);
529 my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
531 return $beenThere->{$key} if (defined($beenThere->{$key}));
533 $sth->{file_in_db} ||= $dbh->prepare(qq{
535 WHERE shareID = ? and
542 my @param = ($shareID,$path,$date,$size);
543 $sth->{file_in_db}->execute(@param);
544 my $rows = $sth->{file_in_db}->rows;
545 print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
547 $beenThere->{$key}++;
549 $sth->{'insert_files'}->execute(@data) unless ($rows);
553 ####################################################
554 # recursing through filesystem structure and #
555 # and returning flattened files list #
556 ####################################################
557 sub recurseDir($$$$$$$$) {
559 my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
561 print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
563 my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
568 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
569 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
571 # first, add all the entries in current directory
572 foreach my $path_key (keys %{$filesInBackup}) {
573 print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
578 $filesInBackup->{$path_key}->{'relPath'},
579 $filesInBackup->{$path_key}->{'mtime'},
580 $filesInBackup->{$path_key}->{'type'},
581 $filesInBackup->{$path_key}->{'size'}
584 my $key = join(" ", (
588 $filesInBackup->{$path_key}->{'mtime'},
589 $filesInBackup->{$path_key}->{'size'}
593 if (! defined($beenThere->{$key}) && ! ($found = found_in_db($key, @data)) ) {
594 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
596 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
597 $new_dirs++ unless ($found);
598 print STDERR " dir\n" if ($debug >= 2);
600 $new_files++ unless ($found);
601 print STDERR " file\n" if ($debug >= 2);
603 $size += $filesInBackup->{$path_key}->{'size'} || 0;
606 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
609 my $full_path = $dir . '/' . $path_key;
610 push @stack, $full_path;
611 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
613 # my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
625 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
627 while ( my $dir = shift @stack ) {
628 my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
629 print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
638 return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);