#!/usr/bin/perl

#
# Administration tool for Thingy,
# the enhanced UseMod Wiki spin-off.
#
# (C) 2005-2011 Julian Mehnle <julian@mehnle.net>
# $Id$
#
##############################################################################

=head1 NAME

thingy-admin - An administration tool for Thingy.

=head1 VERSION

1.107

=head1 SYNOPSIS

B<thingy-admin> B<list>|B<ls> [I<thingy-name-pattern>]

B<thingy-admin> B<create> I<thingy-name>

B<thingy-admin> B<remove>|B<rm> [B<--force>|B<-f>] I<thingy-name>

B<thingy-admin> B<clone>|B<copy>|B<cp> I<source-thingy-name> I<target-thingy-name>

B<thingy-admin> B<upgrade>|B<up> I<thingy-name>

B<thingy-admin> B<listusers>|B<users> [B<--long>|B<-l>] I<thingy-name> [I<user-name-pattern>]

B<thingy-admin> B<listpages>|B<pages> [B<--long>|B<-l>] I<thingy-name> [I<page-name-pattern>]

B<thingy-admin> B<help>

=head1 DESCRIPTION

Create and remove Thingy instances.

=head1 AUTHOR

Julian Mehnle <julian@mehnle.net>

=cut

use version; our $VERSION = qv('1.107');

use warnings;
use strict;
use locale;
use lib '/usr/share/thingy/lib';

use Getopt::Long qw(:config gnu_getopt require_order);
use IO::File;
use Error ':try';

use Thingy::Config;
use Thingy;
use Thingy::Util;

use constant TRUE   => (0 == 0);
use constant FALSE  => not TRUE;

sub usage {
    my ($exit_code, $error_message) = @_;
    $exit_code = 1 if not defined($exit_code);
    my $output_handle = $exit_code ? *STDERR : *STDOUT;
    $output_handle->print("thingy-admin: $error_message\n") if defined($error_message);
    $output_handle->print(<<"EOF");
Usage:
  thingy-admin list|ls [<thingy-name-pattern>]
  thingy-admin create <thingy-name>
  thingy-admin remove|rm [--force|-f] <thingy-name>
  thingy-admin clone|copy|cp <source-thingy-name> <target-thingy-name>
  thingy-admin upgrade|up <thingy-name>
  thingy-admin listusers|users [--long|-l] <thingy-name> [<user-name-pattern>]
  thingy-admin listpages|pages [--long|-l] <thingy-name> [<page-name-pattern>]
  thingy-admin help
EOF
    exit($exit_code);
}

sub get_options {
    my (@option_defs) = @_;
    GetOptions(\my %options, @option_defs)
        or usage(1);
    return \%options;
}

sub logger {
    my ($log_level, @messages) = @_;
    print(map('    ' x $log_level . "$_\n", @messages));
}

my $global_options = get_options(
    'help|h'
);
usage(0) if $global_options->{help};

my $action = shift(@ARGV);
usage(1) if not defined($action);
usage(0) if $action eq 'help';

try {
    if ($action eq 'list' or $action eq 'ls') {
        usage(1) if @ARGV > 1;
        my (@thingy_name_glob) = @ARGV;
        print(map("$_\n", Thingy->enumerate(@thingy_name_glob)));
        exit(0);
    }
    elsif ($action eq 'create') {
        usage(1) if @ARGV != 1;
        my ($thingy_name) = @ARGV;
        my $thingy = Thingy->create(name => $thingy_name);
        print('Thingy instance "' . $thingy->name . '" created.' . "\n");
        print('Include ' . $thingy->httpd_conf_file . " in your httpd configuration to activate it.\n");
        print('You may have to edit your instance configuration in ' . $thingy->config_dir . ".\n");
        exit(0);
    }
    elsif ($action eq 'remove' or $action eq 'rm') {
        my $options = get_options(
            'force|f'
        );
        usage(1) if @ARGV != 1;
        my ($thingy_name) = @ARGV;
        
        Thingy::Util::require_superuser();  # Detect missing privileges early.

        my $thingy;
        try {
            $thingy = Thingy->get(name => $thingy_name);
        }
        catch Thingy::EInstanceNotExists with {
            # Explicitly catch EInstanceNotExists because it shall not be a fatal
            # error and we want to exit with code 0.
            print("Warning: Thingy instance \"$thingy_name\" does not exist.\n");
            exit(0);
        };
        
        if (not $options->{force}) {
            # Require confirmation:
            print("Warning: Configuration and database directories will be deleted.\n");
            print("Really remove Thingy instance \"$thingy_name\"? ");
            if (not <STDIN> =~ /^y/i) {
                print("Aborting.\n");
                exit(1);
            }
        }
        
        $thingy->delete();
        print("Thingy instance \"$thingy_name\" removed.\n");
        exit(0);
    }
    elsif ($action eq 'clone' or $action eq 'copy' or $action eq 'cp') {
        usage(1) if @ARGV != 2;
        my ($source_thingy_name, $target_thingy_name) = @ARGV;
        my $source_thingy = Thingy->get(name => $source_thingy_name);
        my $target_thingy = $source_thingy->clone(name => $target_thingy_name);
        print("Thingy instance \"$source_thingy_name\" cloned as \"$target_thingy_name\".\n");
        exit(0);
    }
    elsif ($action eq 'upgrade' or $action eq 'up') {
        usage(1) if @ARGV != 1;
        my ($thingy_name) = @ARGV;
        my $thingy = Thingy->get(name => $thingy_name);
        $thingy->upgrade(\&logger, 0);
        print("Finished.\n");
        exit(0);
    }
    elsif ($action =~ /^(listusers|lsusers|users)$/) {
        my $options = get_options(
            'long|l'
        );
        usage(1) if @ARGV < 1 or @ARGV > 2;
        my ($thingy_name, @user_name_glob) = @ARGV;
        my $thingy = Thingy->get(name => $thingy_name);
        my @users = $thingy->users(@user_name_glob);
        my $users_count = @users;
        @users = grep(defined($_->name), @users);
        my $anon_users_count = $users_count - @users;
        @users = sort { $a->name cmp $b->name or $a->id <=> $b->id } @users;
        if ($options->{long}) {
            foreach my $user (@users) {
                printf("%5s %s\n", $user->id, $user->name);
            }
        }
        else {
            print(map($_->name . "\n", @users));
        }
        STDERR->print($anon_users_count, " anonymous user(s) not shown.\n")
            if $anon_users_count;
        exit(0);
    }
    elsif ($action =~ /^(listpages|lspages|pages)$/) {
        my $options = get_options(
            'long|l'
        );
        usage(1) if @ARGV < 1 or @ARGV > 2;
        my ($thingy_name, @page_name_glob) = @ARGV;
        my $thingy = Thingy->get(name => $thingy_name);
        my @pages = $thingy->pages(@page_name_glob);
        @pages = sort { $a->name cmp $b->name } @pages;
        if ($options->{long}) {
            foreach my $page (@pages) {
                printf(
                    "%5s %-20s %6s %-16s %s\n",
                    $page->revision,
                    $page->author_name,
                    $page->size,
                    Thingy::Util::format_iso_timestamp(scalar $page->mtime),
                    $page->name
                );
            }
        }
        else {
            print(map($_->name . "\n", @pages));
        }
        exit(0);
    }
    else {
        usage(1, "Unrecognized action: $action");
    }
}
catch Thingy::Exception with {
    my ($e) = @_;
    print("thingy-admin: Error: $e");
    exit(1);
};

