1 # Blosxom Plugin: allconsuming -*- cperl -*-
2 # Author: Todd Larason (jtl@molehill.org)
4 # Blosxom Home/Docs/Licensing: http://blosxom.sourceforge.net/
5 # Netflix plugin Home/Docs/Licensing:
6 # http://molelog.molehill.org/blox/Computers/Internet/Web/Blosxom/AllConsuming/
9 # http://allconsuming.net/news/000012.html
11 # -------------- Configuration Variables --------------
12 # AllConsuming username
14 unless defined $username;
16 # Amazon Associate ID; feel free to leave this =)
17 $associate_id = 'mtmolel-20'
18 unless defined $associate_id;
21 # 0 == "don't list at all"
22 # >0 == list first N (or all, if < N)
23 # <0 == list random N (or all in random order, if < N)
25 purchased => 5, # most recent 5
26 reading => undef, # all
27 rereading => undef, # all
28 favorite => -5, # random 5
29 completed => 5, # most recent 5
31 ) unless scalar keys %num > 0;
33 # one of: SOAP::Lite, LWP, wget (or a pathname to wget), curl (or a pathname)
34 # SOAP::Lite should be fastest and most likely to stay working long-term,
35 # but is the hardest to get installed
37 unless defined $networking;
39 # Whether to try to use caching; default is yes, and caching is very
40 # strongly recommended
42 unless defined $use_caching;
44 # how long to go between re-fetching the data, in seconds
45 # default value is 1 week
46 $max_cache_data_age = 60 * 60 * 24 * 7
47 unless defined $max_cache_data_age;
49 # how long to go between re-formatting the lists, in seconds
50 # default is 5 minutes
51 $max_cache_layout_age = 60 * 5
52 unless defined $max_cache_layout_age;
55 unless defined $debug_level;
56 # -----------------------------------------------------
67 use vars qw/$username $associate_id $max_cache_data_age $max_cache_layout_age
68 %num $networking $use_caching $debug_level
69 $purchased $reading $rereading $favorite $completed $nofinish/;
72 my $package = "allconsuming";
73 my $cachefile = "$blosxom::plugin_state_dir/.$package.cache";
76 # General utility functions
79 my ($level, @msg) = @_;
81 if ($debug_level >= $level) {
82 print STDERR "$package debug $level: @msg\n";
88 return $blosxom::template->('', "$package.$bit", $blosxom::flavour);
92 my ($bit, $listname, $title, $author, $asin, $image, $allconsuming_url, $amazon_url) = @_;
93 my $f = load_template("$listname.$bit") || load_template($bit);
94 $title = encode_entities($title);
95 $author = encode_entities($author);
96 $f =~ s/((\$[\w:]+)|(\$\{[\w:]+\}))/$1 . "||''"/gee;
100 sub encode_entities {
102 eval "require HTML::Entities";
104 my %map = ('<' => 'lt', '&' => 'amp', '>' => 'gt');
105 my $keys = join '',keys %map;
106 $text =~ s:([$keys]):&$map{$1};:g;
109 return HTML::Entities::encode_entities($text);
117 if ($networking =~ m:curl:) {
118 return `$networking -m 30 -s "$url"`;
119 } elsif ($networking =~ m:wget:) {
120 return `$networking --quiet -O - "$url"`;
121 } elsif ($networking eq 'LWP') {
122 foreach (qw/LWP::UserAgent HTTP::Request::Common/) {
125 debug(0, "Can't load $_, can't use LWP networking: $@");
129 my $ua = LWP::UserAgent->new;
130 my $res = $ua->request(HTTP::Request::Common::GET $url);
131 if (!$res->is_success) {
132 my $error = $res->status_line;
133 debug(0, "HTTP GET error: $error");
136 return $res->content;
138 debug(0, "ERROR: invalid \$networking $networking");
142 # AllConsuming-specific networking
144 sub allconsuming_handle {
145 if ($networking eq 'SOAP::Lite') {
146 eval "require SOAP::Lite;";
148 debug(0, "SOAP::Lite couldn't be loaded");
152 my $soap = SOAP::Lite
153 -> uri('http://www.allconsuming.net/AllConsumingAPI')
154 -> proxy('http://www.allconsuming.net/soap.cgi');
156 -> call(new => $now[2], $now[3], $now[4] + 1, $now[5] + 1900)
158 return {soap => $soap,
160 map => {purchased => 'GetPurchasedBooksList',
161 reading => 'GetCurrentlyReadingList',
162 rereading => 'GetRereadingBooksList',
163 favorite => 'GetFavoriteBooksList',
164 completed => 'GetCompletedBooksList',
165 nofinish => 'GetNeverFinishedBooksList'}
169 map => {purchased => 'purchased_books',
170 reading => 'currently_reading',
171 rereading => 'rereading_books',
172 favorite => 'favorite_books',
173 completed => 'completed_books',
174 nofinish => 'never_finished_books'}
179 sub allconsuming_lookup {
180 my ($handle, $username, $list) = @_;
182 return undef unless defined $handle;
184 if ($networking eq 'SOAP::Lite') {
185 return undef unless defined $handle->{map}{$list};
186 return $handle->{soap}
187 -> call($handle->{map}{$list} => $handle->{obj}, $username)
190 my $data = GET('http://allconsuming.net/soap-client.cgi?' .
191 "$handle->{map}{$list}=1&username=$username");
192 $data =~ s:\A\<pre>\$VAR1 =(.*)</pre>\Z:\1:ms;
198 if (defined $cache->{data} and
199 $^T - $cache->{data_timestamp} < $max_cache_data_age) {
202 debug(1, "cache miss data");
203 $cache->{data_timestamp} = $^T;
204 my $obj = allconsuming_handle();
206 foreach (keys %num) {
207 next if defined($num{$_}) && $num{$_} == 0;
208 $cache->{data}{$_} = allconsuming_lookup($obj, $username, $_);
216 return if (!$use_caching);
217 eval "require Storable";
219 debug(1, "cache disabled, Storable not available");
223 if (!Storable->can('lock_retrieve')) {
224 debug(1, "cache disabled, Storable::lock_retrieve not available");
228 $cache = (-r $cachefile ? Storable::lock_retrieve($cachefile) : {});
229 if (defined(param('allconsuming'))) {
230 if (param('allconsuming') eq 'refresh_data') {
232 } elsif (param('allconsuming') eq 'refresh_layout') {
233 $cache->{layout} = {};
239 return if (!$use_caching || !$save_cache);
240 debug(1, "Saving cache");
241 -d $blosxom::plugin_state_dir
242 or mkdir $blosxom::plugin_state_dir
243 or (debug(0, "State dir $blosxom::plugin_state_dir nonexistant and noncreatable: $!") and return);
244 Storable::lock_store($cache, $cachefile);
248 my ($listname, $num, $list) = @_;
252 return '' if (defined $num and $num == 0);
253 $list = [$list] if (ref $list eq 'HASH');
254 if (defined $list and defined $num and $num < 0) {
255 # algorithm from Algorithm::Numerical::Shuffle by Abigail
256 for (my $i = @$list; -- $i;) {
257 my $r = int rand ($i + 1);
258 ($list -> [$i], $list -> [$r]) = ($list -> [$r], $list -> [$i]);
262 $results = report('head', $listname);
264 $results .= report('item', $listname,
265 @{$_}{qw/title author asin image allconsuming_url
268 last if (defined $num and $count == $num);
270 $results .= report('foot', $listname);
275 # Blosxom plugin interface
282 foreach (keys %num) {
283 next if defined($num{$_}) && $num{$_} == 0;
285 $$_ = $cache->{layout}{$_}{$blosxom::flavour};
286 next if (defined $$_ &&
287 ($^T - $cache->{layout_timestamp}{$_}{$blosxom::flavour}
288 < $max_cache_layout_age));
289 debug(1, "cache miss layout $_ $blosxom::flavour");
290 $$_ = build_list($_, $num{$_}, $cache->{data}{$_}{asins});
291 $cache->{layout}{$_}{$blosxom::flavour} = $$_;
292 $cache->{layout_timestamp}{$_}{$blosxom::flavour} = $^T;
302 return 0 unless defined $username;
304 last if /^(__END__)?$/;
306 my ($flavour, $comp, $txt) = split ' ',$_,3;
308 $blosxom::template{$flavour}{"$package.$comp"} = $txt;
315 error head <table class="allconsuming $listname">\n
316 error item <tr><td><a href="http://www.amazon.com/exec/obidos/ASIN/$asin/$associate_id/ref=nosim"><img border="0" src="$image" alt="$title Book cover"></a></td><td><a href="http://www.amazon.com/exec/obidos/ASIN/$asin/$associate_id/ref=nosim"><i>$title</i></a>, $author</td></tr>\n
322 Blosxom Plug-in: allconsuming
326 Purpose: Lets you easily share your AllConsuming information
328 * $allconsuming::purchased -- list of books you've purchased
329 * $allconsuming::reading -- list of books you're reading
330 * $allconsuming::rereading -- list of books you're re-reading
331 * $allconsuming::favorite -- list of your favorite books
332 * $allconsuming::completed -- list of books you've completed
333 * $allconsuming::nofinish -- list of books you never finished
343 Todd Larason <jtl@molehill.org>, http://molelog.molehill.org/
345 This plugin is now maintained by the Blosxom Sourceforge Team,
346 <blosxom-devel@lists.sourceforge.net>.
350 None known; please send bug reports and feedback to the Blosxom
351 development mailing list <blosxom-devel@lists.sourceforge.net>.
355 =head2 Configuration variables
357 C<$username> is your AllConsuming username. Until it's defined, this plugin does nothing.
359 C<$associate_id> is an Amazon Associate ID. By default, it's mine;
360 change it to yours if you have one.
362 C<%num> sets how many items to include in each list. Each of C<purchased>,
363 C<reading>, C<rereading>, C<favorite>, C<completed> and C<nofinish> can be
364 set separately. Setting C<$num{foo}> to undef means to include the whole
365 list; setting it to 0 means to not build the list at all (or retrieve the
366 data from AllConsuming); setting it to a positive number N means to list the
367 first N items (or the whole list, if there aren't that many items) in order;
368 setting it to a negative number -N means to list a randomly selected set of
369 N items (or the whole list, in a random order, if there are fewer than N
372 C<$networking> controls which networking implemenentation to use. If set to
373 'SOAP::Lite', then the SOAP::Lite module will be used to communicate with
374 AllConsuming's official SOAP interface; this method is preferable for both
375 speed and reliability, but requires by far the most work to get working if
376 you don't already have the modules installed. If set to 'LWP', then the
377 LWP family of modules will be used to communicate with a demonstration CGI
378 script. If set to 'wget' or 'curl' (or a pathname that includes one of
379 those), then the respective external utility is used to communicate with
380 the demonstration CGI script.
382 C<$use_caching> is a boolean controlling whether to use caching at all.
383 Caching is very strongly recommended -- AllConsuming is rather slow.
385 C<$max_cache_data_age> sets how long to cache the downloaded AllConsuming
386 information for. Fetching the data is pretty slow, so this defaults to a high
389 C<$max_cache_layout_age> sets how long to cache the formatted data.
390 Formatting the data is relatively fast, so this defaults to a small value -- 5
391 minutes. If you aren't modifying templates and aren't using randomized lists,
392 this can be set to the same as $max_cache_data_age without ill effects.
394 C<$debug_level> can be set to a value between 0 and 5; 0 will output
395 no debug information, while 5 will be very verbose. The default is 1,
396 and should be changed after you've verified the plugin is working
399 =head2 Classes for CSS control
401 There's are some classes used, available for CSS customization.
403 * C<allconsuming> -- all lists are in the netflix class
404 * C<purchased>, etc -- each list is also in a class named for the list
406 =head2 Flavour-style files
408 If you want a format change that can't be made by CSS, you can
409 override the HTML generated by creating files similar to Blosxom's
410 flavour files. They should be named allconsuming.I<bit>.I<flavour>; for
411 available I<bit>s and their default meanings, see the C<__DATA__>
412 section in the plugin.
416 Because fetching the queue information is relatively slow, caching is very
417 strongly recommended. Caching requires a version of the Storable module
418 that supports the 'lock_save' and 'lock_retrieve' functions.
420 Since the data reload is so slow, you may wish to raise the $max_cache_data_age
421 even higher, and use explicit cache reloading. The cache can be reloaded
422 either by deleting the cache file $plugin_state_dir/.allconsuming.cache, or
423 by passing an C<allconsuming=refresh_data> parameter to the blosxom script;
424 the latter is preferable, as you can insure that you take the time hit, not
430 Copyright 2003, Todd Larason
432 (This license is the same as Blosxom's)
434 Permission is hereby granted, free of charge, to any person obtaining a
435 copy of this software and associated documentation files (the "Software"),
436 to deal in the Software without restriction, including without limitation
437 the rights to use, copy, modify, merge, publish, distribute, sublicense,
438 and/or sell copies of the Software, and to permit persons to whom the
439 Software is furnished to do so, subject to the following conditions:
441 The above copyright notice and this permission notice shall be included
442 in all copies or substantial portions of the Software.
444 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
445 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
446 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
447 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
448 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
449 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
450 OTHER DEALINGS IN THE SOFTWARE.