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";
127 if ($host_id && $share_id && $num) {
136 my $limit = sprintf('LIMIT '.EST_CHUNK.' OFFSET %d', $offset);
138 my $sth = $dbh->prepare(qq{
142 shares.name AS sname,
143 -- shares.share AS sharename,
144 files.backupnum AS backupnum,
145 -- files.name AS filename,
146 files.path AS filepath,
150 files.shareid AS shareid,
151 backups.date AS backup_date
153 INNER JOIN shares ON files.shareID=shares.ID
154 INNER JOIN hosts ON hosts.ID = shares.hostID
155 INNER JOIN backups ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
161 $results = $sth->rows;
164 print " - no more files\n";
169 my $t = shift || return;
170 my $iso = BackupPC::Lib::timeStamp($t);
175 while (my $row = $sth->fetchrow_hashref()) {
177 my $fid = $row->{'fid'} || die "no fid?";
178 my $uri = 'file:///' . $fid;
180 my $id = $hest_db->uri_to_id($uri);
181 next unless ($id == -1);
183 # create a document object
184 my $doc = HyperEstraier::Document->new;
186 # add attributes to the document object
187 $doc->add_attr('@uri', $uri);
189 foreach my $c (@{ $sth->{NAME} }) {
190 $doc->add_attr($c, $row->{$c}) if ($row->{$c});
193 #$doc->add_attr('@cdate', fmt_date($row->{'date'}));
195 # add the body text to the document object
196 my $path = $row->{'filepath'};
197 $doc->add_text($path);
198 $path =~ s/(.)/$1 /g;
199 $doc->add_hidden_text($path);
201 print STDERR $doc->dump_draft,"\n" if ($debug > 1);
203 # register the document object to the database
204 $hest_db->put_doc($doc, $HyperEstraier::Database::PDCLEAN);
211 $offset += EST_CHUNK;
213 } while ($results == EST_CHUNK);
218 my $dur = (time() - $t) || 1;
219 printf(" [%.2f/s dur: %s]\n",
229 if (($opt{i} || ($index_path && ! -e $index_path)) && !$opt{c}) {
231 print "force update of HyperEstraier index ";
232 print "importing existing data" unless (-e $index_path);
233 print "by -i flag" if ($opt{i});
241 my $index = shift || return;
242 my ($table,$col,$unique) = split(/_/, $index);
245 $dbh->do(qq{ create $unique index $index on $table($col) });
248 print "creating tables...\n";
252 ID SERIAL PRIMARY KEY,
253 name VARCHAR(30) NOT NULL,
259 create table shares (
260 ID SERIAL PRIMARY KEY,
261 hostID INTEGER NOT NULL references hosts(id),
262 name VARCHAR(30) NOT NULL,
263 share VARCHAR(200) NOT NULL,
264 localpath VARCHAR(200)
269 create table backups (
270 hostID INTEGER NOT NULL references hosts(id),
271 num INTEGER NOT NULL,
272 date integer NOT NULL,
273 type CHAR(4) not null,
274 shareID integer not null references shares(id),
275 size integer not null,
276 PRIMARY KEY(hostID, num, shareID)
280 #do_index('backups_hostid,num_unique');
284 ID SERIAL PRIMARY KEY,
285 num INTEGER NOT NULL,
286 name VARCHAR(255) NOT NULL,
293 ID SERIAL PRIMARY KEY,
294 shareID INTEGER NOT NULL references shares(id),
295 backupNum INTEGER NOT NULL,
296 name VARCHAR(255) NOT NULL,
297 path VARCHAR(255) NOT NULL,
298 date integer NOT NULL,
299 type INTEGER NOT NULL,
300 size INTEGER NOT NULL,
301 dvdid INTEGER references dvds(id)
305 print "creating indexes:";
307 foreach my $index (qw(
328 ## delete data before inseting ##
331 foreach my $table (qw(files dvds backups shares hosts)) {
333 $dbh->do(qq{ DELETE FROM $table });
340 ## insert new values ##
343 $hosts = $bpc->HostInfoRead();
349 $sth->{insert_hosts} = $dbh->prepare(qq{
350 INSERT INTO hosts (name, IP) VALUES (?,?)
353 $sth->{hosts_by_name} = $dbh->prepare(qq{
354 SELECT ID FROM hosts WHERE name=?
357 $sth->{backups_count} = $dbh->prepare(qq{
360 WHERE hostID=? AND num=? AND shareid=?
363 $sth->{insert_backups} = $dbh->prepare(qq{
364 INSERT INTO backups (hostID, num, date, type, shareid, size)
368 $sth->{insert_files} = $dbh->prepare(qq{
370 (shareID, backupNum, name, path, date, type, size)
371 VALUES (?,?,?,?,?,?,?)
374 foreach my $host_key (keys %{$hosts}) {
376 my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
378 $sth->{hosts_by_name}->execute($hosts->{$host_key}->{'host'});
380 unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
381 $sth->{insert_hosts}->execute(
382 $hosts->{$host_key}->{'host'},
383 $hosts->{$host_key}->{'ip'}
386 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
389 print "host ".$hosts->{$host_key}->{'host'}.": ";
391 # get backups for a host
392 my @backups = $bpc->BackupInfoRead($hostname);
393 my $incs = scalar @backups;
394 print "$incs increments\n";
399 foreach my $backup (@backups) {
402 last if ($opt{m} && $inc_nr > $opt{m});
404 my $backupNum = $backup->{'num'};
405 my @backupShares = ();
407 printf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n",
408 $hosts->{$host_key}->{'host'},
409 $inc_nr, $incs, $backupNum,
410 $backup->{type} || '?',
411 $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
412 strftime($t_fmt,localtime($backup->{startTime})),
413 fmt_time($backup->{endTime} - $backup->{startTime})
416 my $files = BackupPC::View->new($bpc, $hostname, \@backups, 1);
417 foreach my $share ($files->shareList($backupNum)) {
421 $shareID = getShareID($share, $hostID, $hostname);
423 $sth->{backups_count}->execute($hostID, $backupNum, $shareID);
424 my ($count) = $sth->{backups_count}->fetchrow_array();
425 # skip if allready in database!
426 next if ($count > 0);
429 print curr_time," ", $share;
431 my ($f, $nf, $d, $nd, $size) = recurseDir($bpc, $hostname, $files, $backupNum, $share, "", $shareID);
433 $sth->{insert_backups}->execute(
436 $backup->{'endTime'},
445 my $dur = (time() - $t) || 1;
446 printf(" %d/%d files %d/%d dirs %0.2f MB [%.2f/s dur: %s]\n",
448 ($size / 1024 / 1024),
453 hest_update($hostID, $shareID, $backupNum);
462 print "total duration: ",fmt_time(time() - $start_t),"\n";
468 my ($share, $hostID, $hostname) = @_;
470 $sth->{share_id} ||= $dbh->prepare(qq{
471 SELECT ID FROM shares WHERE hostID=? AND name=?
474 $sth->{share_id}->execute($hostID,$share);
476 my ($id) = $sth->{share_id}->fetchrow_array();
478 return $id if (defined($id));
480 $sth->{insert_share} ||= $dbh->prepare(qq{
482 (hostID,name,share,localpath)
486 my $drop_down = $hostname . '/' . $share;
487 $drop_down =~ s#//+#/#g;
489 $sth->{insert_share}->execute($hostID,$share, $drop_down ,undef);
490 return $dbh->last_insert_id(undef,undef,'shares',undef);
498 my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
500 return $beenThere->{$key} if (defined($beenThere->{$key}));
502 $sth->{file_in_db} ||= $dbh->prepare(qq{
504 WHERE shareID = ? and
511 my @param = ($shareID,$path,$date,$size);
512 $sth->{file_in_db}->execute(@param);
513 my $rows = $sth->{file_in_db}->rows;
514 print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
516 $beenThere->{$key}++;
518 $sth->{'insert_files'}->execute(@data) unless ($rows);
522 ####################################################
523 # recursing through filesystem structure and #
524 # and returning flattened files list #
525 ####################################################
526 sub recurseDir($$$$$$$$) {
528 my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
530 print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
532 my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
537 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
538 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
540 # first, add all the entries in current directory
541 foreach my $path_key (keys %{$filesInBackup}) {
542 print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
547 $filesInBackup->{$path_key}->{'relPath'},
548 $filesInBackup->{$path_key}->{'mtime'},
549 $filesInBackup->{$path_key}->{'type'},
550 $filesInBackup->{$path_key}->{'size'}
553 my $key = join(" ", (
557 $filesInBackup->{$path_key}->{'mtime'},
558 $filesInBackup->{$path_key}->{'size'}
562 if (! defined($beenThere->{$key}) && ! ($found = found_in_db($key, @data)) ) {
563 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
565 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
566 $new_dirs++ unless ($found);
567 print STDERR " dir\n" if ($debug >= 2);
569 $new_files++ unless ($found);
570 print STDERR " file\n" if ($debug >= 2);
572 $size += $filesInBackup->{$path_key}->{'size'} || 0;
575 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
578 my $full_path = $dir . '/' . $path_key;
579 push @stack, $full_path;
580 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
582 # my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
594 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
596 while ( my $dir = shift @stack ) {
597 my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
598 print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
607 return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);