fix
[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                         primary key(id)
308                 );
309
310                 -- report backups and corresponding dvd
311
312                 create view backups_on_dvds as
313                 select
314                         backups.id as id,
315                         hosts.name || ':' || shares.name as share,
316                         backups.num as num,
317                         backups.type as type,
318                         abstime(backups.date) as backup_date,
319                         backups.size as size,
320                         backups.inc_size as gzip_size,
321                         archive.id as archive_id,
322                         archive.dvd_nr
323                 from backups
324                 join shares on backups.shareid=shares.id
325                 join hosts on shares.hostid = hosts.id
326                 left outer join archive_backup on backups.id = archive_backup.backup_id
327                 left outer join archive on archive_backup.archive_id = archive.id
328                 where backups.parts > 0 and size > 0
329                 order by backups.date
330                 ;
331         });
332
333         print "creating indexes: ";
334
335         foreach my $index (qw(
336                 hosts:name
337                 backups:hostID
338                 backups:num
339                 backups:shareID
340                 shares:hostID
341                 shares:name
342                 files:shareID
343                 files:path
344                 files:name
345                 files:date
346                 files:size
347                 archive:dvd_nr
348                 archive_burned:archive_id
349                 backup_parts:backup_id,part_nr:unique
350         )) {
351                 do_index($index);
352         }
353
354         print " creating sequence: ";
355         foreach my $seq (qw/dvd_nr/) {
356                 print "$seq ";
357                 $dbh->do( qq{ CREATE SEQUENCE $seq } );
358         }
359
360 =for later
361
362         print " creating triggers ";
363         $dbh->do( <<__END_OF_TRIGGER__ );
364
365 create or replace function backup_parts_check() returns trigger as '
366 declare
367         b_parts integer;
368         b_counted integer;
369         b_id    integer;
370 begin
371         -- raise notice ''old/new parts %/% backup_id %/%'', old.parts, new.parts, old.id, new.id;
372         if (TG_OP=''UPDATE'') then
373                 b_id := new.id;
374                 b_parts := new.parts;
375         elsif (TG_OP = ''INSERT'') then
376                 b_id := new.id;
377                 b_parts := new.parts;
378         end if;
379         b_counted := (select count(*) from backup_parts where backup_id = b_id);
380         -- raise notice ''backup % parts %'', b_id, b_parts;
381         if ( b_parts != b_counted ) then
382                 raise exception ''Update of backup % aborted, requested % parts and there are really % parts'', b_id, b_parts, b_counted;
383         end if;
384         return null;
385 end;
386 ' language plpgsql;
387
388 create trigger do_backup_parts_check
389         after insert or update or delete on backups
390         for each row execute procedure backup_parts_check();
391
392 create or replace function backup_backup_parts_check() returns trigger as '
393 declare
394         b_id            integer;
395         my_part_nr      integer;
396         calc_part       integer;
397 begin
398         if (TG_OP = ''INSERT'') then
399                 -- raise notice ''trigger: % backup_id %'', TG_OP, new.backup_id;
400                 b_id = new.backup_id;
401                 my_part_nr = new.part_nr;
402                 execute ''update backups set parts = parts + 1 where id = '' || b_id;
403         elsif (TG_OP = ''DELETE'') then
404                 -- raise notice ''trigger: % backup_id %'', TG_OP, old.backup_id;
405                 b_id = old.backup_id;
406                 my_part_nr = old.part_nr;
407                 execute ''update backups set parts = parts - 1 where id = '' || b_id;
408         end if;
409         calc_part := (select count(part_nr) from backup_parts where backup_id = b_id);
410         if ( my_part_nr != calc_part ) then
411                 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;
412         end if;
413         return null;
414 end;
415 ' language plpgsql;
416
417 create trigger do_backup_backup_parts_check
418         after insert or update or delete on backup_parts
419         for each row execute procedure backup_backup_parts_check();
420
421 __END_OF_TRIGGER__
422
423 =cut
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                         }
604                 }
605
606         }
607 }
608 undef $sth;
609 $dbh->commit();
610 $dbh->disconnect();
611
612 print "total duration: ",fmt_time(time() - $start_t),"\n";
613
614 $pidfile->remove;
615
616 sub getShareID() {
617
618         my ($share, $hostID, $hostname) = @_;
619
620         $sth->{share_id} ||= $dbh->prepare(qq{
621                 SELECT ID FROM shares WHERE hostID=? AND name=?
622         });
623
624         $sth->{share_id}->execute($hostID,$share);
625
626         my ($id) = $sth->{share_id}->fetchrow_array();
627
628         return $id if (defined($id));
629
630         $sth->{insert_share} ||= $dbh->prepare(qq{
631                 INSERT INTO shares 
632                         (hostID,name,share) 
633                 VALUES (?,?,?)
634         });
635
636         my $drop_down = $hostname . '/' . $share;
637         $drop_down =~ s#//+#/#g;
638
639         $sth->{insert_share}->execute($hostID,$share, $drop_down);
640         return $dbh->last_insert_id(undef,undef,'shares',undef);
641 }
642
643 sub found_in_db {
644
645         my @data = @_;
646         shift @data;
647
648         my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
649
650         return $beenThere->{$key} if (defined($beenThere->{$key}));
651
652         $sth->{file_in_db} ||= $dbh->prepare(qq{
653                 SELECT 1 FROM files
654                 WHERE shareID = ? and
655                         path = ? and 
656                         size = ? and
657                         ( date = ? or date = ? or date = ? )
658                 LIMIT 1
659         });
660
661         my @param = ($shareID,$path,$size,$date, $date-$dst_offset, $date+$dst_offset);
662         $sth->{file_in_db}->execute(@param);
663         my $rows = $sth->{file_in_db}->rows;
664         print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
665
666         $beenThere->{$key}++;
667
668         $sth->{'insert_files'}->execute(@data) unless ($rows);
669         return $rows;
670 }
671
672 ####################################################
673 # recursing through filesystem structure and       #
674 # and returning flattened files list               #
675 ####################################################
676 sub recurseDir($$$$$$$$) {
677
678         my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
679
680         print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
681
682         my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
683
684         { # scope
685                 my @stack;
686
687                 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
688                 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
689
690                 # first, add all the entries in current directory
691                 foreach my $path_key (keys %{$filesInBackup}) {
692                         print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
693                         my @data = (
694                                 $shareID,
695                                 $backupNum,
696                                 $path_key,
697                                 $filesInBackup->{$path_key}->{'relPath'},
698                                 $filesInBackup->{$path_key}->{'mtime'},
699                                 $filesInBackup->{$path_key}->{'type'},
700                                 $filesInBackup->{$path_key}->{'size'}
701                         );
702
703                         my $key = join(" ", (
704                                 $shareID,
705                                 $dir,
706                                 $path_key,
707                                 $filesInBackup->{$path_key}->{'mtime'},
708                                 $filesInBackup->{$path_key}->{'size'}
709                         ));
710
711                         my $key_dst_prev = join(" ", (
712                                 $shareID,
713                                 $dir,
714                                 $path_key,
715                                 $filesInBackup->{$path_key}->{'mtime'} - $dst_offset,
716                                 $filesInBackup->{$path_key}->{'size'}
717                         ));
718
719                         my $key_dst_next = join(" ", (
720                                 $shareID,
721                                 $dir,
722                                 $path_key,
723                                 $filesInBackup->{$path_key}->{'mtime'} + $dst_offset,
724                                 $filesInBackup->{$path_key}->{'size'}
725                         ));
726
727                         my $found;
728                         if (
729                                 ! defined($beenThere->{$key}) &&
730                                 ! defined($beenThere->{$key_dst_prev}) &&
731                                 ! defined($beenThere->{$key_dst_next}) &&
732                                 ! ($found = found_in_db($key, @data))
733                         ) {
734                                 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
735
736                                 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
737                                         $new_dirs++ unless ($found);
738                                         print STDERR " dir\n" if ($debug >= 2);
739                                 } else {
740                                         $new_files++ unless ($found);
741                                         print STDERR " file\n" if ($debug >= 2);
742                                 }
743                                 $size += $filesInBackup->{$path_key}->{'size'} || 0;
744                         }
745
746                         if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
747                                 $nr_dirs++;
748
749                                 my $full_path = $dir . '/' . $path_key;
750                                 push @stack, $full_path;
751                                 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
752
753 #                               my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
754 #
755 #                               $nr_files += $f;
756 #                               $new_files += $nf;
757 #                               $nr_dirs += $d;
758 #                               $new_dirs += $nd;
759
760                         } else {
761                                 $nr_files++;
762                         }
763                 }
764
765                 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
766
767                 while ( my $dir = shift @stack ) {
768                         my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
769                         print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
770                         $nr_files += $f;
771                         $new_files += $nf;
772                         $nr_dirs += $d;
773                         $new_dirs += $nd;
774                         $size += $s;
775                 }
776         }
777
778         return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);
779 }
780