Merge branch 'master' into squeeze
[debian/amanda] / perl / Amanda / Curinfo / Info.pm
diff --git a/perl/Amanda/Curinfo/Info.pm b/perl/Amanda/Curinfo/Info.pm
new file mode 100644 (file)
index 0000000..f05e0da
--- /dev/null
@@ -0,0 +1,552 @@
+# Copyright (c) 2010 Zmanda, Inc.  All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
+# Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
+
+
+=head1 NAME
+
+Amanda::Curinfo::Info - Perl extension for representing dump
+information
+
+=head1 SYNOPSIS
+
+   use Amanda::Curinfo::Info;
+
+   my $info = Amanda::Curinfo::Info->new($infofile);
+
+=head1 DESCRIPTION
+
+C<Amanda::Curinfo::Info> is the format representation for the curinfo
+database.  It handles the reading and writing of the individual
+entries, while the entry management is left to C<Amanda::Curinfo>.
+Further parsing is also dispatched to C<Amanda::Curinfo::History>,
+C<Amanda::Curinfo::Stats>, and C<Amanda::Curinfo::Perf>.
+
+=head1 INTERFACE
+
+The constructor for a new info object is very simple.
+
+   my $info = Amanda::Curinfo::Info->new();
+
+Will return an empty info object with the necessary fields all blank.
+
+Given an existing C<$info> object, for example, as provided by
+C<Amanda::Curinfo::get_info>, there are other functions present in this
+library, but they are helper functions to the previously described
+methods, and not to be used directly.
+
+It should also be noted that the reading and writing methods of
+C<Amanda::Curinfo::Info> are not meant to be used directly, and should be
+left to L<Amanda::Curinfo>.
+
+Reading a previously stored info object is handled with the same
+subroutine.
+
+   my $info = Amanda::Curinfo::Info->new($infofile);
+
+Here, C<$info> will contain all the information that was stored in
+C<$infofile>.
+
+To write the file to a new location, use the following command:
+
+   $info->write_to_file($infofile);
+
+There are also three corresponding container classes that hold data
+and perform parsing functions.  They should only be used when actually
+writing info file data.
+
+   my $history =
+     Amanda::Curinfo::History->new( $level, $size, $csize, $date, $secs );
+   my $stats =
+     Amanda::Curinfo::Stats->new( $level, $size, $csize, $secs, $date, $filenum,
+       $label );
+
+   my $perf = Amanda::Curinfo::Perf->new();
+   $perf->set_rate( $pct1, $pct2, $pct3 );
+   $perf->set_comp( $dbl1, $dbl2, $dbl3 );
+
+Note that C<Amanda::Curinfo::Perf> is different.  This is because its
+structure is broken up into two lines in the infofile format, and the
+length of the C<rate> and C<comp> arrays maybe subject to change in
+the future.
+
+You can also instantiate these objects directly from a
+properly-formatted line in an infofile:
+
+   my $history = Amanda::Curinfo::History->from_line($hist_line);
+   my $stats   = Amanda::Curinfo::Stats->from_line($stat_line);
+
+   my $perf = Amanda::Curinfo::Perf->new();
+   $perf->set_rate_from_line($rate_line);
+   $perf->set_comp_from_line($comp_line);
+
+Again, creating C<Amanda::Curinfo::Perf> is broken into two calls
+because its object appears on two lines.
+
+Writing these objects back to the info file, however, are all identical:
+
+   print $infofh $history->to_line();
+   print $infofh $stats->to_line();
+   print $infofh $perf_full->to_line("full");
+   print $infofh $perf_incr->to_line("incr");
+
+Additionally, the C<$perf> object accepts a prefix to the line.
+
+=head1 SEE ALSO
+
+This package is meant to replace the file reading and writing portions
+of server-src/infofile.h.  If you notice any bugs or compatibility
+issues, please report them.
+
+=head1 AUTHOR
+
+Paul C. Mantz E<lt>pcmantz@zmanda.comE<gt>
+
+=cut
+
+my $numdot = qr{[.\d]};
+
+package Amanda::Curinfo::Info;
+
+use strict;
+use warnings;
+use Carp;
+
+use Amanda::Config;
+
+sub new
+{
+    my ($class, $infofile) = @_;
+
+    my $self = {
+        command => undef,
+        full    => Amanda::Curinfo::Perf->new(),
+        incr    => Amanda::Curinfo::Perf->new(),
+        inf              => [],      # contains Amanda::Curinfo::Stats
+        history          => [],      # contains Amanda::Curinfo::History
+        last_level       => undef,
+        consecutive_runs => undef,
+    };
+
+    bless $self, $class;
+    $self->read_infofile($infofile) if -e $infofile;
+
+    return $self;
+}
+
+sub get_dumpdate
+{
+    my ( $self, $level ) = @_;
+    my $inf  = $self->{inf};
+    my $date = 0;            # Ideally should be set to the epoch, but 0 is fine
+
+    for ( my $l = 0 ; $l < $level ; $l++ ) {
+
+        my $this_date = $inf->[$l]->{date};
+        $date = $this_date if ( $this_date > $date );
+    }
+
+    my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
+      gmtime $date;
+
+    my $dumpdate = sprintf(
+        '%d:%d:%d:%d:%d:%d',
+        $year + 1900,
+        $mon + 1, $mday, $hour, $min, $sec
+    );
+
+    return $dumpdate;
+}
+
+sub read_infofile
+{
+    my ( $self, $infofile ) = @_;
+
+    open my $fh, "<", $infofile or croak "couldn't open $infofile: $!";
+
+    ## read in the fixed-length data
+    $self->read_infofile_perfs($fh);
+
+    ## read in the stats data
+    $self->read_infofile_stats($fh);
+
+    ## read in the history data
+    $self->read_infofile_history($fh);
+
+    close $fh;
+
+    return 1;
+}
+
+sub read_infofile_perfs
+{
+    my ($self, $fh) = @_;
+
+    my $fail = sub {
+        my ($line) = @_;
+        croak "error: malformed infofile header in $self->infofile:$line\n";
+    };
+
+    my $skip_blanks = sub {
+        my $line = "";
+        while ($line eq "") {
+            croak "error: infofile ended prematurely" if eof($fh);
+            $line = <$fh>;
+        }
+        return $line;
+    };
+
+    # version not paid attention to right now
+    my $line = $skip_blanks->();
+    ($line =~ /^version: ($numdot+)/) ? 1 : $fail->($line);
+
+    $line = $skip_blanks->();
+    ($line =~ /^command: ($numdot+)/) ? $self->{command} = $1 : $fail->($line);
+
+    $line = $skip_blanks->();
+    ($line =~ /^full-rate: ($numdot+) ($numdot+) ($numdot+)/)
+      ? $self->{full}->set_rate($1, $2, $3)
+      : $fail->($line);
+
+    $line = $skip_blanks->();
+    ($line =~ /^full-comp: ($numdot+) ($numdot+) ($numdot+)/)
+      ? $self->{full}->set_comp($1, $2, $3)
+      : $fail->($line);
+
+    $line = $skip_blanks->();
+    ($line =~ /^incr-rate: ($numdot+) ($numdot+) ($numdot+)/)
+      ? $self->{incr}->set_rate($1, $2, $3)
+      : $fail->($line);
+
+    $line = $skip_blanks->();
+    ($line =~ /^incr-comp: ($numdot+) ($numdot+) ($numdot+)/)
+      ? $self->{incr}->set_comp($1, $2, $3)
+      : $fail->($line);
+
+    return 1;
+}
+
+sub read_infofile_stats
+{
+    my ( $self, $fh ) = @_;
+
+    my $inf = $self->{inf};
+
+    while ( my $line = <$fh> ) {
+
+        ## try next line if blank
+        if ( $line eq "" ) {
+            next;
+
+        } elsif ( $line =~ m{^//} ) {
+            croak "unexpected end of data in stats section (received //)\n";
+
+        } elsif ( $line =~ m{^history:} ) {
+            croak "history line before end of stats section\n";
+
+        } elsif ( $line =~ m{^stats:} ) {
+
+            ## make a new Stats object and push it on to the queue
+            my $stats = Amanda::Curinfo::Stats->from_line($line);
+            push @$inf, $stats;
+
+        } elsif ( $line =~ m{^last_level: (\d+) (\d+)$} ) {
+
+            $self->{last_level}       = $1;
+            $self->{consecutive_runs} = $2;
+            last;
+
+        } else {
+            croak "bad line in read_infofile_stats: $line";
+        }
+    }
+
+    return 1;
+}
+
+sub read_infofile_history
+{
+    my ( $self, $fh ) = @_;
+
+    my $history = $self->{history};
+
+    while ( my $line = <$fh> ) {
+
+        if ( $line =~ m{^//} ) {
+            return;
+
+        } elsif ( $line =~ m{^history:} ) {
+            my $hist = Amanda::Curinfo::History->from_line($line);
+            push @$history, $hist;
+
+        } else {
+            croak "bad line found in history section:$line\n";
+        }
+    }
+
+    #
+    # TODO: make sure there were the right number of history lines
+    #
+
+    return 1;
+}
+
+sub write_to_file
+{
+    my ( $self, $infofile ) = @_;
+
+    unlink $infofile if -f $infofile;
+
+    open my $fh, ">", $infofile or die "error: couldn't open $infofile: $!";
+
+    ## print basics
+
+    print $fh "version: 0\n";    # 0 for now, may change in future
+    print $fh "command: $self->{command}\n";
+    print $fh $self->{full}->to_line("full");
+    print $fh $self->{incr}->to_line("incr");
+
+    ## print stats
+
+    foreach my $stat ( @{ $self->{inf} } ) {
+        print $fh $stat->to_line();
+    }
+    print $fh "last_level: $self->{last_level} $self->{consecutive_runs}\n";
+
+    foreach my $hist ( @{ $self->{history} } ) {
+        print $fh $hist->to_line();
+    }
+    print $fh "//\n";
+
+    return 1;
+}
+
+1;
+
+#
+#
+#
+
+package Amanda::Curinfo::History;
+
+use strict;
+use warnings;
+use Carp;
+
+sub new
+{
+    my $class = shift;
+    my ( $level, $size, $csize, $date, $secs ) = @_;
+
+    my $self = {
+        level => $level,
+        size  => $size,
+        csize => $csize,
+        date  => $date,
+        secs  => $secs,
+    };
+
+    return bless $self, $class;
+}
+
+sub from_line
+{
+    my ( $class, $line ) = @_;
+
+    my $self = undef;
+
+    if (
+        $line =~ m{^history:    \s+
+                     (\d+)      \s+  # level
+                     ($numdot+) \s+  # size
+                     ($numdot+) \s+  # csize
+                     ($numdot+) \s+  # date
+                     ($numdot+) $    # secs
+                  }x
+      ) {
+        $self = {
+            level => $1,
+            size  => $2,
+            csize => $3,
+            date  => $4,
+            secs  => $5,
+        };
+    } else {
+        croak "bad history line: $line";
+    }
+
+    return bless $self, $class;
+}
+
+sub to_line
+{
+    my ($self) = @_;
+    return
+"history: $self->{level} $self->{size} $self->{csize} $self->{date} $self->{secs}\n";
+}
+
+1;
+
+#
+#
+#
+
+package Amanda::Curinfo::Perf;
+
+use strict;
+use warnings;
+use Carp;
+
+use Amanda::Config;
+
+sub new
+{
+    my ($class) = @_;
+
+    my $self = {
+        rate => undef,
+        comp => undef,
+    };
+
+    return bless $self, $class;
+}
+
+sub set_rate
+{
+    my ( $self, @rate ) = @_;
+    $self->{rate} = \@rate;
+}
+
+sub set_comp
+{
+    my ( $self, @comp ) = @_;
+    $self->{comp} = \@comp;
+}
+
+sub set_rate_from_line
+{
+    my ( $self, $line ) = @_;
+    return $self->set_field_from_line( $self, $line, "rate" );
+
+}
+
+sub set_comp_from_line
+{
+    my ( $self, $line ) = @_;
+    return $self->set_field_from_line( $self, $line, "comp" );
+
+}
+
+sub set_field_from_line
+{
+    my ( $self, $line, $field ) = @_;
+
+    if (
+        $line =~ m{\w+-$field\: \s+
+                      ($numdot) \s+
+                      ($numdot) \s+
+                      ($numdot) $
+                   }x
+      ) {
+        $self->{$field} = [ $1, $2, $3 ];
+
+    } else {
+        croak "bad perf $field line: $line";
+    }
+
+    return;
+}
+
+sub to_line
+{
+    my ( $self, $lvl ) = @_;
+    return
+        "$lvl-rate: "
+      . join( " ", @{ $self->{rate} } ) . "\n"
+      . "$lvl-comp: "
+      . join( " ", @{ $self->{comp} } ) . "\n";
+}
+
+1;
+
+#
+#
+#
+
+package Amanda::Curinfo::Stats;
+
+use strict;
+use warnings;
+use Carp;
+
+sub new
+{
+    my $class = shift;
+    my ( $level, $size, $csize, $secs, $date, $filenum, $label ) = @_;
+
+    my $self = {
+        level   => $level,
+        size    => $size,
+        csize   => $csize,
+        secs    => $secs,
+        date    => $date,
+        filenum => $filenum,
+        label   => $label,
+    };
+
+    bless $self, $class;
+    return $self;
+}
+
+sub from_line
+{
+    my ( $class, $line ) = @_;
+    my $self = undef;
+
+    $line =~ m{^stats:      \s+
+                     (\d+)      \s+   # level
+                     ($numdot+) \s+   # size
+                     ($numdot+) \s+   # csize
+                     ($numdot+) \s+   # sec
+                     ($numdot+) \s+   # date
+                     ($numdot+) \s+   # filenum
+                     (.*) $           # label
+              }x
+      or croak "bad stats line: $line";
+
+    $self = {
+        level   => $1,
+        size    => $2,
+        csize   => $3,
+        secs    => $4,
+        date    => $5,
+        filenum => $6,
+        label   => $7,
+    };
+    return bless $self, $class;
+}
+
+sub to_line
+{
+    my ($self) = @_;
+    return join( " ",
+        "stats:",      $self->{level}, $self->{size},    $self->{csize},
+        $self->{secs}, $self->{date},  $self->{filenum}, $self->{label} )
+      . "\n";
+}
+
+1;