/[BackupPC]/trunk/bin/BackupPC_updatedb
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /trunk/bin/BackupPC_updatedb

Parent Directory Parent Directory | Revision Log Revision Log


Revision 325 - (show annotations)
Tue Jan 31 16:29:30 2006 UTC (18 years, 4 months ago) by dpavlin
File size: 17866 byte(s)
 r9164@llin:  dpavlin | 2006-01-31 17:29:14 +0100
 create trigger, small fixes for deployment without HyperEstraier

1 #!/usr/local/bin/perl -w
2
3 use strict;
4 use lib "__INSTALLDIR__/lib";
5
6 use DBI;
7 use BackupPC::Lib;
8 use BackupPC::View;
9 use Data::Dumper;
10 use Getopt::Std;
11 use Time::HiRes qw/time/;
12 use File::Pid;
13 use POSIX qw/strftime/;
14 use BackupPC::SearchLib;
15 use Cwd qw/abs_path/;
16
17 use constant BPC_FTYPE_DIR => 5;
18 use constant EST_CHUNK => 4096;
19
20 # daylight saving time change offset for 1h
21 my $dst_offset = 60 * 60;
22
23 my $debug = 0;
24 $|=1;
25
26 my $start_t = time();
27
28 my $pid_path = abs_path($0);
29 $pid_path =~ s/\W+/_/g;
30
31 my $pidfile = new File::Pid({
32 file => "/tmp/$pid_path",
33 });
34
35 if (my $pid = $pidfile->running ) {
36 die "$0 already running: $pid\n";
37 } elsif ($pidfile->pid ne $$) {
38 $pidfile->remove;
39 $pidfile = new File::Pid;
40 }
41 print STDERR "$0 using pid ",$pidfile->pid," file ",$pidfile->file,"\n";
42 $pidfile->write;
43
44 my $t_fmt = '%Y-%m-%d %H:%M:%S';
45
46 my $hosts;
47 my $bpc = BackupPC::Lib->new || die;
48 my %Conf = $bpc->Conf();
49 my $TopDir = $bpc->TopDir();
50 my $beenThere = {};
51
52 my $dsn = $Conf{SearchDSN} || die "Need SearchDSN in config.pl\n";
53 my $user = $Conf{SearchUser} || '';
54
55 my $index_node_url = $Conf{HyperEstraierIndex};
56
57 my $dbh = DBI->connect($dsn, $user, "", { RaiseError => 1, AutoCommit => 0 });
58
59 my %opt;
60
61 if ( !getopts("cdm:v:ijfq", \%opt ) ) {
62 print STDERR <<EOF;
63 usage: $0 [-c|-d] [-m num] [-v|-v level] [-i|-j|-f]
64
65 Options:
66 -c create database on first use
67 -d delete database before import
68 -m num import just num increments for one host
69 -v num set verbosity (debug) level (default $debug)
70 -i update Hyper Estraier full text index
71 -j update full text, don't check existing files
72 -f don't do anything with full text index
73 -q be quiet for hosts without changes
74
75 Option -j is variation on -i. It will allow faster initial creation
76 of full-text index from existing database.
77
78 Option -f will create database which is out of sync with full text index. You
79 will have to re-run $0 with -i to fix it.
80
81 EOF
82 exit 1;
83 }
84
85 if ($opt{v}) {
86 print "Debug level at $opt{v}\n";
87 $debug = $opt{v};
88 } elsif ($opt{f}) {
89 print "WARNING: disabling full-text index update. You need to re-run $0 -j !\n";
90 $index_node_url = undef;
91 }
92
93 #---- subs ----
94
95 sub fmt_time {
96 my $t = shift || return;
97 my $out = "";
98 my ($ss,$mm,$hh) = gmtime($t);
99 $out .= "${hh}h" if ($hh);
100 $out .= sprintf("%02d:%02d", $mm,$ss);
101 return $out;
102 }
103
104 sub curr_time {
105 return strftime($t_fmt,localtime());
106 }
107
108 my $hest_node;
109
110 sub hest_update {
111
112 my ($host_id, $share_id, $num) = @_;
113
114 my $skip_check = $opt{j} && print STDERR "Skipping check for existing files -- this should be used only with initital import\n";
115
116 unless (defined($index_node_url)) {
117 print STDERR "HyperEstraier support not enabled in configuration\n";
118 $index_node_url = 0;
119 return;
120 }
121
122 print curr_time," updating Hyper Estraier:";
123
124 my $t = time();
125
126 my $offset = 0;
127 my $added = 0;
128
129 if ($index_node_url) {
130 print " opening index $index_node_url";
131 $hest_node ||= Search::Estraier::Node->new(
132 url => $index_node_url,
133 user => 'admin',
134 passwd => 'admin',
135 croak_on_error => 1,
136 );
137 print " via node URL";
138 }
139
140 my $results = 0;
141
142 do {
143
144 my $where = '';
145 my @data;
146 if (defined($host_id) && defined($share_id) && defined($num)) {
147 $where = qq{
148 WHERE
149 hosts.id = ? AND
150 shares.id = ? AND
151 files.backupnum = ?
152 };
153 @data = ( $host_id, $share_id, $num );
154 }
155
156 my $limit = sprintf('LIMIT '.EST_CHUNK.' OFFSET %d', $offset);
157
158 my $sth = $dbh->prepare(qq{
159 SELECT
160 files.id AS fid,
161 hosts.name AS hname,
162 shares.name AS sname,
163 -- shares.share AS sharename,
164 files.backupnum AS backupnum,
165 -- files.name AS filename,
166 files.path AS filepath,
167 files.date AS date,
168 files.type AS type,
169 files.size AS size,
170 files.shareid AS shareid,
171 backups.date AS backup_date
172 FROM files
173 INNER JOIN shares ON files.shareID=shares.ID
174 INNER JOIN hosts ON hosts.ID = shares.hostID
175 INNER JOIN backups ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
176 $where
177 $limit
178 });
179
180 $sth->execute(@data);
181 $results = $sth->rows;
182
183 if ($results == 0) {
184 print " - no new files\n";
185 return;
186 } else {
187 print "...";
188 }
189
190 sub fmt_date {
191 my $t = shift || return;
192 my $iso = BackupPC::Lib::timeStamp($t);
193 $iso =~ s/\s/T/;
194 return $iso;
195 }
196
197 while (my $row = $sth->fetchrow_hashref()) {
198
199 my $uri = $row->{hname} . ':' . $row->{sname} . '#' . $row->{backupnum} . ' ' . $row->{filepath};
200 unless ($skip_check && $hest_node) {
201 my $id = $hest_node->uri_to_id($uri);
202 next if ($id && $id == -1);
203 }
204
205 # create a document object
206 my $doc = Search::Estraier::Document->new;
207
208 # add attributes to the document object
209 $doc->add_attr('@uri', $uri);
210
211 foreach my $c (@{ $sth->{NAME} }) {
212 print STDERR "attr $c = $row->{$c}\n" if ($debug > 2);
213 $doc->add_attr($c, $row->{$c}) if (defined($row->{$c}));
214 }
215
216 #$doc->add_attr('@cdate', fmt_date($row->{'date'}));
217
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);
223
224 print STDERR $doc->dump_draft,"\n" if ($debug > 1);
225
226 # register the document object to the database
227 if ($hest_node) {
228 $hest_node->put_doc($doc);
229 } else {
230 die "not supported";
231 }
232 $added++;
233 }
234
235 print "$added";
236
237 $offset += EST_CHUNK;
238
239 } while ($results == EST_CHUNK);
240
241 my $dur = (time() - $t) || 1;
242 printf(" [%.2f/s dur: %s]\n",
243 ( $added / $dur ),
244 fmt_time($dur)
245 );
246 }
247
248 #---- /subs ----
249
250
251 ## update index ##
252 if ( ( $opt{i} || $opt{j} ) && !$opt{c} ) {
253 # update all
254 print "force update of Hyper Estraier index ";
255 print "by -i flag" if ($opt{i});
256 print "by -j flag" if ($opt{j});
257 print "\n";
258 hest_update();
259 }
260
261 ## create tables ##
262 if ($opt{c}) {
263 sub do_index {
264 my $index = shift || return;
265 my ($table,$col,$unique) = split(/:/, $index);
266 $unique ||= '';
267 $index =~ s/\W+/_/g;
268 print "$index on $table($col)" . ( $unique ? "u" : "" ) . " ";
269 $dbh->do(qq{ create $unique index $index on $table($col) });
270 }
271
272 print "creating tables...\n";
273
274 $dbh->do( qq{
275 create table hosts (
276 ID SERIAL PRIMARY KEY,
277 name VARCHAR(30) NOT NULL,
278 IP VARCHAR(15)
279 );
280
281 create table shares (
282 ID SERIAL PRIMARY KEY,
283 hostID INTEGER NOT NULL references hosts(id),
284 name VARCHAR(30) NOT NULL,
285 share VARCHAR(200) NOT NULL
286 );
287
288 create table dvds (
289 ID SERIAL PRIMARY KEY,
290 num INTEGER NOT NULL,
291 name VARCHAR(255) NOT NULL,
292 mjesto VARCHAR(255)
293 );
294
295 create table backups (
296 id serial,
297 hostID INTEGER NOT NULL references hosts(id),
298 num INTEGER NOT NULL,
299 date integer NOT NULL,
300 type CHAR(4) not null,
301 shareID integer not null references shares(id),
302 size bigint not null,
303 inc_size bigint not null default -1,
304 inc_deleted boolean default false,
305 parts integer not null default 0,
306 PRIMARY KEY(id)
307 );
308
309 create table files (
310 ID SERIAL,
311 shareID INTEGER NOT NULL references shares(id),
312 backupNum INTEGER NOT NULL,
313 name VARCHAR(255) NOT NULL,
314 path VARCHAR(255) NOT NULL,
315 date integer NOT NULL,
316 type INTEGER NOT NULL,
317 size bigint NOT NULL,
318 primary key(id)
319 );
320
321 create table archive (
322 id serial,
323 dvd_nr int not null,
324 total_size bigint default -1,
325 note text,
326 username varchar(20) not null,
327 date timestamp default now(),
328 primary key(id)
329 );
330
331 create table archive_backup (
332 archive_id int not null references archive(id) on delete cascade,
333 backup_id int not null references backups(id),
334 primary key(archive_id, backup_id)
335 );
336
337 create table archive_burned (
338 archive_id int references archive(id),
339 date timestamp default now(),
340 part int not null default 1,
341 copy int not null default 1,
342 iso_size bigint default -1
343 );
344
345 create table backup_parts (
346 id serial,
347 backup_id int references backups(id),
348 part_nr int not null check (part_nr > 0),
349 tar_size bigint not null check (tar_size > 0),
350 size bigint not null check (size > 0),
351 md5 text not null,
352 items int not null check (items > 0),
353 date timestamp default now(),
354 primary key(id)
355 );
356 });
357
358 print "creating indexes: ";
359
360 foreach my $index (qw(
361 hosts:name
362 backups:hostID
363 backups:num
364 backups:shareID
365 shares:hostID
366 shares:name
367 files:shareID
368 files:path
369 files:name
370 files:date
371 files:size
372 archive:dvd_nr
373 archive_burned:archive_id
374 backup_parts:backup_id,part_nr
375 )) {
376 do_index($index);
377 }
378
379 print " creating sequence: ";
380 foreach my $seq (qw/dvd_nr/) {
381 print "$seq ";
382 $dbh->do( qq{ CREATE SEQUENCE $seq } );
383 }
384
385 print " creating triggers ";
386 $dbh->do( qq{
387 create or replace function backup_parts_check() returns trigger as '
388 declare
389 b_parts integer;
390 b_counted integer;
391 b_id integer;
392 begin
393 if (TG_OP=''UPDATE'') then
394 b_id := old.id;
395 b_parts := old.parts;
396 elsif (TG_OP = ''INSERT'') then
397 b_id := new.id;
398 b_parts := new.parts;
399 end if;
400 b_counted := (select count(*) from backup_parts where backup_id = b_id);
401 if ( b_parts != b_counted ) then
402 raise exception ''Update of backup % aborted, requested % parts and there are really % parts'', b_id, b_parts, b_counted;
403 end if;
404 return null;
405 end;
406 ' language plpgsql;
407
408 create trigger do_backup_parts_check
409 after insert or update or delete on backups
410 for each row execute procedure backup_parts_check();
411 });
412
413 print "...\n";
414
415 $dbh->commit;
416
417 }
418
419 ## delete data before inseting ##
420 if ($opt{d}) {
421 print "deleting ";
422 foreach my $table (qw(files dvds backups shares hosts)) {
423 print "$table ";
424 $dbh->do(qq{ DELETE FROM $table });
425 }
426 print " done...\n";
427
428 $dbh->commit;
429 }
430
431 ## insert new values ##
432
433 # get hosts
434 $hosts = $bpc->HostInfoRead();
435 my $hostID;
436 my $shareID;
437
438 my $sth;
439
440 $sth->{insert_hosts} = $dbh->prepare(qq{
441 INSERT INTO hosts (name, IP) VALUES (?,?)
442 });
443
444 $sth->{hosts_by_name} = $dbh->prepare(qq{
445 SELECT ID FROM hosts WHERE name=?
446 });
447
448 $sth->{backups_count} = $dbh->prepare(qq{
449 SELECT COUNT(*)
450 FROM backups
451 WHERE hostID=? AND num=? AND shareid=?
452 });
453
454 $sth->{insert_backups} = $dbh->prepare(qq{
455 INSERT INTO backups (hostID, num, date, type, shareid, size)
456 VALUES (?,?,?,?,?,-1)
457 });
458
459 $sth->{update_backups_size} = $dbh->prepare(qq{
460 UPDATE backups SET size = ?
461 WHERE hostID = ? and num = ? and date = ? and type =? and shareid = ?
462 });
463
464 $sth->{insert_files} = $dbh->prepare(qq{
465 INSERT INTO files
466 (shareID, backupNum, name, path, date, type, size)
467 VALUES (?,?,?,?,?,?,?)
468 });
469
470 my @hosts = keys %{$hosts};
471 my $host_nr = 0;
472
473 foreach my $host_key (@hosts) {
474
475 my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
476
477 $sth->{hosts_by_name}->execute($hosts->{$host_key}->{'host'});
478
479 unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
480 $sth->{insert_hosts}->execute(
481 $hosts->{$host_key}->{'host'},
482 $hosts->{$host_key}->{'ip'}
483 );
484
485 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
486 }
487
488 $host_nr++;
489 # get backups for a host
490 my @backups = $bpc->BackupInfoRead($hostname);
491 my $incs = scalar @backups;
492
493 my $host_header = sprintf("host %s [%d/%d]: %d increments\n",
494 $hosts->{$host_key}->{'host'},
495 $host_nr,
496 ($#hosts + 1),
497 $incs
498 );
499 print $host_header unless ($opt{q});
500
501 my $inc_nr = 0;
502 $beenThere = {};
503
504 foreach my $backup (@backups) {
505
506 $inc_nr++;
507 last if ($opt{m} && $inc_nr > $opt{m});
508
509 my $backupNum = $backup->{'num'};
510 my @backupShares = ();
511
512 my $share_header = sprintf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n",
513 $hosts->{$host_key}->{'host'},
514 $inc_nr, $incs, $backupNum,
515 $backup->{type} || '?',
516 $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
517 strftime($t_fmt,localtime($backup->{startTime})),
518 fmt_time($backup->{endTime} - $backup->{startTime})
519 );
520 print $share_header unless ($opt{q});
521
522 my $files = BackupPC::View->new($bpc, $hostname, \@backups, 1);
523 foreach my $share ($files->shareList($backupNum)) {
524
525 my $t = time();
526
527 $shareID = getShareID($share, $hostID, $hostname);
528
529 $sth->{backups_count}->execute($hostID, $backupNum, $shareID);
530 my ($count) = $sth->{backups_count}->fetchrow_array();
531 # skip if allready in database!
532 next if ($count > 0);
533
534 # dump host and share header for -q
535 if ($opt{q}) {
536 if ($host_header) {
537 print $host_header;
538 $host_header = undef;
539 }
540 print $share_header;
541 }
542
543 # dump some log
544 print curr_time," ", $share;
545
546 $sth->{insert_backups}->execute(
547 $hostID,
548 $backupNum,
549 $backup->{'endTime'},
550 substr($backup->{'type'},0,4),
551 $shareID,
552 );
553
554 my ($f, $nf, $d, $nd, $size) = recurseDir($bpc, $hostname, $files, $backupNum, $share, "", $shareID);
555
556 eval {
557 $sth->{update_backups_size}->execute(
558 $size,
559 $hostID,
560 $backupNum,
561 $backup->{'endTime'},
562 substr($backup->{'type'},0,4),
563 $shareID,
564 );
565 print " commit";
566 $dbh->commit();
567 };
568 if ($@) {
569 print " rollback";
570 $dbh->rollback();
571 }
572
573 my $dur = (time() - $t) || 1;
574 printf(" %d/%d files %d/%d dirs %0.2f MB [%.2f/s dur: %s]\n",
575 $nf, $f, $nd, $d,
576 ($size / 1024 / 1024),
577 ( ($f+$d) / $dur ),
578 fmt_time($dur)
579 );
580
581 hest_update($hostID, $shareID, $backupNum) if ($nf + $nd > 0);
582 }
583
584 }
585 }
586 undef $sth;
587 $dbh->commit();
588 $dbh->disconnect();
589
590 print "total duration: ",fmt_time(time() - $start_t),"\n";
591
592 $pidfile->remove;
593
594 sub getShareID() {
595
596 my ($share, $hostID, $hostname) = @_;
597
598 $sth->{share_id} ||= $dbh->prepare(qq{
599 SELECT ID FROM shares WHERE hostID=? AND name=?
600 });
601
602 $sth->{share_id}->execute($hostID,$share);
603
604 my ($id) = $sth->{share_id}->fetchrow_array();
605
606 return $id if (defined($id));
607
608 $sth->{insert_share} ||= $dbh->prepare(qq{
609 INSERT INTO shares
610 (hostID,name,share)
611 VALUES (?,?,?)
612 });
613
614 my $drop_down = $hostname . '/' . $share;
615 $drop_down =~ s#//+#/#g;
616
617 $sth->{insert_share}->execute($hostID,$share, $drop_down);
618 return $dbh->last_insert_id(undef,undef,'shares',undef);
619 }
620
621 sub found_in_db {
622
623 my @data = @_;
624 shift @data;
625
626 my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
627
628 return $beenThere->{$key} if (defined($beenThere->{$key}));
629
630 $sth->{file_in_db} ||= $dbh->prepare(qq{
631 SELECT 1 FROM files
632 WHERE shareID = ? and
633 path = ? and
634 size = ? and
635 ( date = ? or date = ? or date = ? )
636 LIMIT 1
637 });
638
639 my @param = ($shareID,$path,$size,$date, $date-$dst_offset, $date+$dst_offset);
640 $sth->{file_in_db}->execute(@param);
641 my $rows = $sth->{file_in_db}->rows;
642 print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
643
644 $beenThere->{$key}++;
645
646 $sth->{'insert_files'}->execute(@data) unless ($rows);
647 return $rows;
648 }
649
650 ####################################################
651 # recursing through filesystem structure and #
652 # and returning flattened files list #
653 ####################################################
654 sub recurseDir($$$$$$$$) {
655
656 my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
657
658 print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
659
660 my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
661
662 { # scope
663 my @stack;
664
665 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
666 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
667
668 # first, add all the entries in current directory
669 foreach my $path_key (keys %{$filesInBackup}) {
670 print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
671 my @data = (
672 $shareID,
673 $backupNum,
674 $path_key,
675 $filesInBackup->{$path_key}->{'relPath'},
676 $filesInBackup->{$path_key}->{'mtime'},
677 $filesInBackup->{$path_key}->{'type'},
678 $filesInBackup->{$path_key}->{'size'}
679 );
680
681 my $key = join(" ", (
682 $shareID,
683 $dir,
684 $path_key,
685 $filesInBackup->{$path_key}->{'mtime'},
686 $filesInBackup->{$path_key}->{'size'}
687 ));
688
689 my $key_dst_prev = join(" ", (
690 $shareID,
691 $dir,
692 $path_key,
693 $filesInBackup->{$path_key}->{'mtime'} - $dst_offset,
694 $filesInBackup->{$path_key}->{'size'}
695 ));
696
697 my $key_dst_next = join(" ", (
698 $shareID,
699 $dir,
700 $path_key,
701 $filesInBackup->{$path_key}->{'mtime'} + $dst_offset,
702 $filesInBackup->{$path_key}->{'size'}
703 ));
704
705 my $found;
706 if (
707 ! defined($beenThere->{$key}) &&
708 ! defined($beenThere->{$key_dst_prev}) &&
709 ! defined($beenThere->{$key_dst_next}) &&
710 ! ($found = found_in_db($key, @data))
711 ) {
712 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
713
714 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
715 $new_dirs++ unless ($found);
716 print STDERR " dir\n" if ($debug >= 2);
717 } else {
718 $new_files++ unless ($found);
719 print STDERR " file\n" if ($debug >= 2);
720 }
721 $size += $filesInBackup->{$path_key}->{'size'} || 0;
722 }
723
724 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
725 $nr_dirs++;
726
727 my $full_path = $dir . '/' . $path_key;
728 push @stack, $full_path;
729 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
730
731 # my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
732 #
733 # $nr_files += $f;
734 # $new_files += $nf;
735 # $nr_dirs += $d;
736 # $new_dirs += $nd;
737
738 } else {
739 $nr_files++;
740 }
741 }
742
743 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
744
745 while ( my $dir = shift @stack ) {
746 my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
747 print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
748 $nr_files += $f;
749 $new_files += $nf;
750 $nr_dirs += $d;
751 $new_dirs += $nd;
752 $size += $s;
753 }
754 }
755
756 return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);
757 }
758

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.26