Comics

 view release on metacpan or  search on metacpan

lib/Comics.pm  view on Meta::CPAN

	fail => [],
	loaded => 0,
	uptodate => 0,
	excluded => 0,
	disabled => 0,
      };

    # Process command line options.
    app_options();

    # Post-processing.
    $trace |= ($debug || $test);
    $verbose = 255 if $debug;
    $spooldir .= "/";
    $spooldir =~ s;/+$;/;;

    File::Path::make_path( $spooldir, { verbose => 1 } )
	unless -d $spooldir;

    $statefile = spoolfile(".state.json");

    $pluginfilter = ".";
    if ( @ARGV ) {
	$pluginfilter = "^(?:" . join("|", @ARGV) . ")\\.pm\$";
    }
    $pluginfilter = qr($pluginfilter)i;

}

sub main {

    # Initialize.
    init();

    # Restore state of previous run.
    get_state();

    # Load the plugins.
    load_plugins();

    # Non-aggregating command: list.
    if ( $list ) {
	list_plugins();
	return;
    }

    # Non-aggregating command: enable/disable.
    if ( $activate ) {
	save_state();
	return unless $rebuild;
    }

    unless ( $rebuild ) {
	# Run the plugins to fetch new images.
	run_plugins();

	# Save the state.
	save_state();
    }

    # Gather the HTML fragments into a single index.html.
    build();

    # Show processing statistics.
    statistics();
}

################ State subroutines ################

use JSON;

my $state;

sub get_state {
    my $opts = { split => 0, fail => "soft" };
    my $data = loadlines( $statefile, $opts );
    if ( $opts->{error} ) {
	$state = { comics => { } };
    }
    else {
	$state = JSON->new->decode($data);
	if ( $refresh ) {
	    delete( $_->{md5} )
	      foreach values( %{ $state->{comics} } );
	}
    }
}

sub save_state {
    unlink($statefile."~");
    rename( $statefile, $statefile."~" );
    open( my $fd, '>:utf8', $statefile );
    print $fd JSON->new->canonical->pretty(1)->encode($state);
    close($fd);
}

################ Plugin subroutines ################

my @plugins;

sub load_plugins {

    opendir( my $dh, $INC[0] . "/Comics/Plugin" )
      or die( $INC[0] . "/Comics/Plugin: $!\n");

    while ( my $m = readdir($dh) ) {
	next unless $m =~ /^[0-9A-Z].*\.pm$/;
	next if $m eq 'Base.pm';
	$stats->{loaded}++;
	$stats->{excluded}++, next unless $m =~ $pluginfilter;

	debug("Loading $m...");
	$m =~ s/\.pm$//;
	# If the module is already loaded, remove it first.
	# Otherwise the require won't produce the __PACKAGE__ result.
	delete $INC{"Comics/Plugin/$m.pm"};
	my $pkg = eval { require "Comics/Plugin/$m.pm" };
	die("Comics/Plugin/$m.pm: $@\n") unless $pkg;
	unless ( $pkg eq "Comics::Plugin::$m" ) {
	    warn("Skipped $m.pm (defines $pkg, should be Comics::Plugin::$m)\n");
	    next;

lib/Comics.pm  view on Meta::CPAN


    $l_plugin -= $lpl;
    $l_fetcher -= $lft;
    my $fmt = "%-${l_name}s   %-${l_plugin}s   %-${l_fetcher}s   %-8s   %s\n";
    foreach my $comic ( @plugins ) {

	my $st = $state->{comics}->{ $comic->{tag} };
	no strict 'refs';
	printf( $fmt,
		$comic->{name},
		substr( ref($comic), $lpl ),
		substr( ${ref($comic)."::"}{ISA}[0], $lft ),
		$st->{disabled} ? "disabled" : "enabled",
		$comic->{update} ? $comic->{updated} : "",
	      );
    }

}

use LWP::UserAgent;

our $ua;
our $uuid;

sub run_plugins {

    unless ( $ua ) {
	$ua = LWP::UserAgent::Custom->new;
	$uuid = uuid();
    }

    foreach my $comic ( @plugins ) {
	warn("Plugin: ", $comic->{name}, "\n") if $verbose > 1;

	# Force existence of this comic's state otherwise
	# it will be autovivified within the fetch method
	# and never get outside.
	$state->{comics}->{$comic->{tag}} ||= {};

	# Make the state accessible.
	$comic->{state} = $state->{comics}->{$comic->{tag}};

	# Skip is disabled.
	next if $comic->{state}->{disabled} && !$force;

	# Run it, trapping errors.
	$stats->{tally}++;
	unless ( eval { $comic->fetch($reuse); 1 } ) {
	    $comic->{state}->{fail} = $@;
	    debug($comic->{state}->{fail});
	    push( @{ $stats->{fail} },
		  [ $comic->{name}, $comic->{state}->{fail} ] );
	}
    }
}

################ Index subroutines ################

sub build {

    # Change to the spooldir and collect all HTML fragments.
    chdir($spooldir) or die("$spooldir: $!\n");
    opendir( my $dir, "." );
    my @files = grep { /^[^._].+(?<!index)\.(?:html)$/ } readdir($dir);
    close($dir);
    warn("Number of images = ", scalar(@files), "\n") if $debug;
    $stats->{tally} = $stats->{uptodate} = @files if $rebuild;

    # Sort the fragments on last modification date.
    @files =
      map { $_->[0] }
	sort { $b->[1] <=> $a->[1] }
	  grep { $force || ! $state->{comics}->{$_->[2]}->{disabled} }
	    map { ( my $t = $_ ) =~ s/\.\w+$//;
		  [ $_, (stat($_))[9], $t  ] }
	      @files;

    if ( $debug > 1 ) {
	warn("Images (sorted):\n");
	warn("   $_\n") for @files;
    }

    # Creat icon.
    unless ( -s "comics.png" ) {
	require Comics::Utils::Icon;
	open( my $fd, '>:raw', "comics.png" );
	print $fd Comics::Utils::Icon::icon();
	close($fd);
    }

    # Create a new index.html.
    open( my $fd, '>:utf8', "index.html" );
    preamble($fd);
    htmlstats($fd);
    for ( @files ) {
	open( my $hh, '<:utf8', $_ )
	  or die("$_: $!");
	print { $fd } <$hh>;
	close($hh);
    }
    postamble($fd);
    close($fd);
}

sub preamble {
    my ( $fd ) = @_;
    print $fd <<EOD;
<html>
<head>
<title>Comics!</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<style type="text/css">
body {
    font-family : Verdana, Arial, Helvetica, sans-serif;
    text-align: center;
    margin-top: 0px;
    margin-right: 0px;
    margin-bottom: 10px;
    margin-left: 0px;
    font-size:12pt;
}
.toontable {
    background-color: #eee;
    padding: 9px;
    margin: 18px;
    border: 1px solid #ddd;
}
.toonimage {
    background-color: white;



( run in 0.576 second using v1.01-cache-2.11-cpan-364913b4093 )