PawsX-S3-Uploader

 view release on metacpan or  search on metacpan

lib/PawsX/S3/Uploader.pm  view on Meta::CPAN

use v5.26; ## no critic(ProhibitVersionStrings)
package PawsX::S3::Uploader;
use strict;
use warnings;
use Feature::Compat::Class 0.07;
use Feature::Compat::Try 0.05;
use builtin::compat 0.003003 qw(true false);
use Carp ();

class PawsX::S3::Uploader;

our $VERSION = '0.0.3'; # VERSION
# ABSTRACT: upload to S3 from a streaming source


# aws doesn't want small multiparts
sub MULTIPART_MIN_SIZE { 5*1024*1024 }


field $s3               :param;
field $bucket           :param;
field $key              :param;
field $min_part_size    :param = MULTIPART_MIN_SIZE;
field $_under_testing   :param(_under_testing) = false;
field $extra_arguments  :param = {};
field $always_multipart :param = false;
field $callback         :param = undef;

field $output :reader;
    
field $upload_id;
field @parts;
field $part_body = '';
field $aborted = false;

## no critic(RequireEndWithOne) # critic gets confused

ADJUST {
    if (!$_under_testing && $min_part_size < MULTIPART_MIN_SIZE) {
        $min_part_size = MULTIPART_MIN_SIZE;
    }
}

method _maybe_callback($op, $details) {
    return unless $callback;
    $callback->($op, $details);
}

method _start_multipart() {
    my $upload = $s3->CreateMultipartUpload(
        $extra_arguments->%*,
        Bucket => $bucket,
        Key    => $key,
    );
    $upload_id = $upload->UploadId;

    $self->_maybe_callback(start_multipart => { upload_id => $upload_id });
}


method _upload_part() {
    my $part_number = 1+@parts;

    try {
        my $part_res = $s3->UploadPart(
            Body       => $part_body,
            Bucket     => $bucket,
            Key        => $key,
            PartNumber => $part_number,
            UploadId   => $upload_id,
        );
        push @parts, {
            PartNumber => $part_number,

lib/PawsX/S3/Uploader.pm  view on Meta::CPAN

    } else {
        $self->_put_object();
    }

    $self->_maybe_callback(finish => { output => $output });

    return $output;
}


method abort($details={}) {
    Carp::croak('Upload already completed') if $output;

    # pretend we can abort more than once
    return if $aborted;

    # if we haven't started the multipart upload, there's nothing to abort
    if ($upload_id) {
        $s3->AbortMultipartUpload(
            Bucket   => $bucket,
            Key      => $key,
            UploadId => $upload_id,
        );
    }

    $aborted = true;
    $self->_maybe_callback(abort_multipart => $details);
}


method upload_fh($fh) {
    my ($part, $done) = ('',false);

    while (!$done) {
        my $size = sysread $fh, $part, $min_part_size;
        if (!defined $size) {
            my $error = $!;
            $self->abort({ error => $error });
            Carp::croak("sysread failed: $error");
        }

        $self->add($part) if $size > 0;
        $part='';
        $done = $size==0;
    }

    return $self->finish;
}


1;

__END__

=pod

=encoding UTF-8

=head1 NAME

PawsX::S3::Uploader - upload to S3 from a streaming source

=head1 VERSION

version 0.0.3

=head1 SYNOPSIS

    use Paws;
    use PawsX::S3::Uploader;
    use Feature::Compat::Try; # or a recent perl

    my $s3 = Paws->service(S3 => ( region => 'eu-west-1' ));

    my $uploader = PawsX::S3::Uploader->new(
        s3     => $s3,
        bucket => 'my-bucket',
        key    => 'my-object-key',

        # optional, anything that PutObject and CreateMultipartUpload
        # can understand
        extra_arguments => {
            ServerSideEncryption => 'aws:kms',
            SSEKMSKeyId          => 'the-secret-key-id',
            Metadata             => { foo => 'bar' },
            Tagging              => 'foo=bar&baz=3',
        }
    );

    try {
        while (my $block = read_data_from_somewhere()) {
            $uploader->add($block);
        }

        $uploader->finish;
    }
    catch ($error) {
        # if `read_data_from_somewhere` can throw, you need to abort
        # the upload yourself
        $uploader->abort();

        warn $error;
    }

=head1 DESCRIPTION

This class automates uploading objects to AWS S3 via L<Paws>, for the
cases where you can't keep the whole data in memory, or you can't even
generate it all ahead of time.

Underneath, it uses L<< C<PutObject>|Paws::S3::PutObject >> for
objects smaller than 5MB, and L<<
C<CreateMultipartUpload>|Paws::S3::CreateMultipartUpload >> / L<<
C<UploadPart>|Paws::S3::UploadPart >> / L<<
C<CompleteMultipartUpload>|Paws::S3::CompleteMultipartUpload >> for
larger objects.

Exceptions in C<UploadPart> cause the upload to be L<aborted|/abort>,
and the exception is re-thrown (so you can trap it yourself if you
need to). You should also explicitly L<abort|/abort> the upload if
something goes wrong in your own code.



( run in 0.798 second using v1.01-cache-2.11-cpan-140bd7fdf52 )