# Blosxom Plugin: allconsuming -*- cperl -*- # Author: Todd Larason (jtl@molehill.org) # Version: 0+4i # Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/ # Netflix plugin Home/Docs/Licensing: # http://molelog.molehill.org/blox/Computers/Internet/Web/Blosxom/AllConsuming/ package allconsuming; # http://allconsuming.net/news/000012.html # -------------- Configuration Variables -------------- # AllConsuming username $username = undef unless defined $username; # Amazon Associate ID; feel free to leave this =) $associate_id = 'mtmolel-20' unless defined $associate_id; # undef == "list all" # 0 == "don't list at all" # >0 == list first N (or all, if < N) # <0 == list random N (or all in random order, if < N) %num = ( purchased => 5, # most recent 5 reading => undef, # all rereading => undef, # all favorite => -5, # random 5 completed => 5, # most recent 5 nofinish => 0 # none ) unless scalar keys %num > 0; # one of: SOAP::Lite, LWP, wget (or a pathname to wget), curl (or a pathname) # SOAP::Lite should be fastest and most likely to stay working long-term, # but is the hardest to get installed $networking = 'LWP' unless defined $networking; # Whether to try to use caching; default is yes, and caching is very # strongly recommended $use_caching = 1 unless defined $use_caching; # how long to go between re-fetching the data, in seconds # default value is 1 week $max_cache_data_age = 60 * 60 * 24 * 7 unless defined $max_cache_data_age; # how long to go between re-formatting the lists, in seconds # default is 5 minutes $max_cache_layout_age = 60 * 5 unless defined $max_cache_layout_age; $debug_level = 1 unless defined $debug_level; # ----------------------------------------------------- $purchased = ''; $reading = ''; $rereading = ''; $favorite = ''; $completed = ''; $nofinish = ''; use CGI qw/param/; use strict; use vars qw/$username $associate_id $max_cache_data_age $max_cache_layout_age %num $networking $use_caching $debug_level $purchased $reading $rereading $favorite $completed $nofinish/; my $cache; my $package = "allconsuming"; my $cachefile = "$blosxom::plugin_state_dir/.$package.cache"; my $save_cache = 0; # General utility functions sub debug { my ($level, @msg) = @_; if ($debug_level >= $level) { print STDERR "$package debug $level: @msg\n"; } } sub load_template { my ($bit) = @_; return $blosxom::template->('', "$package.$bit", $blosxom::flavour); } sub report { my ($bit, $listname, $title, $author, $asin, $image, $allconsuming_url, $amazon_url) = @_; my $f = load_template("$listname.$bit") || load_template($bit); $title = encode_entities($title); $author = encode_entities($author); $f =~ s/((\$[\w:]+)|(\$\{[\w:]+\}))/$1 . "||''"/gee; return $f; } sub encode_entities { my ($text) = @_; eval "require HTML::Entities"; if ($@) { my %map = ('<' => 'lt', '&' => 'amp', '>' => 'gt'); my $keys = join '',keys %map; $text =~ s:([$keys]):&$map{$1};:g; return $text; } return HTML::Entities::encode_entities($text); } # General networking sub GET { my ($url) = @_; if ($networking =~ m:curl:) { return `$networking -m 30 -s "$url"`; } elsif ($networking =~ m:wget:) { return `$networking --quiet -O - "$url"`; } elsif ($networking eq 'LWP') { foreach (qw/LWP::UserAgent HTTP::Request::Common/) { eval "require $_"; if ($@) { debug(0, "Can't load $_, can't use LWP networking: $@"); return undef; } } my $ua = LWP::UserAgent->new; my $res = $ua->request(HTTP::Request::Common::GET $url); if (!$res->is_success) { my $error = $res->status_line; debug(0, "HTTP GET error: $error"); return undef; } return $res->content; } else { debug(0, "ERROR: invalid \$networking $networking"); } } # AllConsuming-specific networking sub allconsuming_handle { if ($networking eq 'SOAP::Lite') { eval "require SOAP::Lite;"; if ($@) { debug(0, "SOAP::Lite couldn't be loaded"); return undef; } my @now = localtime; my $soap = SOAP::Lite -> uri('http://www.allconsuming.net/AllConsumingAPI') -> proxy('http://www.allconsuming.net/soap.cgi'); my $obj = $soap -> call(new => $now[2], $now[3], $now[4] + 1, $now[5] + 1900) -> result; return {soap => $soap, obj => $obj, map => {purchased => 'GetPurchasedBooksList', reading => 'GetCurrentlyReadingList', rereading => 'GetRereadingBooksList', favorite => 'GetFavoriteBooksList', completed => 'GetCompletedBooksList', nofinish => 'GetNeverFinishedBooksList'} }; } else { return { map => {purchased => 'purchased_books', reading => 'currently_reading', rereading => 'rereading_books', favorite => 'favorite_books', completed => 'completed_books', nofinish => 'never_finished_books'} }; } } sub allconsuming_lookup { my ($handle, $username, $list) = @_; return undef unless defined $handle; if ($networking eq 'SOAP::Lite') { return undef unless defined $handle->{map}{$list}; return $handle->{soap} -> call($handle->{map}{$list} => $handle->{obj}, $username) -> result; } else { my $data = GET('http://allconsuming.net/soap-client.cgi?' . "$handle->{map}{$list}=1&username=$username"); $data =~ s:\A\
\$VAR1 =(.*)\Z:\1:ms; return eval $data; } } sub get_data { if (defined $cache->{data} and $^T - $cache->{data_timestamp} < $max_cache_data_age) { return; } debug(1, "cache miss data"); $cache->{data_timestamp} = $^T; my $obj = allconsuming_handle(); foreach (keys %num) { next if defined($num{$_}) && $num{$_} == 0; $cache->{data}{$_} = allconsuming_lookup($obj, $username, $_); } $save_cache = 1; } # Cache handling sub prime_cache { return if (!$use_caching); eval "require Storable"; if ($@) { debug(1, "cache disabled, Storable not available"); $use_caching = 0; return 0; } if (!Storable->can('lock_retrieve')) { debug(1, "cache disabled, Storable::lock_retrieve not available"); $use_caching = 0; return 0; } $cache = (-r $cachefile ? Storable::lock_retrieve($cachefile) : {}); if (defined(param('allconsuming'))) { if (param('allconsuming') eq 'refresh_data') { $cache = {}; } elsif (param('allconsuming') eq 'refresh_layout') { $cache->{layout} = {}; } } } sub save_cache { return if (!$use_caching || !$save_cache); debug(1, "Saving cache"); -d $blosxom::plugin_state_dir or mkdir $blosxom::plugin_state_dir or (debug(0, "State dir $blosxom::plugin_state_dir nonexistant and noncreatable: $!") and return); Storable::lock_store($cache, $cachefile); } sub build_list { my ($listname, $num, $list) = @_; my $count = 0; my $results; return '' if (defined $num and $num == 0); $list = [$list] if (ref $list eq 'HASH'); if (defined $list and defined $num and $num < 0) { # algorithm from Algorithm::Numerical::Shuffle by Abigail for (my $i = @$list; -- $i;) { my $r = int rand ($i + 1); ($list -> [$i], $list -> [$r]) = ($list -> [$r], $list -> [$i]); } $num = -$num; } $results = report('head', $listname); foreach (@$list) { $results .= report('item', $listname, @{$_}{qw/title author asin image allconsuming_url amazon_url/}); $count++; last if (defined $num and $count == $num); } $results .= report('foot', $listname); return $results; } # Blosxom plugin interface sub head { prime_cache(); get_data(); save_cache(); foreach (keys %num) { next if defined($num{$_}) && $num{$_} == 0; no strict; $$_ = $cache->{layout}{$_}{$blosxom::flavour}; next if (defined $$_ && ($^T - $cache->{layout_timestamp}{$_}{$blosxom::flavour} < $max_cache_layout_age)); debug(1, "cache miss layout $_ $blosxom::flavour"); $$_ = build_list($_, $num{$_}, $cache->{data}{$_}{asins}); $cache->{layout}{$_}{$blosxom::flavour} = $$_; $cache->{layout_timestamp}{$_}{$blosxom::flavour} = $^T; $save_cache = 1; use strict; } save_cache(); 1; } sub start { return 0 unless defined $username; while () { last if /^(__END__)?$/; chomp; my ($flavour, $comp, $txt) = split ' ',$_,3; $txt =~ s:\\n:\n:g; $blosxom::template{$flavour}{"$package.$comp"} = $txt; } return 1; } 1; __DATA__ error head
$title, $author |