The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package WebService::Dropbox::Files::UploadSession;
use strict;
use warnings;
use parent qw(Exporter);

our @EXPORT = do {
    no strict 'refs';
    grep { $_ !~ qr{ \A [A-Z]+ \z }xms } keys %{ __PACKAGE__ . '::' };
};

sub upload_session {
    my ($self, $path, $content, $optional_params, $limit) = @_;

    $limit ||= 4 * 1024 * 1024; # A typical chunk is 4 MB

    my $session_id;
    my $offset = 0;

    my $commit_params = {
        path => $path,
        %{ $optional_params || {} },
    };

    my $upload;
    $upload = sub {
        my $buf;
        my $total = 0;
        my $chunk = 1024;
        my $tmp = File::Temp->new;
        my $is_last;
        while (my $read = read($content, $buf, $chunk)) {
            $tmp->print($buf);
            $total += $read;
            my $remaining = $limit - $total;
            if ($chunk > $remaining) {
                $chunk = $remaining;
            }
            unless ($chunk) {
                last;
            }
        }

        $tmp->flush;
        $tmp->seek(0, 0);

        # finish or small file
        if ($total < $limit) {
            if ($session_id) {
                my $params = {
                    cursor => {
                        session_id => $session_id,
                        offset     => $offset,
                    },
                    commit => $commit_params,
                };
                return $self->upload_session_finish($tmp, $params);
            } else {
                return $self->upload($path, $tmp, $commit_params);
            }
        }

        # append
        elsif ($session_id) {
            my $params = {
                cursor => {
                    session_id => $session_id,
                    offset     => $offset,
                },
            };
            unless ($self->upload_session_append_v2($tmp, $params)) {
                # some error
                return;
            }
            $offset += $total;
        }

        # start
        else {
            my $res = $self->upload_session_start($tmp);
            if ($res && $res->{session_id}) {
                $session_id = $res->{session_id};
                $offset = $total;
            } else {
                # some error
                return;
            }
        }

        $upload->();
    };
    $upload->();
}

# https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
sub upload_session_start {
    my ($self, $content, $params) = @_;

    $self->api({
        url => 'https://content.dropboxapi.com/2/files/upload_session/start',
        params => $params,
        content => $content,
    });
}

# https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
sub upload_session_append_v2 {
    my ($self, $content, $params) = @_;

    $self->api({
        url => 'https://content.dropboxapi.com/2/files/upload_session/append_v2',
        params => $params,
        content => $content,
    });
}

# https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
sub upload_session_finish {
    my ($self, $content, $params) = @_;

    $self->api({
        url => 'https://content.dropboxapi.com/2/files/upload_session/finish',
        params => $params,
        content => $content,
    });
}

1;