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 => 10000;
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} || '';
44 my $index_path = $Conf{HyperEstraierIndex};
45 $index_path = $TopDir . '/' . $index_path;
46 $index_path =~ s#//#/#g;
52 my $dbh = DBI->connect($dsn, $user, "", { RaiseError => 1, AutoCommit => 0 });
56 if ( !getopts("cdm:v:i", \%opt ) ) {
58 usage: $0 [-c|-d] [-m num] [-v|-v level] [-i]
61 -c create database on first use
62 -d delete database before import
63 -m num import just num increments for one host
64 -v num set verbosity (debug) level (default $debug)
65 -i update HyperEstraier full text index
71 print "Debug level at $opt{v}\n";
78 my $t = shift || return;
80 my ($ss,$mm,$hh) = gmtime($t);
81 $out .= "${hh}h" if ($hh);
82 $out .= sprintf("%02d:%02d", $mm,$ss);
87 return strftime($t_fmt,localtime());
95 print "\nCaught a SIG$sig--syncing database and shutting down\n";
102 $SIG{'INT'} = \&signal;
103 $SIG{'QUIT'} = \&signal;
107 my ($host_id, $share_id, $num) = @_;
109 print curr_time," updating HyperEstraier:";
116 print " opening index $index_path";
117 $hest_db = HyperEstraier::Database->new();
118 $hest_db->open($index_path, $HyperEstraier::Database::DBWRITER | $HyperEstraier::Database::DBCREAT);
125 if ($host_id && $share_id && $num) {
134 my $limit = sprintf('LIMIT '.EST_CHUNK.' OFFSET %d', $offset);
136 my $sth = $dbh->prepare(qq{
140 shares.name AS sname,
141 -- shares.share AS sharename,
142 files.backupnum AS backupnum,
143 -- files.name AS filename,
144 files.path AS filepath,
148 files.shareid AS shareid,
149 backups.date AS backup_date
151 INNER JOIN shares ON files.shareID=shares.ID
152 INNER JOIN hosts ON hosts.ID = shares.hostID
153 INNER JOIN backups ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
159 $results = $sth->rows;
162 print " - no more files\n";
167 my $t = shift || return;
168 my $iso = BackupPC::Lib::timeStamp($t);
173 while (my $row = $sth->fetchrow_hashref()) {
175 my $fid = $row->{'fid'} || die "no fid?";
176 my $uri = 'file:///' . $fid;
178 my $id = $hest_db->uri_to_id($uri);
179 next unless ($id == -1);
181 # create a document object
182 my $doc = HyperEstraier::Document->new;
184 # add attributes to the document object
185 $doc->add_attr('@uri', $uri);
187 foreach my $c (@{ $sth->{NAME} }) {
188 $doc->add_attr($c, $row->{$c}) if ($row->{$c});
191 #$doc->add_attr('@cdate', fmt_date($row->{'date'}));
193 # add the body text to the document object
194 my $path = $row->{'filepath'};
195 $doc->add_text($path);
196 $path =~ s/(.)/$1 /g;
197 $doc->add_hidden_text($path);
199 print STDERR $doc->dump_draft,"\n" if ($debug > 1);
201 # register the document object to the database
202 $hest_db->put_doc($doc, $HyperEstraier::Database::PDCLEAN);
209 $offset += EST_CHUNK;
211 } while ($results == EST_CHUNK);
216 my $dur = (time() - $t) || 1;
217 printf(" [%.2f/s dur: %s]\n",
227 if (($opt{i} || ($index_path && ! -e $index_path)) && !$opt{c}) {
229 print "force update of HyperEstraier index ";
230 print "importing existing data" unless (-e $index_path);
231 print "by -i flag" if ($opt{i});
239 my $index = shift || return;
240 my ($table,$col,$unique) = split(/_/, $index);
243 $dbh->do(qq{ create $unique index $index on $table($col) });
246 print "creating tables...\n";
250 ID SERIAL PRIMARY KEY,
251 name VARCHAR(30) NOT NULL,
257 create table shares (
258 ID SERIAL PRIMARY KEY,
259 hostID INTEGER NOT NULL references hosts(id),
260 name VARCHAR(30) NOT NULL,
261 share VARCHAR(200) NOT NULL,
262 localpath VARCHAR(200)
267 create table backups (
268 hostID INTEGER NOT NULL references hosts(id),
269 num INTEGER NOT NULL,
270 date integer NOT NULL,
271 type CHAR(4) not null,
272 shareID integer not null references shares(id),
273 size integer not null,
274 PRIMARY KEY(hostID, num, shareID)
278 #do_index('backups_hostid,num_unique');
282 ID SERIAL PRIMARY KEY,
283 num INTEGER NOT NULL,
284 name VARCHAR(255) NOT NULL,
291 ID SERIAL PRIMARY KEY,
292 shareID INTEGER NOT NULL references shares(id),
293 backupNum INTEGER NOT NULL,
294 name VARCHAR(255) NOT NULL,
295 path VARCHAR(255) NOT NULL,
296 date integer NOT NULL,
297 type INTEGER NOT NULL,
298 size INTEGER NOT NULL,
299 dvdid INTEGER references dvds(id)
303 print "creating indexes:";
305 foreach my $index (qw(
326 ## delete data before inseting ##
329 foreach my $table (qw(files dvds backups shares hosts)) {
331 $dbh->do(qq{ DELETE FROM $table });
338 ## insert new values ##
341 $hosts = $bpc->HostInfoRead();
347 $sth->{insert_hosts} = $dbh->prepare(qq{
348 INSERT INTO hosts (name, IP) VALUES (?,?)
351 $sth->{hosts_by_name} = $dbh->prepare(qq{
352 SELECT ID FROM hosts WHERE name=?
355 $sth->{backups_count} = $dbh->prepare(qq{
358 WHERE hostID=? AND num=? AND shareid=?
361 $sth->{insert_backups} = $dbh->prepare(qq{
362 INSERT INTO backups (hostID, num, date, type, shareid, size)
366 $sth->{insert_files} = $dbh->prepare(qq{
368 (shareID, backupNum, name, path, date, type, size)
369 VALUES (?,?,?,?,?,?,?)
372 foreach my $host_key (keys %{$hosts}) {
374 my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
376 $sth->{hosts_by_name}->execute($hosts->{$host_key}->{'host'});
378 unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
379 $sth->{insert_hosts}->execute(
380 $hosts->{$host_key}->{'host'},
381 $hosts->{$host_key}->{'ip'}
384 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
387 print "host ".$hosts->{$host_key}->{'host'}.": ";
389 # get backups for a host
390 my @backups = $bpc->BackupInfoRead($hostname);
391 my $incs = scalar @backups;
392 print "$incs increments\n";
397 foreach my $backup (@backups) {
400 last if ($opt{m} && $inc_nr > $opt{m});
402 my $backupNum = $backup->{'num'};
403 my @backupShares = ();
405 printf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n",
406 $hosts->{$host_key}->{'host'},
407 $inc_nr, $incs, $backupNum,
408 $backup->{type} || '?',
409 $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
410 strftime($t_fmt,localtime($backup->{startTime})),
411 fmt_time($backup->{endTime} - $backup->{startTime})
414 my $files = BackupPC::View->new($bpc, $hostname, \@backups, 1);
415 foreach my $share ($files->shareList($backupNum)) {
419 $shareID = getShareID($share, $hostID, $hostname);
421 $sth->{backups_count}->execute($hostID, $backupNum, $shareID);
422 my ($count) = $sth->{backups_count}->fetchrow_array();
423 # skip if allready in database!
424 next if ($count > 0);
427 print curr_time," ", $share;
429 my ($f, $nf, $d, $nd, $size) = recurseDir($bpc, $hostname, $files, $backupNum, $share, "", $shareID);
431 $sth->{insert_backups}->execute(
434 $backup->{'endTime'},
443 my $dur = (time() - $t) || 1;
444 printf(" %d/%d files %d/%d dirs %0.2f MB [%.2f/s dur: %s]\n",
446 ($size / 1024 / 1024),
451 hest_update($hostID, $shareID, $backupNum);
460 print "total duration: ",fmt_time(time() - $start_t),"\n";
466 my ($share, $hostID, $hostname) = @_;
468 $sth->{share_id} ||= $dbh->prepare(qq{
469 SELECT ID FROM shares WHERE hostID=? AND name=?
472 $sth->{share_id}->execute($hostID,$share);
474 my ($id) = $sth->{share_id}->fetchrow_array();
476 return $id if (defined($id));
478 $sth->{insert_share} ||= $dbh->prepare(qq{
480 (hostID,name,share,localpath)
484 my $drop_down = $hostname . '/' . $share;
485 $drop_down =~ s#//+#/#g;
487 $sth->{insert_share}->execute($hostID,$share, $drop_down ,undef);
488 return $dbh->last_insert_id(undef,undef,'shares',undef);
496 my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
498 return $beenThere->{$key} if (defined($beenThere->{$key}));
500 $sth->{file_in_db} ||= $dbh->prepare(qq{
502 WHERE shareID = ? and
509 my @param = ($shareID,$path,$date,$size);
510 $sth->{file_in_db}->execute(@param);
511 my $rows = $sth->{file_in_db}->rows;
512 print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
514 $beenThere->{$key}++;
516 $sth->{'insert_files'}->execute(@data) unless ($rows);
520 ####################################################
521 # recursing through filesystem structure and #
522 # and returning flattened files list #
523 ####################################################
524 sub recurseDir($$$$$$$$) {
526 my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
528 print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
530 my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
535 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
536 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
538 # first, add all the entries in current directory
539 foreach my $path_key (keys %{$filesInBackup}) {
540 print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
545 $filesInBackup->{$path_key}->{'relPath'},
546 $filesInBackup->{$path_key}->{'mtime'},
547 $filesInBackup->{$path_key}->{'type'},
548 $filesInBackup->{$path_key}->{'size'}
551 my $key = join(" ", (
555 $filesInBackup->{$path_key}->{'mtime'},
556 $filesInBackup->{$path_key}->{'size'}
560 if (! defined($beenThere->{$key}) && ! ($found = found_in_db($key, @data)) ) {
561 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
563 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
564 $new_dirs++ unless ($found);
565 print STDERR " dir\n" if ($debug >= 2);
567 $new_files++ unless ($found);
568 print STDERR " file\n" if ($debug >= 2);
570 $size += $filesInBackup->{$path_key}->{'size'} || 0;
573 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
576 my $full_path = $dir . '/' . $path_key;
577 push @stack, $full_path;
578 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
580 # my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
592 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
594 while ( my $dir = shift @stack ) {
595 my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
596 print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
605 return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);