begin search integration into version 3.2.0
[BackupPC.git] / bin / BackupPC_updatedb
1 #!/usr/bin/perl
2
3 use strict;
4 #use lib "/data/backuppc/lib";
5 use lib "/usr/share/backuppc/lib";
6
7 use DBI;
8 use BackupPC::Lib;
9 use BackupPC::View;
10 use Data::Dumper;
11 use Getopt::Std;
12 use Time::HiRes qw/time/;
13 use File::Pid;
14 use POSIX qw/strftime/;
15 use BackupPC::SearchLib;
16 use Cwd qw/abs_path/;
17 use Data::Dump qw(dump);
18
19 use constant BPC_FTYPE_DIR => 5;
20 use constant EST_CHUNK => 4096;
21
22 # daylight saving time change offset for 1h
23 my $dst_offset = 60 * 60;
24
25 my $debug = 0;
26 $|=1;
27
28 my $start_t = time();
29
30 my $pid_path = abs_path($0);
31 $pid_path =~ s/\W+/_/g;
32
33 my $pidfile = new File::Pid({
34         file => "/tmp/$pid_path",
35 });
36
37 if (my $pid = $pidfile->running ) {
38         die "$0 already running: $pid\n";
39 } elsif ($pidfile->pid ne $$) {
40         $pidfile->remove;
41         $pidfile = new File::Pid;
42 }
43 print STDERR "$0 using pid ",$pidfile->pid," file ",$pidfile->file,"\n";
44 $pidfile->write;
45
46 my $t_fmt = '%Y-%m-%d %H:%M:%S';
47
48 my $hosts;
49 my $bpc = BackupPC::Lib->new || die;
50 my %Conf = $bpc->Conf();
51 my $TopDir = $bpc->TopDir();
52 my $beenThere = {};
53
54 my $dsn = $Conf{SearchDSN} || die "Need SearchDSN in config.pl\n";
55 my $user = $Conf{SearchUser} || '';
56
57 my $index_node_url = $Conf{HyperEstraierIndex};
58
59 my $dbh = DBI->connect($dsn, $user, "", { RaiseError => 1, AutoCommit => 0 });
60
61 my %opt;
62
63 if ( !getopts("cdm:v:ijfq", \%opt ) ) {
64         print STDERR <<EOF;
65 usage: $0 [-c|-d] [-m num] [-v|-v level] [-i|-j|-f]
66
67 Options:
68         -c      create database on first use
69         -d      delete database before import
70         -m num  import just num increments for one host
71         -v num  set verbosity (debug) level (default $debug)
72         -i      update Hyper Estraier full text index
73         -j      update full text, don't check existing files
74         -f      don't do anything with full text index
75         -q      be quiet for hosts without changes
76
77 Option -j is variation on -i. It will allow faster initial creation
78 of full-text index from existing database.
79
80 Option -f will create database which is out of sync with full text index. You
81 will have to re-run $0 with -i to fix it.
82
83 EOF
84         exit 1;
85 }
86
87 if ($opt{v}) {
88         print "Debug level at $opt{v}\n";
89         $debug = $opt{v};
90 } elsif ($opt{f}) {
91         print "WARNING: disabling full-text index update. You need to re-run $0 -j !\n";
92         $index_node_url = undef;
93 }
94
95 #---- subs ----
96
97 sub fmt_time {
98         my $t = shift || return;
99         my $out = "";
100         my ($ss,$mm,$hh) = gmtime($t);
101         $out .= "${hh}h" if ($hh);
102         $out .= sprintf("%02d:%02d", $mm,$ss);
103         return $out;
104 }
105
106 sub curr_time {
107         return strftime($t_fmt,localtime());
108 }
109
110 my $hest_node;
111
112 sub hest_update {
113
114         my ($host_id, $share_id, $num) = @_;
115
116         my $skip_check = $opt{j} && print STDERR "Skipping check for existing files -- this should be used only with initital import\n";
117
118         unless ($index_node_url && $index_node_url =~ m#^http://#) {
119                 print STDERR "HyperEstraier support not enabled or index node invalid\n" if ($debug);
120                 $index_node_url = 0;
121                 return;
122         }
123
124         print curr_time," updating Hyper Estraier:";
125
126         my $t = time();
127
128         my $offset = 0;
129         my $added = 0;
130
131         if ($index_node_url) {
132                 print " opening index $index_node_url";
133                 $hest_node ||= Search::Estraier::Node->new(
134                         url => $index_node_url,
135                         user => 'admin',
136                         passwd => 'admin',
137                         croak_on_error => 1,
138                 );
139                 print " via node URL";
140         }
141
142         my $results = 0;
143
144         do {
145
146                 my $where = '';
147                 my @data;
148                 if (defined($host_id) && defined($share_id) && defined($num)) {
149                         $where = qq{
150                         WHERE
151                                 hosts.id = ? AND
152                                 shares.id = ? AND
153                                 files.backupnum = ?
154                         };
155                         @data = ( $host_id, $share_id, $num );
156                 }
157
158                 my $limit = sprintf('LIMIT '.EST_CHUNK.' OFFSET %d', $offset);
159
160                 my $sth = $dbh->prepare(qq{
161                         SELECT
162                                 files.id                        AS fid,
163                                 hosts.name                      AS hname,
164                                 shares.name                     AS sname,
165                                 -- shares.share                 AS sharename,
166                                 files.backupnum                 AS backupnum,
167                                 -- files.name                   AS filename,
168                                 files.path                      AS filepath,
169                                 files.date                      AS date,
170                                 files.type                      AS type,
171                                 files.size                      AS size,
172                                 files.shareid                   AS shareid,
173                                 backups.date                    AS backup_date
174                         FROM files 
175                                 INNER JOIN shares       ON files.shareID=shares.ID
176                                 INNER JOIN hosts        ON hosts.ID = shares.hostID
177                                 INNER JOIN backups      ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
178                         $where
179                         $limit
180                 });
181
182                 $sth->execute(@data);
183                 $results = $sth->rows;
184
185                 if ($results == 0) {
186                         print " - no new files\n";
187                         return;
188                 } else {
189                         print "...";
190                 }
191
192                 sub fmt_date {
193                         my $t = shift || return;
194                         my $iso = BackupPC::Lib::timeStamp($t);
195                         $iso =~ s/\s/T/;
196                         return $iso;
197                 }
198
199                 while (my $row = $sth->fetchrow_hashref()) {
200
201                         my $uri = $row->{hname} . ':' . $row->{sname} . '#' . $row->{backupnum} . ' ' . $row->{filepath};
202                         if (! $skip_check && $hest_node) {
203                                 my $id = $hest_node->uri_to_id($uri);
204                                 next if ($id && $id == -1);
205                         }
206
207                         # create a document object 
208                         my $doc = Search::Estraier::Document->new;
209
210                         # add attributes to the document object 
211                         $doc->add_attr('@uri', $uri);
212
213                         foreach my $c (@{ $sth->{NAME} }) {
214                                 print STDERR "attr $c = $row->{$c}\n" if ($debug > 2);
215                                 $doc->add_attr($c, $row->{$c}) if (defined($row->{$c}));
216                         }
217
218                         #$doc->add_attr('@cdate', fmt_date($row->{'date'}));
219
220                         # add the body text to the document object 
221                         my $path = $row->{'filepath'};
222                         $doc->add_text($path);
223                         $path =~ s/(.)/$1 /g;
224                         $doc->add_hidden_text($path);
225
226                         print STDERR $doc->dump_draft,"\n" if ($debug > 1);
227
228                         # register the document object to the database
229                         $hest_node->put_doc($doc) if ($hest_node);
230
231                         $added++;
232                 }
233
234                 print "$added";
235
236                 $offset += EST_CHUNK;
237
238         } while ($results == EST_CHUNK);
239
240         my $dur = (time() - $t) || 1;
241         printf(" [%.2f/s dur: %s]\n",
242                 ( $added / $dur ),
243                 fmt_time($dur)
244         );
245 }
246
247 #---- /subs ----
248
249
250 ## update index ##
251 if ( ( $opt{i} || $opt{j} ) && !$opt{c} ) {
252         # update all
253         print "force update of Hyper Estraier index ";
254         print "by -i flag" if ($opt{i});
255         print "by -j flag" if ($opt{j});
256         print "\n";
257         hest_update();
258 }
259
260 ## create tables ##
261 if ($opt{c}) {
262         sub do_index {
263                 my $index = shift || return;
264                 my ($table,$col,$unique) = split(/:/, $index);
265                 $unique ||= '';
266                 $index =~ s/\W+/_/g;
267                 print "$index on $table($col)" . ( $unique ? "u" : "" ) . " ";
268                 $dbh->do(qq{ create $unique index $index on $table($col) });
269         }
270
271         print "creating tables...\n";
272
273         $dbh->do( qq{
274                 create table hosts (
275                         ID      SERIAL          PRIMARY KEY,
276                         name    VARCHAR(30)     NOT NULL,
277                         IP      VARCHAR(15)
278                 );            
279
280                 create table shares (
281                         ID      SERIAL          PRIMARY KEY,
282                         hostID  INTEGER         NOT NULL references hosts(id),
283                         name    VARCHAR(30)     NOT NULL,
284                         share   VARCHAR(200)    NOT NULL
285                 );            
286
287                 create table dvds (
288                         ID      SERIAL          PRIMARY KEY, 
289                         num     INTEGER         NOT NULL,
290                         name    VARCHAR(255)    NOT NULL,
291                         mjesto  VARCHAR(255)
292                 );
293
294                 create table backups (
295                         id      serial,
296                         hostID  INTEGER         NOT NULL references hosts(id),
297                         num     INTEGER         NOT NULL,
298                         date    integer         NOT NULL, 
299                         type    CHAR(4)         not null,
300                         shareID integer         not null references shares(id),
301                         size    bigint          not null,
302                         inc_size bigint         not null default -1,
303                         inc_deleted boolean     default false,
304                         parts   integer         not null default 0,
305                         PRIMARY KEY(id)
306                 );            
307
308                 create table files (
309                         ID              SERIAL,
310                         shareID         INTEGER NOT NULL references shares(id),
311                         backupNum       INTEGER NOT NULL,
312                         name            VARCHAR(255) NOT NULL,
313                         path            VARCHAR(255) NOT NULL,
314                         date            integer NOT NULL,
315                         type            INTEGER NOT NULL,
316                         size            bigint  NOT NULL,
317                         primary key(id)
318                 );
319
320                 create table archive (
321                         id              serial,
322                         dvd_nr          int not null,
323                         total_size      bigint default -1,
324                         note            text,
325                         username        varchar(20) not null,
326                         date            timestamp default now(),
327                         primary key(id)
328                 );      
329
330                 create table archive_backup (
331                         archive_id      int not null references archive(id) on delete cascade,
332                         backup_id       int not null references backups(id),
333                         primary key(archive_id, backup_id)
334                 );
335
336                 create table archive_burned (
337                         archive_id      int references archive(id),
338                         date            timestamp default now(),
339                         part            int not null default 1,
340                         copy            int not null default 1,
341                         iso_size bigint default -1
342                 );
343
344                 create table backup_parts (
345                         id serial,
346                         backup_id int references backups(id),
347                         part_nr int not null check (part_nr > 0),
348                         tar_size bigint not null check (tar_size > 0),
349                         size bigint not null check (size > 0),
350                         md5 text not null,
351                         items int not null check (items > 0),
352                         date timestamp default now(),
353                         primary key(id)
354                 );
355         });
356
357         print "creating indexes: ";
358
359         foreach my $index (qw(
360                 hosts:name
361                 backups:hostID
362                 backups:num
363                 backups:shareID
364                 shares:hostID
365                 shares:name
366                 files:shareID
367                 files:path
368                 files:name
369                 files:date
370                 files:size
371                 archive:dvd_nr
372                 archive_burned:archive_id
373                 backup_parts:backup_id,part_nr:unique
374         )) {
375                 do_index($index);
376         }
377
378         print " creating sequence: ";
379         foreach my $seq (qw/dvd_nr/) {
380                 print "$seq ";
381                 $dbh->do( qq{ CREATE SEQUENCE $seq } );
382         }
383
384         print " creating triggers ";
385         $dbh->do( <<__END_OF_TRIGGER__ );
386
387 create or replace function backup_parts_check() returns trigger as '
388 declare
389         b_parts integer;
390         b_counted integer;
391         b_id    integer;
392 begin
393         -- raise notice ''old/new parts %/% backup_id %/%'', old.parts, new.parts, old.id, new.id;
394         if (TG_OP=''UPDATE'') then
395                 b_id := new.id;
396                 b_parts := new.parts;
397         elsif (TG_OP = ''INSERT'') then
398                 b_id := new.id;
399                 b_parts := new.parts;
400         end if;
401         b_counted := (select count(*) from backup_parts where backup_id = b_id);
402         -- raise notice ''backup % parts %'', b_id, b_parts;
403         if ( b_parts != b_counted ) then
404                 raise exception ''Update of backup % aborted, requested % parts and there are really % parts'', b_id, b_parts, b_counted;
405         end if;
406         return null;
407 end;
408 ' language plpgsql;
409
410 create trigger do_backup_parts_check
411         after insert or update or delete on backups
412         for each row execute procedure backup_parts_check();
413
414 create or replace function backup_backup_parts_check() returns trigger as '
415 declare
416         b_id            integer;
417         my_part_nr      integer;
418         calc_part       integer;
419 begin
420         if (TG_OP = ''INSERT'') then
421                 -- raise notice ''trigger: % backup_id %'', TG_OP, new.backup_id;
422                 b_id = new.backup_id;
423                 my_part_nr = new.part_nr;
424                 execute ''update backups set parts = parts + 1 where id = '' || b_id;
425         elsif (TG_OP = ''DELETE'') then
426                 -- raise notice ''trigger: % backup_id %'', TG_OP, old.backup_id;
427                 b_id = old.backup_id;
428                 my_part_nr = old.part_nr;
429                 execute ''update backups set parts = parts - 1 where id = '' || b_id;
430         end if;
431         calc_part := (select count(part_nr) from backup_parts where backup_id = b_id);
432         if ( my_part_nr != calc_part ) then
433                 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;
434         end if;
435         return null;
436 end;
437 ' language plpgsql;
438
439 create trigger do_backup_backup_parts_check
440         after insert or update or delete on backup_parts
441         for each row execute procedure backup_backup_parts_check();
442
443 __END_OF_TRIGGER__
444
445         print "...\n";
446
447         $dbh->commit;
448
449 }
450
451 ## delete data before inseting ##
452 if ($opt{d}) {
453         print "deleting ";
454         foreach my $table (qw(files dvds backups shares hosts)) {
455                 print "$table ";
456                 $dbh->do(qq{ DELETE FROM $table });
457         }
458         print " done...\n";
459
460         $dbh->commit;
461 }
462
463 ## insert new values ##
464
465 # get hosts
466 $hosts = $bpc->HostInfoRead();
467 my $hostID;
468 my $shareID;
469
470 my $sth;
471
472 $sth->{insert_hosts} = $dbh->prepare(qq{
473 INSERT INTO hosts (name, IP) VALUES (?,?)
474 });
475
476 $sth->{hosts_by_name} = $dbh->prepare(qq{
477 SELECT ID FROM hosts WHERE name=?
478 });
479
480 $sth->{backups_count} = $dbh->prepare(qq{
481 SELECT COUNT(*)
482 FROM backups
483 WHERE hostID=? AND num=? AND shareid=?
484 });
485
486 $sth->{insert_backups} = $dbh->prepare(qq{
487 INSERT INTO backups (hostID, num, date, type, shareid, size)
488 VALUES (?,?,?,?,?,-1)
489 });
490
491 $sth->{update_backups_size} = $dbh->prepare(qq{
492 UPDATE backups SET size = ?
493 WHERE hostID = ? and num = ? and date = ? and type =? and shareid = ?
494 });
495
496 $sth->{insert_files} = $dbh->prepare(qq{
497 INSERT INTO files
498         (shareID, backupNum, name, path, date, type, size)
499         VALUES (?,?,?,?,?,?,?)
500 });
501
502 my @hosts = keys %{$hosts};
503 my $host_nr = 0;
504
505 foreach my $host_key (@hosts) {
506
507         my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
508
509         $sth->{hosts_by_name}->execute($hosts->{$host_key}->{'host'});
510
511         unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
512                 $sth->{insert_hosts}->execute(
513                         $hosts->{$host_key}->{'host'},
514                         $hosts->{$host_key}->{'ip'}
515                 );
516
517                 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
518         }
519
520         $host_nr++;
521         # get backups for a host
522         my @backups = $bpc->BackupInfoRead($hostname);
523 warn "XXXX ",dump(@backups);
524         my $incs = scalar @backups;
525
526         my $host_header = sprintf("host %s [%d/%d]: %d increments\n",
527                 $hosts->{$host_key}->{'host'},
528                 $host_nr,
529                 ($#hosts + 1),
530                 $incs
531         );
532         print $host_header unless ($opt{q});
533  
534         my $inc_nr = 0;
535         $beenThere = {};
536
537         foreach my $backup (@backups) {
538
539                 $inc_nr++;
540                 last if ($opt{m} && $inc_nr > $opt{m});
541
542                 my $backupNum = $backup->{'num'};
543                 my @backupShares = ();
544
545                 my $share_header = sprintf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n", 
546                         $hosts->{$host_key}->{'host'},
547                         $inc_nr, $incs, $backupNum, 
548                         $backup->{type} || '?',
549                         $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
550                         strftime($t_fmt,localtime($backup->{startTime})),
551                         fmt_time($backup->{endTime} - $backup->{startTime})
552                 );
553                 print $share_header unless ($opt{q});
554
555                 my $files = BackupPC::View->new($bpc, $hostname, \@backups, 1);
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