The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# $Id: Makefile.PL,v 1.51 2003/08/28 19:19:33 andreychek Exp $

use lib "./inc";
use strict;
use File::Copy;
use File::Path;
use File::NCopy           qw();
use ExtUtils::MakeMaker   qw( prompt );
use ExtUtils::AutoInstall qw();

# Perl version 5.005_03 is our minimum
eval "use 5.005_03";
if( $@ ) {
    print <<EOT;

 =============================================================
   Perl version 5.005_03 or later is required for OpenPlugin.
   Unfortunatly, you'll need to upgrade in order for it
   to work.  Try installing a new version of Perl to see
   what you're missing :-)
 =============================================================

EOT

   die "Perl version too old, quiting...\n";
}

my $op = OpenPlugin::Installer->new();
my @plugin_dependencies = $op->get_install_list;
my @optional_modules;

if ( $] >= 5.006 ) {
    @optional_modules = qw( Attribute::Handlers 0 );
}

ExtUtils::AutoInstall->import (
   -version             => '0.30',
   -config              => {
      force             => 0,
   },
   -core                => [
      'Class::Factory'      => '1.0',
      'Digest::MD5'         => '2.16',
      'MD5'                 => '',
      'Test::Harness'       => '2.0',
      'Test::More'          => '0.47',
      'Error'               => '',
      'Storable'            => '1.011',
      'Devel::StackTrace'   => '',
      'Time::HiRes'         => '1.20',
      'Archive::Tar'        => '0.23',
      @optional_modules,
      'Params::Validate'    => '',
      'XML::DOM'            => '1.29',
      'Log::Dispatch'       => '2.00',
      'Log::Log4perl'       => '0.25',
      'HTML::Template'      => '',
      'CGI::Application'    => '2.6',
      @plugin_dependencies,
   ],
);

config_openplugin();

WriteMakefile(
    NAME            => 'OpenPlugin',
    VERSION_FROM    => './OpenPlugin.pm',
    test            => { TESTS => $op->get_core_tests . $op->get_plugin_tests },
    ($] >= 5.005 ?
      (ABSTRACT_FROM    => 'OpenPlugin.pm',
       AUTHOR           => 'Eric Andreychek <eric at openthought.net>') : ()),
);

sub config_openplugin {
    unless (prompt("\nCan I install your data files now?", 'Y/n') =~ /^y/i) {
        print "Warning: skipping data file installation.\n";
        print "Normal OpenPlugin operation requires these data files!\n\n";
        return;
    }

    my $data_file = prompt("\n\nWhere would you like to put the OpenPlugin\n" .
                           "configuration files?\n", "/usr/local/etc");
    chomp $data_file;

    unless ( -d $data_file ) {
      mkdir $data_file, 0777 or die
          "Cannot create $data_file: $!";
    }

    if( -f "$data_file/OpenPlugin.conf" ) {
        File::Copy::copy("$data_file/OpenPlugin.conf",
                         "$data_file/OpenPlugin.conf.orig") or die
                            "Cannot backup config file: $!";
        print "\nFound existing [OpenPlugin.conf] file.  Backing up to [OpenPlugin.conf.orig].\n";
    }
    if( -f "$data_file/OpenPlugin-drivermap.conf" ) {
        File::Copy::copy("$data_file/OpenPlugin-drivermap.conf",
                         "$data_file/OpenPlugin-drivermap.conf.orig") or die
                            "Cannot backup config file: $!";
        print "Found existing [OpenPlugin-drivermap.conf] file.  Backing up to [OpenPlugin-drivermap.conf.orig].\n\n";
    }
    my $file = File::NCopy->new(recursive => 1);
    $file->copy("conf/*", "$data_file") or die
        "Cannot copy conf directory to $data_file: $!";

    print "OpenPlugin config files are now in $data_file\n";
    return {};
}

package OpenPlugin::Installer;

use strict;
use ExtUtils::MakeMaker qw(prompt);

###################
# Data Format
#
#  deps
# A reference to an array containing a list of all a particular
# driver's dependencies, in the format:
#   'Module' => 'Version'
#
#  meta
# Meta information for a particular driver.  The following meta information is
# recognized:
# -install -- Means this driver will be installed by default
# -force   -- Forces installation of a particular driver (driver is required)
# -test    -- Name of test to run if installing this driver

sub new {
    my $class = shift;

    my $self = {
        'Request' => {
            Apache2  => {
                deps => [ 'Apache2'   => '' ],
                meta => { -install            => 0,
                          -test               => 't/request_apache2.t' },
            },
            Apache  => {
                deps => [ 'Apache::Request'   => '0.30' ],
                meta => { -install            => 0,
                          -test               => 't/request_apache.t' },
            },
            CGI     => {
                deps => [ 'CGI'    => '', ],
                meta => { -install => 1,
                          -test    => 't/request_cgi.t' },
            },
        },
        Authenticate    => {
            #DBI     => {
            #    deps => [ DBI      => '', ],
            #    meta => { -install => 1,  },
            #},
            PAM     => {
                deps => [ 'Authen::PAM'   => '', ],
                meta => {},
            },
            SMB     => {
                deps => [ 'Authen::Smb'   => '', ],
                meta => {},
            },
        },
        Cache           => {
            File    => {
                deps => [ 'Digest::SHA1'  => '2.02',
                          'Cache::Cache'  => '', ],
                meta => { -install        => 1,
                          -test           => 't/cache_file.t'  },
            },
        },
        Config          => {
            Conf    => {
                deps => [ 'Config::General'   => '', ],
                meta => { -install            => 1,
                          -force              => 1,  },
            },
            Ini     => {
                deps => [],
                meta => { -install        => 1,
                          -force          => 1, },
            },
            Perl    => {
                deps => [ 'Data::Dumper'  => '', ],
                meta => {},
            },

            XML     => {
                deps => [ 'OpenThought::XML2Hash'  => '0.56', ],
                meta => {},
            },
        },
        Datasource      => {
            DBI     => {
                deps => [ DBI      => '', ],
                meta => { -install => 1,  },
            },
            LDAP    => {
                deps => [ 'Net::LDAP' => '', ],
                meta => {},
            },
        },
        Log             => {
            ApacheLog  => {
                deps => [ 'Apache::Log' => '', ],
                meta => {},
            },
            DBI  => {
                deps => [ 'DBI'   => '',
                          'Log::Dispatch::DBI' => '', ],
                meta => {},
            },
            Email  => {
                deps => [ 'Mail::Sendmail' => '', ],
                meta => {},
            },
            Jabber  => {
                deps => [ 'Unicode::String' => '',
                          'Net::SSLeay'     => '',
                          'IO::Socket::SSL' => '0.81',
                          'XML::Stream'     => '',
                          'Net::Jabber'     => '', ],
                meta => {},
            },
            Syslog  => {
                deps => [ 'Sys::Syslog' => '', ],
                meta => {},
            },
        },
        Session         => {
            ApacheSession  => {
                deps => [ 'MIME::Base64'    => '',
                          'DB_File'         => '',
                          'Apache::Session' => '1.54', ],
                meta => { -install          => 1,
                          -test             => 't/session_apachesession.t' },
            },
        },
    };

    bless( $self, $class );
    return $self;
}

sub get_core_tests {
    my $self = shift;

    # Leave a trailing space
    my $tests = "t/01_require.t t/02_config.t t/03_log.t t/04_exception.t ";

    return $tests;
}

# Get a hash (name, version) of modules to be installed
sub get_install_list {
    my $self = shift;

    $self->check_deps;
    $self->handle_input;

    my @install_list = $self->get_scheduled_driver_deps;

    if( @install_list ) {
        return @install_list;
    }
    else {
        return ();
    }
}

sub get_scheduled_driver_deps {
    my $self = shift;

    my @deps_list;
    foreach my $plugin ( $self->get_plugins ) {
        foreach my $driver ( $self->get_scheduled_drivers( $plugin )) {
            foreach my $dep ( $self->_get_driver_deps( $plugin, $driver )) {
                push @deps_list, $dep;
            }
        }
    }

    return @deps_list;
}

sub get_plugin_tests {
    my $self = shift;

    my $test_list;
    foreach my $plugin ( $self->get_plugins ) {
        foreach my $driver ( $self->get_installed_drivers( $plugin ) ){
            foreach my $test ( $self->_get_driver_test( $plugin, $driver )) {
                $test_list .= " $test";
            }
        }

        foreach my $driver ( $self->get_scheduled_drivers( $plugin ) ){
            foreach my $test ( $self->_get_driver_test( $plugin, $driver )) {
                $test_list .= " $test";
            }
        }
    }

    $test_list =~ s/\s+/ /g;
    return $test_list;
}

# The main look for the installation screen
sub handle_input {
    my $self = shift;

    my $action;
    while ( $action ne "d" ) {
        $action = "";
        $self->display_installation_screen;
        while ($action !~ m/^[ardAR]/) {
            $action = (prompt("\n[a]dd, [r]emove, [A]dd All, [R]emove All, [d]one:", "d"));
        }

        if( $action eq "a") {
            $self->add_driver;
        }
        elsif( $action eq "r") {
            $self->remove_driver;
        }
        elsif( $action eq "A") {
            $self->add_all_drivers;
        }
        elsif( $action eq "R") {
            $self->remove_all_drivers;
        }
    }
    print "\nDone.\n\n";
}

# Return a list of all plugins
sub get_plugins {
    my $self = shift;

    return sort keys %{ $self };
}

# Return a list of all drivers for a given plugin
sub get_drivers {
    my ( $self, $plugin ) = @_;

    return sort keys %{ $self->{ $plugin } };
}

# List drivers which currently are not scheduled to be installed
sub get_available_drivers {
    my ( $self, $plugin ) = @_;

    my @all_drivers = sort keys %{ $self->{ $plugin } };
    my @available_drivers;

    foreach my $driver ( @all_drivers ) {

        # If it's already installed, or scheduled to be installed, it's not
        # considered available
        unless(( $self->_to_be_installed( $plugin, $driver )) ||
               ( $self->_already_installed( $plugin, $driver))) {
            push @available_drivers, $driver;
        }
    }
    return @available_drivers;
}

# List drivers which are currently scheduled to be installed
sub get_scheduled_drivers {
    my ( $self, $plugin ) = @_;

    my @all_drivers = sort keys %{ $self->{ $plugin } };
    my @drivers_to_install;

    foreach my $driver ( @all_drivers ) {
        if( $self->_to_be_installed( $plugin, $driver )) {
            push @drivers_to_install, $driver;
        }
    }
    return @drivers_to_install;
}

# List drivers which are already installed
sub get_installed_drivers {
    my ( $self, $plugin ) = @_;

    my @all_drivers = sort keys %{ $self->{ $plugin } };
    my @installed_drivers;

    foreach my $driver ( @all_drivers ) {
        if( $self->_already_installed( $plugin, $driver )) {
            push @installed_drivers, $driver;
        }
    }
    return @installed_drivers;
}

# List drivers which are currently scheduled to be installed
sub get_scheduled_drivers {
    my ( $self, $plugin ) = @_;

    my @all_drivers = sort keys %{ $self->{ $plugin } };
    my @drivers_to_install;

    foreach my $driver ( @all_drivers ) {
        if( $self->_to_be_installed( $plugin, $driver )) {
            push @drivers_to_install, $driver;
        }
    }
    return @drivers_to_install;
}

# See which drivers we already have all the dependencies installed for
sub check_deps {
    my $self = shift;

    foreach my $plugin ( $self->get_plugins ) {
        foreach my $driver ( $self->get_drivers( $plugin ) ) {
            my $driver_test = 1;
            my %deps = $self->_get_driver_deps( $plugin, $driver );

            foreach my $module ( keys %deps ){
                unless (defined (ExtUtils::AutoInstall::_version_check(
                    ExtUtils::AutoInstall::_load($module),
                    $deps{$module} ||= 0 ))) {
                        $driver_test = 0;
                }
            }
            if( $driver_test ) {
                $self->_set_driver_installed( $plugin, $driver );
            }
        }
    }
}

# Remove a scheduled driver
sub remove_driver {
    my $self = shift;

    my @plugins = $self->get_plugins;
    $self->display_ordered_list( @plugins );

    my $msg = "Remove driver from which plugin?";
    my $plugin_num = $self->get_number_input( 1, scalar @plugins, $msg );
    return unless defined $plugin_num;

    my $plugin = $self->get_plugin_by_number( $plugin_num );

    my @drivers = $self->get_scheduled_drivers( $plugin );

    my $driver_num;
    if ( @drivers > 1 ) {
        $self->display_ordered_list( @drivers );
        my $msg = "Remove which driver?";
        my $driver_num = $self->get_number_input( 1, scalar @drivers, $msg );
        return unless defined $driver_num;
    }
    elsif ( @drivers == 1 ) {
        $driver_num = 0;
    }
    else {
        prompt("\n * No drivers from $plugin to remove: ",  "whoops");
        return;
    }

    my $driver = $self->get_scheduled_drivers_by_number( $plugin, $driver_num );
    if ( $self->_unschedule_driver( $plugin, $driver ) ) {
        prompt("\n * Removed the $driver driver from $plugin: ", "okay");
    }
    else {
        prompt("\n * $driver is either already installed on your system, or required: ", "okay");
    }

}

# Schedule a driver to be installed
sub add_driver {
    my $self = shift;

    my @plugins = $self->get_plugins;
    $self->display_ordered_list( @plugins );

    my $msg = "Add driver from which plugin?";
    my $plugin_num = $self->get_number_input( 1, scalar @plugins, $msg );
    return unless defined $plugin_num;

    my $plugin = $self->get_plugin_by_number( $plugin_num );

    my @drivers = $self->get_available_drivers( $plugin );

    my $driver_num;
    if ( @drivers > 1 ) {
        $self->display_ordered_list( @drivers );
        my $msg = "Add which driver?";

        $driver_num = $self->get_number_input( 1, scalar @drivers, $msg );
        return unless defined $driver_num;
    }
    elsif ( @drivers == 1 ) {
        $driver_num = 0;
    }
    else {
        prompt("\n * No more drivers from $plugin to install: ",  "bummer");
        return;
    }

    my $driver = $self->get_available_drivers_by_number( $plugin, $driver_num );
    $self->_schedule_driver( $plugin, $driver );
    prompt("\n * Scheduled the $driver driver from $plugin to be installed: ",
           "okay");
}

sub add_all_drivers {
    my $self = shift;

    foreach my $plugin ( $self->get_plugins ) {
        foreach my $driver ( $self->get_available_drivers( $plugin )) {
            print "P: $plugin, $driver\n";
            $self->_schedule_driver( $plugin, $driver );
        }
    }
}

sub remove_all_drivers {
    my $self = shift;

    foreach my $plugin ( $self->get_plugins ) {
        foreach my $driver ( $self->get_scheduled_drivers( $plugin )) {
            $self->_unschedule_driver( $plugin, $driver );
        }
    }
}

sub get_plugin_by_number {
    my ( $self, $number ) = @_;

    return (sort $self->get_plugins)[$number-1];
}

sub get_available_drivers_by_number {
    my ( $self, $plugin, $number ) = @_;

    return (sort $self->get_available_drivers( $plugin ))[$number-1];
}

sub get_scheduled_drivers_by_number {
    my ( $self, $plugin, $number ) = @_;

    return (sort $self->get_scheduled_drivers( $plugin ))[$number];
}

# Given a min and max number, a prompt and error message, this function gets a
# number as input from the user.  It keeps trying until they give a number
# within the limit
sub get_number_input {
    my ( $self, $min, $max, $prompt ) = @_;

    my $num = $min - 1;

    while(( $num < $min ) || ( $num > $max )) {
        $num = prompt( $prompt . ": [$min-$max]" );

        return undef if $num =~ m/^\s*$/g;

        if(( $num < $min ) || ( $num > $max )) {
            prompt( "\n * Is '$num' one of the choices?", "nope" );
            print "\n";
        }
    }

    return $num;
}

# Given a list of items, displays it line by line with prefixed numbers
sub display_ordered_list {
    my ( $self, @list ) = @_;

    print "\n";
    for( my $i = 1; $i <= $#list+1; $i++ ) {
        print "[$i] $list[$i-1]\n";
    }
    print "\n";
}


sub display_installation_screen {
    my $self = shift;

    my $i;
    foreach my $plugin ( $self->get_plugins ){
         $i += scalar $self->get_installed_drivers( $plugin );
    }
print qq{
\n\n\n\n\n\n\n\n\n\n\n
 OpenPlugin Driver Installation Screen

  I detected dependencies for ($i) drivers already installed on your system.
  You may now choose additional drivers to install.

------------------------------------------------------------------------------
Plugin       | Available Drivers            | Install
------------------------------------------------------------------------------
};
    foreach my $plugin ( $self->get_plugins ) {
        my $available_drivers;
        foreach my $driver ( $self->get_available_drivers( $plugin ) ){
             $available_drivers .= "$driver ";
        }

        my $drivers_to_install;
        foreach my $driver ( $self->get_scheduled_drivers( $plugin ) ){
             $drivers_to_install .= "$driver ";
        }
        #my $installed_drivers;
        #foreach my $driver ( $self->get_installed_drivers( $plugin ) ){
        #     $installed_drivers .= "$driver ";
        #}
format STDOUT =
@<<<<<<<<<<<<<<@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$plugin        $available_drivers              $drivers_to_install
.
        write;
    }
}

sub display_installed_drivers_screen {
    my $self = shift;

    my $i;
    foreach my $plugin ( $self->get_plugins ){
         $i += scalar $self->get_installed_drivers( $plugin );
    }
    foreach my $plugin ( $self->get_plugins ){
         $i += scalar $self->get_scheduled_drivers( $plugin );
    }
print qq{
\n\n\n\n\n\n\n\n\n\n\n
  When the installation is complete, the following $i drivers will be
  available to you:

------------------------------------------------------------------------------
Plugin       | Drivers
------------------------------------------------------------------------------
};
    foreach my $plugin ( $self->get_plugins ) {
        my $driver_list;
        foreach my $driver ( $self->get_installed_drivers( $plugin ) ){
             $driver_list .= "$driver ";
        }

        foreach my $driver ( $self->get_scheduled_drivers( $plugin ) ){
             $driver_list .= "$driver ";
        }
format STDOUT1 =
@<<<<<<<<<<<<<<@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$plugin        $driver_list
.
        write STDOUT1;
    }
    <STDIN>;
}

###################
# Private Functions

# Determine whether or not a driver is already installed
sub _already_installed {
    my ( $self, $plugin, $driver ) = @_;

    if( $self->{ $plugin }{ $driver }{'meta'}{'-installed'} ) {
        return 1;
    }
    else {
        return 0;
    }
}

# Set a driver as already being installed
sub _set_driver_installed {
    my ( $self, $plugin, $driver ) = @_;

    $self->{ $plugin }{ $driver }{'meta'}{ '-installed' } = 1;
    $self->{ $plugin }{ $driver }{'meta'}{ '-install' } = 0;
}

# Schedule a driver to be installed
sub _schedule_driver {
    my ( $self, $plugin, $driver ) = @_;

    return $self->{ $plugin }{ $driver }{'meta'}{ '-install' } = 1;
}

# Unschedule a driver from installtion
sub _unschedule_driver {
    my ( $self, $plugin, $driver ) = @_;

    if( $self->{ $plugin }{ $driver }{'meta'}{ '-force' } ) {
        return 0;
    }
    else {
        $self->{ $plugin }{ $driver }{'meta'}{ '-install' } = 0;
        return 1;
    }
}

# Determine whether or not a driver is scheduled for installation
sub _to_be_installed {
    my ( $self, $plugin, $driver ) = @_;

    if( $self->{ $plugin }{ $driver }{'meta'}{'-install'} ) {
        return 1;
    }
    else {
        return 0;
    }
}

# Get a list of dependencies for a given driver
sub _get_driver_deps {
    my ( $self, $plugin, $driver ) = @_;

    return @{ $self->{ $plugin }{ $driver }{'deps'} };
}

# Get a list of dependencies for a given driver
sub _get_driver_test {
    my ( $self, $plugin, $driver ) = @_;

    return $self->{ $plugin }{ $driver }{'meta'}{'-test'};
}

1;