#!/usr/bin/perl
use warnings; use strict;
# $Revision: 319 $ $Date: 2009-02-12 15:11:37 -0500 (Thu, 12 Feb 2009) $
# $Id: ebook.pl 319 2009-02-12 20:11:37Z zed $


=head1 NAME

ebook - create and manipulate e-books from the command line

=head1 SYNOPSIS

 ebook COMMAND arg1 arg2 --opt1 --opt2

See also L</EXAMPLES>.

=cut


use Config::IniFiles;
use EBook::Tools qw(:all);
use EBook::Tools::IMP qw(:all);
use EBook::Tools::Unpack;
use File::Basename 'fileparse';
use File::Path;              # Exports 'mkpath' and 'rmtree'
use File::Slurp qw(slurp);   # Also exports 'read_file' and 'write_file'
use Getopt::Long qw(:config bundling);

# Exit values
use constant EXIT_SUCCESS       => 0;   # Success
use constant EXIT_BADCOMMAND    => 1;   # Invalid main command
use constant EXIT_BADOPTION     => 2;   # Invalid subcommand or option
use constant EXIT_BADINPUT      => 10;  # Bad input data
use constant EXIT_BADOUTPUT     => 11;  # Bad/unexpected output data
use constant EXIT_TOOLSERROR    => 20;  # Internal EBook::Tools error
use constant EXIT_MISSINGHELPER => 30;  # Required helper file not found
use constant EXIT_HELPERERROR   => 31;  # Helper command exited improperly


########################################
########## CONFIGURATION FILE ##########
########################################

my $defaultconfig = slurp(\*DATA);
my $configdir = userconfigdir();
my $configfile = $configdir . '/config.ini';
my $config;
if(-f $configfile)
{ 
    $config = Config::IniFiles->new( -file => $configfile );
}
$config = Config::IniFiles->new() unless($config);
#$config->read($configfile);

# Tidysafety requires special handling, since 0 is a valid value
my $tidysafety = $config->val('config','tidysafety');
undef($tidysafety) if(defined($tidysafety) and $tidysafety eq '');


#####################################
########## OPTION HANDLING ##########
#####################################

my %opt = (
    'author'      => undef,
    'category'    => undef,
    'compression' => undef,
    'dir'         => '',
    'fileas'      => '',
    'firstname'   => undef,
    'help'        => 0,
    'htmlconvert' => 0,
    'identifier'  => undef,
    'input'       => '',
    'key'         => '',
    'lastname'    => undef,
    'middlename'  => undef,
    'mimetype'    => '',
    'mobi'        => 0,
    'mobigencmd'  => $config->val('helpers','mobigen'),
    'nosave'      => 0,
    'noscript'    => 0,
    'oeb12'       => 0,
    'opf20'       => 0,
    'opffile'     => '',
    'raw'         => 0,
    'subcategory' => undef,
    'tidy'        => 0,
    'tidycmd'     => $config->val('helpers','tidy'),
    'tidysafety'  => $tidysafety,
    'title'       => undef,
    'verbose'     => $config->val('config','debug') || 0,
    );

GetOptions(
    \%opt,
    'authors=s',
    'category|cat|c=s',
    'compression|cm=i',
    'dir|d=s',
    'fileas=s',
    'firstname|author|a=s',
    'help|h|?',
    'htmlconvert',
    'identifier|id=s',
    'input|i=s',
    'key|pid=s',
    'lastname=s',
    'middlename=s',
    'mimetype|mtype=s',
    'mobi|m',
    'mobigencmd|mobigen=s',
    'nosave',
    'noscript',
    'raw',
    'oeb12',
    'opf20',
    'opffile|opf=s',
    'output|o=s',
    'subcategory|subcat=s',
    'tidy',
    'tidycmd',
    'tidysafety|ts=i',
    'title|t=s',
    'verbose|v+',
    );

if($opt{oeb12} && $opt{opf20})
{
    print "Options --oeb12 and --opf20 are mutually exclusive.\n";
    exit(EXIT_BADOPTION);
}

# Default to OEB12 if neither format is specified
if(!$opt{oeb12} && !$opt{opf20}) { $opt{oeb12} = 1; }

$EBook::Tools::debug = $opt{verbose};
$EBook::Tools::tidycmd = $opt{tidycmd} if($opt{tidycmd});
$EBook::Tools::tidysafety = $opt{tidysafety} if(defined $opt{tidysafety});


######################################
########## COMMAND HANDLING ##########
######################################

my %dispatch = (
    'impmeta'     => \&impmeta,
    );

my $cmd = 'impmeta';

if(!$cmd)
{
    print "No command specified.\n";
    print "Valid commands are: ",join(" ",sort keys %dispatch),"\n";
    exit(EXIT_BADCOMMAND);
}    
if(!$dispatch{$cmd})
{
    print "Invalid command '",$cmd,"'\n";
    print "Valid commands are: ",join(" ",sort keys %dispatch),"\n";
    exit(EXIT_BADCOMMAND);
}

$dispatch{$cmd}(@ARGV);


#########################################
########## COMMAND SUBROUTINES ##########
#########################################

=head2 C<genimp>

Generate a eBookwise .imp book from a .RES directory

=head3 Options

=over

=item C<--input DIRNAME.RES>

=item C<-i DIRNAME.RES>

Specifies the resource directory to use for input.  A valid resource
directory will contain at least a C<RSRC.INF> file, a C<DATA.FRK>
file, and several other files with four-capital-letter filenames.

This can also be specified as the first non-option argument, which
will override this option if it exists.  If not specified, the current
directory will be used.

=item C<--output bookname.epub>

=item C<-o bookname.epub>

Use the specified name for the final output file.  If not specified,
the book will have the same filename as the input, with the extension
changed to C<.imp>.

=back

=head3 Examples

 ebook genimp MyUnpackedBook.RES MyBook.imp
 ebook genimp --resdir ../MyUnpackedBook.RES -f imp/MyBook.imp

=cut

sub genimp
{
    my ($input,$output) = @_;
    my $ebook;
    my $retval;

    $input ||= $opt{input};
    $input ||= '.';
    $output ||= $opt{output};

    if(! $input)
    {
        print {*STDERR} "Resource directory not specified!\n";
        exit(EXIT_BADOPTION);
    }

    if(! -d $input)
    {
        print {*STDERR} "Resource directory '",$input,"' not found!\n";
        exit(EXIT_BADOPTION);
    }

    if(! $output)
    {
        print {*STDERR} "Output file not specified!\n";
        exit(EXIT_BADOPTION);
    }

    my $imp = EBook::Tools::IMP->new();
    if(! $imp->load_resdir($input) )
    {
        print {*STDERR} ("Failed to load from resource directory '",
                         $input,"'!\n");
        exit(EXIT_BADINPUT);
    }
    if(! $imp->write_imp($output) or ! -f $output)
    {
        print {*STDERR} ("Failed to generate '",$output,"'!\n");
        exit(EXIT_BADOUTPUT);
    }
    exit(EXIT_SUCCESS);
}


=head2 C<impmeta>

Set specific metadata values in an ETI .imp file.  

=head3 Options

=over

=item * C<--input filename.imp>

=item * C<-i filename.imp>

Specify the input filename.  This can also be specified as the first
argument, in which case the -i option will be ignored.

=item * C<--output modified.imp>

=item * C<-o modified.imp>

Specify the output filename.  If not specified, the input file will be
overwritten.

=item * C<--identifier>

Specify the identifier metadata.

=item * C<--category>

=item * C<--cat>

=item * C<--c>

Specify the category metadata.

=item * C<--subcategory>

=item * C<--subcat>

Specify the subcategory metadata.

=item * C<--title>

=item * C<--t>

Specify the title metadata.

=item * C<--lastname>

Specify the author last name metadata.

=item * C<--middlename>

Specify the author middle name metadata.

=item * C<--firstname>

=item * C<--author>

=item * C<--a>

Specify the author first name metadata.  Note that IMP files commonly
place the full name in this component, and leave the middlename and
lastname entries blank.

=back

=head3 Examples

 impmeta mybook.imp --title 'Fixed Title' --lastname 'John Q. Brandy'
 impmeta -i mybook.imp -o fixed.imp --title 'Fixed Title'

=cut

sub impmeta
{
    my ($input) = @_;
    $input ||= $opt{input} if($opt{input});
    my $output = $opt{output};
    
    unless($input)
    {
        print "You must specify an input file.\n";
        exit(EXIT_BADOPTION);
    }
    $output ||= $input;

    my $imp = EBook::Tools::IMP->new();
    if(! $imp->load($input))
    {
        print "Failed to load '",$input,"' -- aborting!\n";
        exit(EXIT_BADINPUT);
    }

    $imp->set_book_properties(
        'identifier'  => $opt{identifier},
        'category'    => $opt{category},
        'subcategory' => $opt{subcategory},
        'title'       => $opt{title},
        'lastname'    => $opt{lastname},
        'middlename'  => $opt{middlename},
        'firstname'   => $opt{firstname}
        );

    if(! $imp->write_imp($output) )
    {
        print "Failed to write '",$output,"' -- aborting!\n";
        exit(EXIT_BADOUTPUT);
    }
    exit(EXIT_SUCCESS);
}


########## PRIVATE PROCEDURES ##########

sub useoptdir
{
    if($opt{dir})
    {
        if(! -d $opt{dir})
        { 
            mkpath($opt{dir})
                or die("Unable to create working directory '",$opt{dir},"'!");
        }
        chdir($opt{dir})
            or die("Unable to chdir to working directory '",$opt{dir},"'!");
    }        
    return 1;
}

########## END CODE ##########

=head1 EXAMPLES

 ebook splitmeta book.html mybook.opf
 ebook tidyxhtml book.html
 ebook tidyxml mybook.opf
 ebook fix mybook.opf --oeb12 --mobi
 ebook genepub 

 ebook blank newbook.opf --title "My Title" --author "My Name"
 ebook adddoc myfile.html
 ebook fix newbook.opf --opf20 -v
 ebook genepub

 ebook unpack mybook.pdb my_book
 cd my_book
 ebook addoc new_document.html
 ebook fix
 ebook genepub

=head1 BUGS/TODO

=over

=item * Need to implement a one-pass conversion from one format to
another.  This will wait until more formats are supported by the
underlying modules, however.

=item * Documentation is incomplete

=item * Not all configuration file options are actually used

=back

=head1 COPYRIGHT

Copyright 2008 Zed Pobre

=head1 LICENSE

Licensed to the public under the terms of the GNU GPL, version 2.

=cut


########## DATA ##########

__DATA__
#
# config.ini
#
# Configuration file for EBook-Tools
#
#

# The [config] section holds general configuration values for
# EBook::Tools.
[config]
#
# debug sets the default debugging level if no verbosity is specified
# Note that this can only be raised, not lowered, from the command line.
debug=0
#
# tidysafety sets the safety level when running system_tidy_xhtml or
# system_tidy_xml.  See the EBook::Tools documentation for possible
# values and what they mean.
tidysafety=1

# The [drm] section holds user-specific information needed to decrypt,
# encrypt, or inscribe protected e-books.  Additional plug-in modules
# or helper applications may be needed for some of these values to
# have any effect.
[drm]
#
# ereaderkeys is a comma-separated list of EReader decryption keys to
# try, in the order that they will be tried.
ereaderkeys=
#
# litkeyfile marks the full path and filename to the keys.txt file
# needed by convertlit to downconvert or unpack MS Reader files.  If
# not specified, a keys.txt file will be searched for in the
# configuration directory and the current working directory.
litkeyfile=
#
# mobpids is a comma-separated list of Mobipocket/Kindle PIDs to try,
# in the order that they will be tried.
mobipids=
#
# username is the full name that will be used when decrypting EReader
# books and when inscribing MS Reader .lit files
username=

# The [helpers] section holds the locations of helper files, including
# the complete path.  If not specified here, they will be searched
# for in the configuration directory and other likely locations.
[helpers]
convertlit=
mobidedrm=
mobigen=
pdbshred=
tidy=
