begin rewrite to select backup_parts
[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_id       int not null references backups(id),
300                         backup_part_id  int not null references backup_parts(id),
301                         primary key(archive_id, backup_id, backup_part_id)
302                 );
303
304                 create table archive_burned (
305                         archive_id      int references archive(id),
306                         date            timestamp default now(),
307                         part            int not null default 1,
308                         copy            int not null default 1,
309                         iso_size bigint default -1
310                 );
311
312                 -- report backups and corresponding dvd
313 --
314 --              create view backups_on_dvds as
315 --              select
316 --                      backups.id as id,
317 --                      hosts.name || ':' || shares.name as share,
318 --                      backups.num as num,
319 --                      backups.type as type,
320 --                      abstime(backups.date) as backup_date,
321 --                      backups.size as size,
322 --                      backups.inc_size as gzip_size,
323 --                      archive.id as archive_id,
324 --                      archive.dvd_nr
325 --              from backups
326 --              join shares on backups.shareid=shares.id
327 --              join hosts on shares.hostid = hosts.id
328 --              left outer join archive_backup on backups.id = archive_backup.backup_id
329 --              left outer join archive on archive_backup.archive_id = archive.id
330 --              where backups.parts > 0 and size > 0
331 --              order by backups.date
332 --              ;
333         })) {
334                 warn "SQL: $sql\n";
335                 $dbh->do( $sql );
336         }
337
338         print "creating indexes: ";
339
340         foreach my $index (qw(
341                 hosts:name
342                 backups:hostID
343                 backups:num
344                 backups:shareID
345                 shares:hostID
346                 shares:name
347                 files:shareID
348                 files:path
349                 files:name
350                 files:date
351                 files:size
352                 archive:dvd_nr
353                 archive_burned:archive_id
354                 backup_parts:backup_id,part_nr:unique
355         )) {
356                 do_index($index);
357         }
358
359         print " creating sequence: ";
360         foreach my $seq (qw/dvd_nr/) {
361                 print "$seq ";
362                 $dbh->do( qq{ CREATE SEQUENCE $seq } );
363         }
364
365         print " creating triggers ";
366         $dbh->do( <<__END_OF_TRIGGER__ );
367
368 create or replace function backup_parts_check() returns trigger as '
369 declare
370         b_parts integer;
371         b_counted integer;
372         b_id    integer;
373 begin
374         -- raise notice ''old/new parts %/% backup_id %/%'', old.parts, new.parts, old.id, new.id;
375         if (TG_OP=''UPDATE'') then
376                 b_id := new.id;
377                 b_parts := new.parts;
378         elsif (TG_OP = ''INSERT'') then
379                 b_id := new.id;
380                 b_parts := new.parts;
381         end if;
382         b_counted := (select count(*) from backup_parts where backup_id = b_id);
383         -- raise notice ''backup % parts %'', b_id, b_parts;
384         if ( b_parts != b_counted ) then
385                 raise exception ''Update of backup % aborted, requested % parts and there are really % parts'', b_id, b_parts, b_counted;
386         end if;
387         return null;
388 end;
389 ' language plpgsql;
390
391 create trigger do_backup_parts_check
392         after insert or update or delete on backups
393         for each row execute procedure backup_parts_check();
394
395 create or replace function backup_backup_parts_check() returns trigger as '
396 declare
397         b_id            integer;
398         my_part_nr      integer;
399         calc_part       integer;
400 begin
401         if (TG_OP = ''INSERT'') then
402                 -- raise notice ''trigger: % backup_id %'', TG_OP, new.backup_id;
403                 b_id = new.backup_id;
404                 my_part_nr = new.part_nr;
405                 execute ''update backups set parts = parts + 1 where id = '' || b_id;
406         elsif (TG_OP = ''DELETE'') then
407                 -- raise notice ''trigger: % backup_id %'', TG_OP, old.backup_id;
408                 b_id = old.backup_id;
409                 my_part_nr = old.part_nr;
410                 execute ''update backups set parts = parts - 1 where id = '' || b_id;
411         end if;
412         calc_part := (select count(part_nr) from backup_parts where backup_id = b_id);
413         if ( my_part_nr != calc_part ) then
414                 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;
415         end if;
416         return null;
417 end;
418 ' language plpgsql;
419
420 create trigger do_backup_backup_parts_check
421         after insert or update or delete on backup_parts
422         for each row execute procedure backup_backup_parts_check();
423
424 __END_OF_TRIGGER__
425
426         print "...\n";
427
428         $dbh->commit;
429
430 }
431
432 ## delete data before inseting ##
433 if ($opt->delete) {
434         print "deleting ";
435         foreach my $table (qw(files dvds backups shares hosts)) {
436                 print "$table ";
437                 $dbh->do(qq{ DELETE FROM $table });
438         }
439         print " done...\n";
440
441         $dbh->commit;
442 }
443
444 ## insert new values ##
445
446 # get hosts
447 $hosts = $bpc->HostInfoRead();
448 my $hostID;
449 my $shareID;
450
451 my $sth;
452
453 $sth->{insert_hosts} = $dbh->prepare(qq{
454 INSERT INTO hosts (name, IP) VALUES (?,?)
455 });
456
457 $sth->{hosts_by_name} = $dbh->prepare(qq{
458 SELECT id FROM hosts WHERE name=?
459 });
460
461 $sth->{backups_count} = $dbh->prepare(qq{
462 SELECT COUNT(*)
463 FROM backups
464 WHERE hostID=? AND num=? AND shareid=?
465 });
466
467 $sth->{insert_backups} = $dbh->prepare(qq{
468 INSERT INTO backups (hostID, num, date, type, shareid, size)
469 VALUES (?,?,?,?,?,-1)
470 });
471
472 $sth->{update_backups_size} = $dbh->prepare(qq{
473 UPDATE backups SET size = ?
474 WHERE hostID = ? and num = ? and date = ? and type =? and shareid = ?
475 });
476
477 $sth->{insert_files} = $dbh->prepare(qq{
478 INSERT INTO files
479         (shareID, backupNum, name, path, date, type, size)
480         VALUES (?,?,?,?,?,?,?)
481 });
482
483 my @hosts = keys %{$hosts};
484 my $host_nr = 0;
485
486 foreach my $host_key (@hosts) {
487
488         my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
489
490         next if $opt->host && ! grep { m/^$hostname$/ } @{ $opt->host };
491
492         $sth->{hosts_by_name}->execute($hostname);
493
494         unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
495                 $sth->{insert_hosts}->execute(
496                         $hosts->{$host_key}->{'host'},
497                         $hosts->{$host_key}->{'ip'}
498                 );
499
500                 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
501         }
502
503         $host_nr++;
504         # get backups for a host
505         my @backups = $bpc->BackupInfoRead($hostname);
506         my $incs = scalar @backups;
507
508         my $host_header = sprintf("host %s [%d/%d]: %d increments\n",
509                 $hosts->{$host_key}->{'host'},
510                 $host_nr,
511                 ($#hosts + 1),
512                 $incs
513         );
514         print $host_header unless $opt->quiet;
515  
516         my $inc_nr = 0;
517         $beenThere = {};
518
519         foreach my $backup (@backups) {
520
521                 $inc_nr++;
522                 last if defined $opt->max && $inc_nr > $opt->max;
523
524                 my $backupNum = $backup->{'num'};
525                 my @backupShares = ();
526
527                 my $share_header = sprintf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n", 
528                         $hosts->{$host_key}->{'host'},
529                         $inc_nr, $incs, $backupNum, 
530                         $backup->{type} || '?',
531                         $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
532                         strftime($t_fmt,localtime($backup->{startTime})),
533                         fmt_time($backup->{endTime} - $backup->{startTime})
534                 );
535                 print $share_header unless $opt->quiet;
536                 status "$hostname $backupNum $share_header";
537
538                 my $files = BackupPC::View->new($bpc, $hostname, \@backups, { only_increment => 1 });
539
540                 foreach my $share ($files->shareList($backupNum)) {
541
542                         my $t = time();
543
544                         $shareID = getShareID($share, $hostID, $hostname);
545                 
546                         $sth->{backups_count}->execute($hostID, $backupNum, $shareID);
547                         my ($count) = $sth->{backups_count}->fetchrow_array();
548                         # skip if allready in database!
549                         next if ($count > 0);
550
551                         # dump host and share header for -q
552                         if ( $opt->quiet ) {
553                                 if ($host_header) {
554                                         print $host_header;
555                                         $host_header = undef;
556                                 }
557                                 print $share_header;
558                         }
559
560                         # dump some log
561                         print curr_time," ", $share;
562
563                         $sth->{insert_backups}->execute(
564                                 $hostID,
565                                 $backupNum,
566                                 $backup->{'endTime'},
567                                 substr($backup->{'type'},0,4),
568                                 $shareID,
569                         );
570
571                         my ($f, $nf, $d, $nd, $size) = recurseDir($bpc, $hostname, $files, $backupNum, $share, "", $shareID);
572
573                         eval {
574                                 $sth->{update_backups_size}->execute(
575                                         $size,
576                                         $hostID,
577                                         $backupNum,
578                                         $backup->{'endTime'},
579                                         substr($backup->{'type'},0,4),
580                                         $shareID,
581                                 );
582                                 print " commit";
583                                 $dbh->commit();
584                         };
585                         if ($@) {
586                                 print " rollback";
587                                 $dbh->rollback();
588                         }
589
590                         my $dur = (time() - $t) || 1;
591                         my $status = sprintf("%d/%d files %d/%d dirs %0.2f MB [%.2f/s dur: %s]",
592                                 $nf, $f, $nd, $d,
593                                 ($size / 1024 / 1024),
594                                 ( ($f+$d) / $dur ),
595                                 fmt_time($dur)
596                         );
597                         print " $status\n";
598                         status "$hostname $backupNum $status";
599
600                         if ($nf + $nd > 0) {
601                                 status "$hostname $backupNum full-text | indexing";
602                                 #eval { hest_update($hostID, $shareID, $backupNum) };
603                                 #warn "ERROR: $@" if $@;
604                                 hest_update($hostID, $shareID, $backupNum);
605                                 # eval breaks our re-try logic
606                         }
607                 }
608
609         }
610 }
611 undef $sth;
612 $dbh->commit();
613 $dbh->disconnect();
614
615 print "total duration: ",fmt_time(time() - $start_t),"\n";
616
617 $pidfile->remove;
618
619 sub getShareID() {
620
621         my ($share, $hostID, $hostname) = @_;
622
623         $sth->{share_id} ||= $dbh->prepare(qq{
624                 SELECT ID FROM shares WHERE hostID=? AND name=?
625         });
626
627         $sth->{share_id}->execute($hostID,$share);
628
629         my ($id) = $sth->{share_id}->fetchrow_array();
630
631         return $id if (defined($id));
632
633         $sth->{insert_share} ||= $dbh->prepare(qq{
634                 INSERT INTO shares 
635                         (hostID,name,share) 
636                 VALUES (?,?,?)
637         });
638
639         my $drop_down = $hostname . '/' . $share;
640         $drop_down =~ s#//+#/#g;
641
642         $sth->{insert_share}->execute($hostID,$share, $drop_down);
643         return $dbh->last_insert_id(undef,undef,'shares',undef);
644 }
645
646 sub found_in_db {
647
648         my @data = @_;
649         shift @data;
650
651         my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
652
653         return $beenThere->{$key} if (defined($beenThere->{$key}));
654
655         $sth->{file_in_db} ||= $dbh->prepare(qq{
656                 SELECT 1 FROM files
657                 WHERE shareID = ? and
658                         path = ? and 
659                         size = ? and
660                         ( date = ? or date = ? or date = ? )
661                 LIMIT 1
662         });
663
664         my @param = ($shareID,$path,$size,$date, $date-$dst_offset, $date+$dst_offset);
665         $sth->{file_in_db}->execute(@param);
666         my $rows = $sth->{file_in_db}->rows;
667         print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
668
669         $beenThere->{$key}++;
670
671         $sth->{'insert_files'}->execute(@data) unless ($rows);
672         return $rows;
673 }
674
675 ####################################################
676 # recursing through filesystem structure and       #
677 # and returning flattened files list               #
678 ####################################################
679 sub recurseDir($$$$$$$$) {
680
681         my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
682
683         print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
684
685         my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
686
687         { # scope
688                 my @stack;
689
690                 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
691                 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
692
693                 # first, add all the entries in current directory
694                 foreach my $path_key (keys %{$filesInBackup}) {
695                         print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
696                         my @data = (
697                                 $shareID,
698                                 $backupNum,
699                                 $path_key,
700                                 $filesInBackup->{$path_key}->{'relPath'},
701                                 $filesInBackup->{$path_key}->{'mtime'},
702                                 $filesInBackup->{$path_key}->{'type'},
703                                 $filesInBackup->{$path_key}->{'size'}
704                         );
705
706                         my $key = join(" ", (
707                                 $shareID,
708                                 $dir,
709                                 $path_key,
710                                 $filesInBackup->{$path_key}->{'mtime'},
711                                 $filesInBackup->{$path_key}->{'size'}
712                         ));
713
714                         my $key_dst_prev = join(" ", (
715                                 $shareID,
716                                 $dir,
717                                 $path_key,
718                                 $filesInBackup->{$path_key}->{'mtime'} - $dst_offset,
719                                 $filesInBackup->{$path_key}->{'size'}
720                         ));
721
722                         my $key_dst_next = join(" ", (
723                                 $shareID,
724                                 $dir,
725                                 $path_key,
726                                 $filesInBackup->{$path_key}->{'mtime'} + $dst_offset,
727                                 $filesInBackup->{$path_key}->{'size'}
728                         ));
729
730                         my $found;
731                         if (
732                                 ! defined($beenThere->{$key}) &&
733                                 ! defined($beenThere->{$key_dst_prev}) &&
734                                 ! defined($beenThere->{$key_dst_next}) &&
735                                 ! ($found = found_in_db($key, @data))
736                         ) {
737                                 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
738
739                                 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
740                                         $new_dirs++ unless ($found);
741                                         print STDERR " dir\n" if ($debug >= 2);
742                                 } else {
743                                         $new_files++ unless ($found);
744                                         print STDERR " file\n" if ($debug >= 2);
745                                 }
746                                 $size += $filesInBackup->{$path_key}->{'size'} || 0;
747                         }
748
749                         if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
750                                 $nr_dirs++;
751
752                                 my $full_path = $dir . '/' . $path_key;
753                                 push @stack, $full_path;
754                                 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
755
756 #                               my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
757 #
758 #                               $nr_files += $f;
759 #                               $new_files += $nf;
760 #                               $nr_dirs += $d;
761 #                               $new_dirs += $nd;
762
763                         } else {
764                                 $nr_files++;
765                         }
766                 }
767
768                 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
769
770                 while ( my $dir = shift @stack ) {
771                         my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
772                         print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
773                         $nr_files += $f;
774                         $new_files += $nf;
775                         $nr_dirs += $d;
776                         $new_dirs += $nd;
777                         $size += $s;
778                 }
779         }
780
781         return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);
782 }
783