summaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin/amazon_s3.pm
blob: cc15fbdb1bd2a967ed7b2aca4e4979b9fd81ffdb (plain)
  1. #!/usr/bin/perl
  2. package IkiWiki::Plugin::amazon_s3;
  3. use warnings;
  4. no warnings 'redefine';
  5. use strict;
  6. use IkiWiki 2.00;
  7. use IkiWiki::Render;
  8. use Net::Amazon::S3;
  9. # Store references to real subs before overriding them.
  10. our %subs;
  11. BEGIN {
  12. foreach my $sub (qw{IkiWiki::writefile IkiWiki::prune}) {
  13. $subs{$sub}=\&$sub;
  14. }
  15. };
  16. sub import { #{{{
  17. hook(type => "checkconfig", id => "amazon_s3", call => \&checkconfig);
  18. } # }}}
  19. sub checkconfig { #{{{
  20. foreach my $field (qw{amazon_s3_key_id amazon_s3_key_file
  21. amazon_s3_bucket}) {
  22. if (! exists $config{$field} || ! defined $config{$field}) {
  23. error(sprintf(gettext("Must specify %s"), $field));
  24. }
  25. }
  26. if (! exists $config{amazon_s3_prefix} ||
  27. ! defined $config{amazon_s3_prefix}) {
  28. $config{amazon_s3_prefix}="wiki/";
  29. }
  30. } #}}}
  31. {
  32. my $bucket;
  33. sub getbucket { #{{{
  34. return $bucket if defined $bucket;
  35. open(IN, "<", $config{amazon_s3_key_file}) || error($config{amazon_s3_key_file}.": ".$!);
  36. my $key=<IN>;
  37. chomp $key;
  38. close IN;
  39. my $s3=Net::Amazon::S3->new({
  40. aws_access_key_id => $config{amazon_s3_key_id},
  41. aws_secret_access_key => $key,
  42. retry => 1,
  43. });
  44. # make sure the bucket exists
  45. if (exists $config{amazon_s3_location}) {
  46. $bucket=$s3->add_bucket({
  47. bucket => $config{amazon_s3_bucket},
  48. location_constraint => $config{amazon_s3_location},
  49. });
  50. }
  51. else {
  52. $bucket=$s3->add_bucket({
  53. bucket => $config{amazon_s3_bucket},
  54. });
  55. }
  56. if (! $bucket) {
  57. error(gettext("Failed to create bucket in S3: ").
  58. $s3->err.": ".$s3->errstr."\n");
  59. }
  60. return $bucket;
  61. } #}}}
  62. }
  63. package IkiWiki;
  64. use File::MimeInfo;
  65. use Encode;
  66. # This is a wrapper around the real writefile.
  67. sub writefile ($$$;$$) { #{{{
  68. my $file=shift;
  69. my $destdir=shift;
  70. my $content=shift;
  71. my $binary=shift;
  72. my $writer=shift;
  73. # First, write the file to disk.
  74. my $ret=$IkiWiki::Plugin::amazon_s3::subs{'IkiWiki::writefile'}->($file, $destdir, $content, $binary, $writer);
  75. # Now, determine if the file was written to the destdir.
  76. # writefile might be used for writing files elsewhere.
  77. # Also, $destdir might be set to a subdirectory of the destdir.
  78. my $key;
  79. if ($destdir eq $config{destdir}) {
  80. $key=$file;
  81. }
  82. elsif ("$destdir/$file" =~ /^\Q$config{destdir}\/\E(.*)/) {
  83. $key=$1;
  84. }
  85. # Store the data in S3.
  86. if (defined $key) {
  87. $key=$config{amazon_s3_prefix}.$key;
  88. my $bucket=IkiWiki::Plugin::amazon_s3::getbucket();
  89. # The http layer tries to downgrade utf-8
  90. # content, but that can fail (see
  91. # http://rt.cpan.org/Ticket/Display.html?id=35710),
  92. # so force convert it to bytes.
  93. $content=encode_utf8($content) if defined $content;
  94. if (defined $content && ! length $content) {
  95. # S3 doesn't allow storing empty files!
  96. $content=" ";
  97. }
  98. my %opts=(
  99. acl_short => 'public-read',
  100. content_type => mimetype("$destdir/$file"),
  101. );
  102. my $res;
  103. if (! $writer) {
  104. $res=$bucket->add_key($key, $content, \%opts);
  105. }
  106. else {
  107. # read back in the file that the writer emitted
  108. $res=$bucket->add_key_filename($key, "$destdir/$file", \%opts);
  109. }
  110. if ($res && $key=~/(^|.*\/)index.$config{htmlext}$/) {
  111. # index.html files are a special case. Since S3 is
  112. # not a normal web server, it won't serve up
  113. # foo/index.html when foo/ is requested. So the
  114. # file has to be stored twice. (This is bad news
  115. # when usedirs is enabled!)
  116. # TODO: invesitgate using the new copy operation.
  117. # (It may not be robust enough.)
  118. my $base=$1;
  119. if (! $writer) {
  120. $res=$bucket->add_key($base, $content, \%opts);
  121. }
  122. else {
  123. $res=$bucket->add_key_filename($base, "$destdir/$file", \%opts);
  124. }
  125. }
  126. if (! $res) {
  127. error(gettext("Failed to save file to S3: ").
  128. $bucket->err.": ".$bucket->errstr."\n");
  129. }
  130. }
  131. return $ret;
  132. } #}}}
  133. # This is a wrapper around the real prune.
  134. sub prune ($) { #{{{
  135. my $file=shift;
  136. # If a file in the destdir is being pruned, need to delete it out
  137. # of S3 as well.
  138. if ($file =~ /^\Q$config{destdir}\/\E(.*)/) {
  139. my $key=$config{amazon_s3_prefix}.$1;
  140. my $bucket=IkiWiki::Plugin::amazon_s3::getbucket();
  141. my $res=$bucket->delete_key($key);
  142. if ($res && $key=~/(^|.*\/)index.$config{htmlext}$/) {
  143. # index.html special case: Delete other file too
  144. $res=$bucket->delete_key($1);
  145. }
  146. if (! $res) {
  147. error(gettext("Failed to delete file from S3: ").
  148. $bucket->err.": ".$bucket->errstr."\n");
  149. }
  150. }
  151. return $IkiWiki::Plugin::amazon_s3::subs{'IkiWiki::prune'}->($file);
  152. } #}}}
  153. 1