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