diff --git a/IkiWiki/Plugin/amazon_s3.pm b/IkiWiki/Plugin/amazon_s3.pm new file mode 100644 index 000000000..b5629eb4a --- /dev/null +++ b/IkiWiki/Plugin/amazon_s3.pm @@ -0,0 +1,161 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::amazon_s3; + +use warnings; +no warnings 'redefine'; +use strict; +use IkiWiki 2.00; +use IkiWiki::Render; +use Net::Amazon::S3; + +# Store references to real subs before overriding them. +our %subs; +BEGIN { + foreach my $sub (qw{IkiWiki::writefile IkiWiki::prune}) { + $subs{$sub}=\&$sub; + } +}; + +sub import { #{{{ + hook(type => "checkconfig", id => "amazon_s3", call => \&checkconfig); +} # }}} + +sub checkconfig { #{{{ + foreach my $field (qw{amazon_s3_key_id amazon_s3_key_file + amazon_s3_bucket}) { + if (! exists $config{$field} || ! defined $config{$field}) { + error(sprintf(gettext("Must specify %s"), $field)); + } + } + if (! exists $config{amazon_s3_prefix} || + ! defined $config{amazon_s3_prefix}) { + $config{amazon_s3_prefix}="wiki/"; + } +} #}}} + +{ +my $bucket; +sub getbucket { #{{{ + return $bucket if defined $bucket; + + open(IN, "<", $config{amazon_s3_key_file}) || error($config{amazon_s3_key_file}.": ".$!); + my $key=; + chomp $key; + close IN; + + my $s3=Net::Amazon::S3->new({ + aws_access_key_id => $config{amazon_s3_key_id}, + aws_secret_access_key => $key, + retry => 1, + }); + + # make sure the bucket exists + if (exists $config{amazon_s3_location}) { + $bucket=$s3->add_bucket({ + bucket => $config{amazon_s3_bucket}, + location_constraint => $config{amazon_s3_location}, + }); + } + else { + $bucket=$s3->add_bucket({ + bucket => $config{amazon_s3_bucket}, + }); + } + + if (! $bucket) { + error(gettext("Failed to create bucket in S3: "). + $s3->err.": ".$s3->errstr."\n"); + } + + return $bucket; +} #}}} +} + +package IkiWiki; +use File::MimeInfo; +use Encode; + +# This is a wrapper around the real writefile. +sub writefile ($$$;$$) { #{{{ + my $file=shift; + my $destdir=shift; + my $content=shift; + my $binary=shift; + my $writer=shift; + + # First, write the file to disk. + my $ret=$IkiWiki::Plugin::amazon_s3::subs{'IkiWiki::writefile'}->($file, $destdir, $content, $binary, $writer); + + # Now, determine if the file was written to the destdir. + # writefile might be used for writing files elsewhere. + # Also, $destdir might be set to a subdirectory of the destdir. + my $key; + if ($destdir eq $config{destdir}) { + $key=$file; + } + elsif ("$destdir/$file" =~ /^\Q$config{destdir}\/\E(.*)/) { + $key=$1; + } + + # Store the data in S3. + if (defined $key) { + $key=$config{amazon_s3_prefix}.$key; + my $bucket=IkiWiki::Plugin::amazon_s3::getbucket(); + + # The http layer tries to downgrade utf-8 + # content, but that can fail (see + # http://rt.cpan.org/Ticket/Display.html?id=35710), + # so force convert it to bytes. + $content=encode_utf8($content) if defined $content; + + if (defined $content && ! length $content) { + # S3 doesn't allow storing empty files! + $content=" "; + } + + my %opts=( + acl_short => 'public-read', + content_type => mimetype("$destdir/$file"), + ); + my $res; + if (! $writer) { + $res=$bucket->add_key($key, $content, \%opts); + } + else { + # read back in the file that the writer emitted + $res=$bucket->add_key_filename($key, "$destdir/$file", \%opts); + } + if ($res && $key=~/(^|\/)index.$config{htmlext}$/) { + # index.html files are a special case. Since S3 is + # not a normal web server, it won't serve up + # foo/index.html when foo/ is requested. So the + # file has to be stored twice. (This is bad news + # when usedirs is enabled!) + $key=~s/index.$config{htmlext}$//; + if (! $writer) { + $res=$bucket->add_key($key, $content, \%opts); + } + else { + $res=$bucket->add_key_filename($key, "$destdir/$file", \%opts); + } + } + if (! $res) { + error(gettext("Failed to save file to S3: "). + $bucket->err.": ".$bucket->errstr."\n"); + } + } + + return $ret; +} #}}} + +# This is a wrapper around the real prune. +sub prune ($) { #{{{ + my $file=shift; + + my $bucket=IkiWiki::Plugin::amazon_s3::getbucket(); + print STDERR "wrapped prune\n"; + + return $IkiWiki::Plugin::amazon_s3::subs{'IkiWiki::writefile'}->($file); +} #}}} + +1