aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/PhotoStorage
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet/PhotoStorage')
-rw-r--r--perllib/FixMyStreet/PhotoStorage/FileSystem.pm111
-rw-r--r--perllib/FixMyStreet/PhotoStorage/S3.pm122
2 files changed, 233 insertions, 0 deletions
diff --git a/perllib/FixMyStreet/PhotoStorage/FileSystem.pm b/perllib/FixMyStreet/PhotoStorage/FileSystem.pm
new file mode 100644
index 000000000..d61a26c7a
--- /dev/null
+++ b/perllib/FixMyStreet/PhotoStorage/FileSystem.pm
@@ -0,0 +1,111 @@
+package FixMyStreet::PhotoStorage::FileSystem;
+
+use Moose;
+use parent 'FixMyStreet::PhotoStorage';
+
+use Path::Tiny 'path';
+
+
+has upload_dir => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $dir = FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{UPLOAD_DIR} ||
+ FixMyStreet->config('UPLOAD_DIR');
+ return path($dir)->absolute(FixMyStreet->path_to());
+ },
+);
+
+=head2 init
+
+Creates UPLOAD_DIR and checks it's writeable.
+
+=cut
+
+sub init {
+ my $self = shift;
+ my $cache_dir = $self->upload_dir;
+ $cache_dir->mkpath;
+ unless ( -d $cache_dir && -w $cache_dir ) {
+ warn "\x1b[31mCan't find/write to photo cache directory '$cache_dir'\x1b[0m\n";
+ return;
+ }
+ return 1;
+}
+
+=head2 get_file
+
+Returns a Path::Tiny path to a file on disk identified by an ID and type.
+File may or may not exist. This handle is then used to read photo data or
+write to disk.
+
+=cut
+
+sub get_file {
+ my ($self, $fileid, $type) = @_;
+ my $cache_dir = $self->upload_dir;
+ return path( $cache_dir, "$fileid.$type" );
+}
+
+
+=head2 store_photo
+
+Stores a blob of binary data representing a photo on disk.
+Returns a key which is used in the future to get the contents of the file.
+
+=cut
+
+sub store_photo {
+ my ($self, $photo_blob) = @_;
+
+ my $type = $self->detect_type($photo_blob) || 'jpeg';
+ my $fileid = $self->get_fileid($photo_blob);
+ my $file = $self->get_file($fileid, $type);
+ $file->spew_raw($photo_blob);
+
+ return $file->basename;
+}
+
+
+=head2 retrieve_photo
+
+Fetches the file content of a particular photo from storage.
+Returns the binary blob and the filetype, if the photo exists in storage.
+
+=cut
+
+sub retrieve_photo {
+ my ($self, $filename) = @_;
+
+ my ($fileid, $type) = split /\./, $filename;
+ my $file = $self->get_file($fileid, $type);
+ if ($file->exists) {
+ my $photo = $file->slurp_raw;
+ return ($photo, $type);
+ }
+}
+
+
+=head2 validate_key
+
+A long-running FMS instance might have reports whose photo IDs in the DB
+don't include the file extension. This function takes a value from the DB and
+returns a 'tidied' version that can be used when calling photo_exists
+or retrieve_photo.
+
+If the passed key doesn't seem like it'll result in a valid filename (i.e.
+it's not a 40-char SHA1 hash) returns undef.
+
+=cut
+
+sub validate_key {
+ my ($self, $key) = @_;
+
+ my ($fileid, $type) = split /\./, $key;
+ $type ||= 'jpeg';
+ if ($fileid && length($fileid) == 40) {
+ return "$fileid.$type";
+ }
+}
+
+1;
diff --git a/perllib/FixMyStreet/PhotoStorage/S3.pm b/perllib/FixMyStreet/PhotoStorage/S3.pm
new file mode 100644
index 000000000..45325e9dc
--- /dev/null
+++ b/perllib/FixMyStreet/PhotoStorage/S3.pm
@@ -0,0 +1,122 @@
+package FixMyStreet::PhotoStorage::S3;
+
+use Moose;
+use parent 'FixMyStreet::PhotoStorage';
+
+use Net::Amazon::S3;
+use Try::Tiny;
+
+
+has client => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $key = FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{ACCESS_KEY};
+ my $secret = FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{SECRET_KEY};
+
+ my $s3 = Net::Amazon::S3->new(
+ aws_access_key_id => $key,
+ aws_secret_access_key => $secret,
+ retry => 1,
+ );
+ return Net::Amazon::S3::Client->new( s3 => $s3 );
+ },
+);
+
+has bucket => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ shift->client->bucket( name => FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{BUCKET} );
+ },
+);
+
+has region => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ return FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{REGION};
+ },
+);
+
+has prefix => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ my $prefix = FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{PREFIX};
+ return "" unless $prefix;
+ $prefix =~ s#/$##;
+ return "$prefix/";
+ },
+);
+
+sub init {
+ my $self = shift;
+
+ return 1 if $self->_bucket_exists();
+
+ if ( FixMyStreet->config('PHOTO_STORAGE_OPTIONS')->{CREATE_BUCKET} ) {
+ my $name = $self->bucket->name;
+ try {
+ $self->client->create_bucket(
+ name => $name,
+ location_constraint => $self->region,
+ );
+ } catch {
+ warn "\x1b[31mCouldn't create S3 bucket '$name'\x1b[0m\n";
+ return;
+ };
+
+ return 1 if $self->_bucket_exists();
+
+ warn "\x1b[31mCouldn't create S3 bucket '$name'\x1b[0m\n";
+ return;
+ } else {
+ my $bucket = $self->bucket->name;
+ warn "\x1b[31mS3 bucket '$bucket' doesn't exist and CREATE_BUCKET is not set.\x1b[0m\n";
+ return;
+ }
+}
+
+sub _bucket_exists {
+ my $self = shift;
+ my $name = $self->bucket->name;
+ my @buckets = $self->client->buckets;
+ return grep { $_->name eq $name } @buckets;
+}
+
+sub get_object {
+ my ($self, $key) = @_;
+ return $self->bucket->object( key => $key );
+}
+
+sub store_photo {
+ my ($self, $photo_blob) = @_;
+
+ my $type = $self->detect_type($photo_blob) || 'jpeg';
+ my $fileid = $self->get_fileid($photo_blob);
+ my $key = $self->prefix . "$fileid.$type";
+
+ my $object = $self->get_object($key);
+ $object->put($photo_blob);
+
+ return $key;
+}
+
+
+sub retrieve_photo {
+ my ($self, $key) = @_;
+
+ my $object = $self->get_object($key);
+ if ($object->exists) {
+ my ($fileid, $type) = split /\./, $key;
+ return ($object->get, $type);
+ }
+
+}
+
+sub validate_key { $_[1] }
+
+
+1;