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