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