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} || '';
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);
120 print " increment is " . EST_CHUNK . " files";
128 if ($host_id && $share_id && $num) {
135 @data = ( $host_id, $share_id, $num );
138 my $limit = sprintf('LIMIT '.EST_CHUNK.' OFFSET %d', $offset);
140 my $sth = $dbh->prepare(qq{
144 shares.name AS sname,
145 -- shares.share AS sharename,
146 files.backupnum AS backupnum,
147 -- files.name AS filename,
148 files.path AS filepath,
152 files.shareid AS shareid,
153 backups.date AS backup_date
155 INNER JOIN shares ON files.shareID=shares.ID
156 INNER JOIN hosts ON hosts.ID = shares.hostID
157 INNER JOIN backups ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
162 $sth->execute(@data);
163 $results = $sth->rows;
166 print " - no more files\n";
171 my $t = shift || return;
172 my $iso = BackupPC::Lib::timeStamp($t);
177 while (my $row = $sth->fetchrow_hashref()) {
179 my $fid = $row->{'fid'} || die "no fid?";
180 my $uri = 'file:///' . $fid;
182 my $id = $hest_db->uri_to_id($uri);
183 next unless ($id == -1);
185 # create a document object
186 my $doc = HyperEstraier::Document->new;
188 # add attributes to the document object
189 $doc->add_attr('@uri', $uri);
191 foreach my $c (@{ $sth->{NAME} }) {
192 $doc->add_attr($c, $row->{$c}) if ($row->{$c});
195 #$doc->add_attr('@cdate', fmt_date($row->{'date'}));
197 # add the body text to the document object
198 my $path = $row->{'filepath'};
199 $doc->add_text($path);
200 $path =~ s/(.)/$1 /g;
201 $doc->add_hidden_text($path);
203 print STDERR $doc->dump_draft,"\n" if ($debug > 1);
205 # register the document object to the database
206 $hest_db->put_doc($doc, $HyperEstraier::Database::PDCLEAN);
213 $offset += EST_CHUNK;
215 } while ($results == EST_CHUNK);
220 my $dur = (time() - $t) || 1;
221 printf(" [%.2f/s dur: %s]\n",
231 if (($opt{i} || ($index_path && ! -e $index_path)) && !$opt{c}) {
233 print "force update of HyperEstraier index ";
234 print "importing existing data" unless (-e $index_path);
235 print "by -i flag" if ($opt{i});
243 my $index = shift || return;
244 my ($table,$col,$unique) = split(/_/, $index);
247 $dbh->do(qq{ create $unique index $index on $table($col) });
250 print "creating tables...\n";
254 ID SERIAL PRIMARY KEY,
255 name VARCHAR(30) NOT NULL,
261 create table shares (
262 ID SERIAL PRIMARY KEY,
263 hostID INTEGER NOT NULL references hosts(id),
264 name VARCHAR(30) NOT NULL,
265 share VARCHAR(200) NOT NULL,
266 localpath VARCHAR(200)
271 create table backups (
272 hostID INTEGER NOT NULL references hosts(id),
273 num INTEGER NOT NULL,
274 date integer NOT NULL,
275 type CHAR(4) not null,
276 shareID integer not null references shares(id),
277 size integer not null,
278 PRIMARY KEY(hostID, num, shareID)
282 #do_index('backups_hostid,num_unique');
286 ID SERIAL PRIMARY KEY,
287 num INTEGER NOT NULL,
288 name VARCHAR(255) NOT NULL,
295 ID SERIAL PRIMARY KEY,
296 shareID INTEGER NOT NULL references shares(id),
297 backupNum INTEGER NOT NULL,
298 name VARCHAR(255) NOT NULL,
299 path VARCHAR(255) NOT NULL,
300 date integer NOT NULL,
301 type INTEGER NOT NULL,
302 size INTEGER NOT NULL,
303 dvdid INTEGER references dvds(id)
307 print "creating indexes:";
309 foreach my $index (qw(
330 ## delete data before inseting ##
333 foreach my $table (qw(files dvds backups shares hosts)) {
335 $dbh->do(qq{ DELETE FROM $table });
342 ## insert new values ##
345 $hosts = $bpc->HostInfoRead();
351 $sth->{insert_hosts} = $dbh->prepare(qq{
352 INSERT INTO hosts (name, IP) VALUES (?,?)
355 $sth->{hosts_by_name} = $dbh->prepare(qq{
356 SELECT ID FROM hosts WHERE name=?
359 $sth->{backups_count} = $dbh->prepare(qq{
362 WHERE hostID=? AND num=? AND shareid=?
365 $sth->{insert_backups} = $dbh->prepare(qq{
366 INSERT INTO backups (hostID, num, date, type, shareid, size)
370 $sth->{insert_files} = $dbh->prepare(qq{
372 (shareID, backupNum, name, path, date, type, size)
373 VALUES (?,?,?,?,?,?,?)
376 foreach my $host_key (keys %{$hosts}) {
378 my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
380 $sth->{hosts_by_name}->execute($hosts->{$host_key}->{'host'});
382 unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
383 $sth->{insert_hosts}->execute(
384 $hosts->{$host_key}->{'host'},
385 $hosts->{$host_key}->{'ip'}
388 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
391 print "host ".$hosts->{$host_key}->{'host'}.": ";
393 # get backups for a host
394 my @backups = $bpc->BackupInfoRead($hostname);
395 my $incs = scalar @backups;
396 print "$incs increments\n";
401 foreach my $backup (@backups) {
404 last if ($opt{m} && $inc_nr > $opt{m});
406 my $backupNum = $backup->{'num'};
407 my @backupShares = ();
409 printf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n",
410 $hosts->{$host_key}->{'host'},
411 $inc_nr, $incs, $backupNum,
412 $backup->{type} || '?',
413 $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
414 strftime($t_fmt,localtime($backup->{startTime})),
415 fmt_time($backup->{endTime} - $backup->{startTime})
418 my $files = BackupPC::View->new($bpc, $hostname, \@backups, 1);
419 foreach my $share ($files->shareList($backupNum)) {
423 $shareID = getShareID($share, $hostID, $hostname);
425 $sth->{backups_count}->execute($hostID, $backupNum, $shareID);
426 my ($count) = $sth->{backups_count}->fetchrow_array();
427 # skip if allready in database!
428 next if ($count > 0);
431 print curr_time," ", $share;
433 my ($f, $nf, $d, $nd, $size) = recurseDir($bpc, $hostname, $files, $backupNum, $share, "", $shareID);
435 $sth->{insert_backups}->execute(
438 $backup->{'endTime'},
447 my $dur = (time() - $t) || 1;
448 printf(" %d/%d files %d/%d dirs %0.2f MB [%.2f/s dur: %s]\n",
450 ($size / 1024 / 1024),
455 hest_update($hostID, $shareID, $backupNum);
464 print "total duration: ",fmt_time(time() - $start_t),"\n";
470 my ($share, $hostID, $hostname) = @_;
472 $sth->{share_id} ||= $dbh->prepare(qq{
473 SELECT ID FROM shares WHERE hostID=? AND name=?
476 $sth->{share_id}->execute($hostID,$share);
478 my ($id) = $sth->{share_id}->fetchrow_array();
480 return $id if (defined($id));
482 $sth->{insert_share} ||= $dbh->prepare(qq{
484 (hostID,name,share,localpath)
488 my $drop_down = $hostname . '/' . $share;
489 $drop_down =~ s#//+#/#g;
491 $sth->{insert_share}->execute($hostID,$share, $drop_down ,undef);
492 return $dbh->last_insert_id(undef,undef,'shares',undef);
500 my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
502 return $beenThere->{$key} if (defined($beenThere->{$key}));
504 $sth->{file_in_db} ||= $dbh->prepare(qq{
506 WHERE shareID = ? and
513 my @param = ($shareID,$path,$date,$size);
514 $sth->{file_in_db}->execute(@param);
515 my $rows = $sth->{file_in_db}->rows;
516 print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
518 $beenThere->{$key}++;
520 $sth->{'insert_files'}->execute(@data) unless ($rows);
524 ####################################################
525 # recursing through filesystem structure and #
526 # and returning flattened files list #
527 ####################################################
528 sub recurseDir($$$$$$$$) {
530 my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
532 print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
534 my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
539 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
540 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
542 # first, add all the entries in current directory
543 foreach my $path_key (keys %{$filesInBackup}) {
544 print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
549 $filesInBackup->{$path_key}->{'relPath'},
550 $filesInBackup->{$path_key}->{'mtime'},
551 $filesInBackup->{$path_key}->{'type'},
552 $filesInBackup->{$path_key}->{'size'}
555 my $key = join(" ", (
559 $filesInBackup->{$path_key}->{'mtime'},
560 $filesInBackup->{$path_key}->{'size'}
564 if (! defined($beenThere->{$key}) && ! ($found = found_in_db($key, @data)) ) {
565 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
567 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
568 $new_dirs++ unless ($found);
569 print STDERR " dir\n" if ($debug >= 2);
571 $new_files++ unless ($found);
572 print STDERR " file\n" if ($debug >= 2);
574 $size += $filesInBackup->{$path_key}->{'size'} || 0;
577 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
580 my $full_path = $dir . '/' . $path_key;
581 push @stack, $full_path;
582 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
584 # my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
596 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
598 while ( my $dir = shift @stack ) {
599 my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
600 print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
609 return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);