The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Catalyst::View::RRDGraph;
{
  $Catalyst::View::RRDGraph::VERSION = '0.10';
}

use strict;
use warnings;

use base 'Catalyst::View';

use RRDs;
use File::Temp qw();
use MRO::Compat;

use Catalyst::Exception;

sub new {
    my ($class, $c, $arguments) = @_;
    my $config = {
        'IMG_DIR' => '/tmp/',
        'IMG_FORMAT' => 'PNG',
        'ON_ERROR_SERVE' => undef,
        %{ $class->config },
        (defined($arguments)?%{$arguments}:()),
    };

    my $self = $class->next::method(
        $c, { %$config },
    );

    $self->config($config);

    return ($self);
}

sub process {
    my ($self, $c) = @_;

    my $props = $c->stash->{'graph'};
    die "No graph in the stash" if (not defined $props);
    die "graph must be an ARRAYREF" if (ref($props) ne 'ARRAY');

    my $tempfile = File::Temp->new( TEMPLATE => 'cat_view_rrd_XXXXXX',
                                    DIR => $self->config->{'IMG_DIR'},
                                    SUFFIX => '.' . lc($self->config->{'IMG_FORMAT'}));

    RRDs::graph($tempfile->filename,
                '--imgformat', $self->config->{'IMG_FORMAT'},
                @$props);

    if (RRDs::error) {
        $self->_handle_error($c, RRDs::error);
        return;
    }
    if (-s $tempfile->filename == 0) {
        $self->_handle_error($c, "RRDgraph is 0 bytes");
        return;
    }

    $c->serve_static_file($tempfile->filename);
}

sub _handle_error {
    my ($self, $c, $error) = @_;
    if (not defined $self->config->{'ON_ERROR_SERVE'}){
        Catalyst::Exception->throw($error);
    } elsif (ref($self->config->{'ON_ERROR_SERVE'}) eq 'CODE') {
        #Call the custom handler
        $self->config->{'ON_ERROR_SERVE'}($self, $c, $error);
    } else {
        $c->log->error($error);
        $c->serve_static_file($self->config->{'ON_ERROR_SERVE'});
    }

}

#################### main pod documentation begin ###################
## Below is the stub of documentation for your module. 
## You better edit it!


=head1 NAME

Catalyst::View::RRDGraph - RRD Graph View Class

=head1 SYNOPSIS

use the helper to create your View myapp_create.pl view RRDGraph RRDGraph

from the controller:

  sub routine :Local {
    my ($self, $c) = @_;

    $c->stash->{'graph'} = [
            "--lower-limit", "0",
            "--start", "end-1d",
            "--vertical-label", "My Label",
            "--height", 600,
            "--width", 300,
            "DEF:Data=/path/to/rrd.rrd:data:AVERAGE",
            "AREA:Data#0000FF:Data "
    ];
    $c->forward('MyApp::View::RRDGraph');
  }

=head1 DESCRIPTION

This view generates RRD graph images from the graph defintion placed in the
stash. The controller is responsable of placing an ARRAYREF in
B<$c->stash->{'graph'}> with the same data as to generate a graph with the RRDs
module, except for I<filename>, that will be automatically generated by the
view.

It doesn't depend on RRDs, but I<does> need it to work (and for the tests to
pass). You can install RRDs, for instance, using L<Alien::RRDtool>, or
compiling it manually from L<http://oss.oetiker.ch/rrdtool/>

=head1 CONFIGURATION

Configurations for the view are:

=head2 IMG_DIR

Directory to generate temporary image files. Defaults to B</tmp/>

=head2 IMG_FORMAT

Image format for the generated files. 'PNG' by default. 

=head2 ON_ERROR_SERVE

On error, if this config value is set, the file to which it points will be
served (so you can serve an "error image" file to the user). Alternately, it
can be set to a code reference, that will called with B<$self>, B<$c> and
B<$error>. You can then generate your own content in this handler. Default
(leaving undefined) is to throw an expception.

See L<http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html> for more info.

=head1 METHODS

=head2 new

Constructor.

=head2 process

Called internally by Catalyst when the view is used.

=head1 AUTHOR

    Jose Luis Martinez
    CPAN ID: JLMARTIN
    CAPSiDE
    jlmartinez@capside.com
    L<http://www.pplusdomain.net>

=head1 THANKS

To Ton Voon for sending in patches, tests, and ideas.

Alexander Kabenin

=head1 COPYRIGHT

This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the
LICENSE file included with this module.


=head1 SEE ALSO

RRDs: L<http://oss.oetiker.ch/rrdtool/prog/RRDs.en.html>

RRD graph docs:
L<http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html>,
L<http://oss.oetiker.ch/rrdtool/doc/rrdgraph_data.en.html>,
L<http://oss.oetiker.ch/rrdtool/doc/rrdgraph_graph.en.html>

=cut

#################### main pod documentation end ###################


1;
# The preceding line will help the module return a true value