Backblaze-B2V2Client
view release on metacpan or search on metacpan
lib/Backblaze/B2V2Client.pm view on Meta::CPAN
# did they provide a file location or path?
if ($args{file_location} && -e "$args{file_location}") {
$args{file_contents} = path( $args{file_location} )->slurp_raw;
# if they didn't provide a file-name, use the one on this file
$args{new_file_name} = path( $args{file_location} )->basename;
}
# were these file contents either provided or found?
if (!length($args{file_contents})) {
$self->error_tracker(qq{You must provide either a valid 'file_location' or 'file_contents' arg for b2_upload_file().});
return 'Error';
}
# check the other needed args
if (!$args{bucket_name} || !$args{new_file_name}) {
$self->error_tracker(qq{You must provide 'bucket_name' and 'new_file_name' args for b2_upload_file().});
return 'Error';
}
# default content-type
$args{content_type} ||= 'b2/x-auto';
# OK, let's continue: get the upload URL and authorization token for this bucket
$self->b2_get_upload_url( $args{bucket_name} );
# send the special request
$self->b2_talker(
'url' => $self->{bucket_info}{ $args{bucket_name} }{upload_url},
'authorization' => $self->{bucket_info}{ $args{bucket_name} }{authorization_token},
'file_contents' => $args{file_contents},
'special_headers' => {
'X-Bz-File-Name' => uri_escape( $args{new_file_name} ),
'X-Bz-Content-Sha1' => sha1_hex( $args{file_contents} ),
'Content-Type' => $args{content_type},
},
);
# b2_talker will handle the rest
# return current status
return $self->{current_status};
}
# method to get the information needed to upload into a specific B2 bucket
sub b2_get_upload_url {
my $self = shift;
# the bucket name is required
my ($bucket_name) = @_;
# bucket_name is required
if (!$bucket_name) {
$self->error_tracker('The bucket_name must be provided for b2_get_upload_url().');
return $self->{current_status};
}
# no need to proceed if we already have done for this bucket this during this session
# return if $self->{bucket_info}{$bucket_name}{upload_url};
# COMMENTED OUT: It seems like B2 wants a new upload_url endpoint for each upload,
# and we may want to upload multiple files into each bucket...so this won't work
# if we don't have the info for the bucket name, retrieve the bucket's ID
if (ref($self->{buckets}{$bucket_name}) ne 'HASH') {
$self->b2_list_buckets($bucket_name);
}
# send the request
$self->b2_talker(
'url' => $self->{api_url}.'/b2api/v2/b2_get_upload_url',
'authorization' => $self->{account_authorization_token},
'post_params' => {
'bucketId' => $self->{buckets}{$bucket_name}{bucket_id},
},
);
# if we succeeded, get the info for this bucket
if ($self->{current_status} eq 'OK') {
$self->{bucket_info}{$bucket_name} = {
'upload_url' => $self->{b2_response}{uploadUrl},
'authorization_token' => $self->{b2_response}{authorizationToken},
};
}
# send the status for consistency
return $self->{current_status};
}
# method to get information on one bucket or all buckets
# specify the bucket-name to search by name
sub b2_list_buckets {
my $self = shift;
# optional first arg is a target bucket name
# optional second arg tells us to auto-create a bucket, if the name is provided but it was not found
my ($bucket_name, $auto_create_bucket) = @_;
# send the request
$self->b2_talker(
'url' => $self->{api_url}.'/b2api/v2/b2_list_buckets',
'authorization' => $self->{account_authorization_token},
'post_params' => {
'accountId' => $self->{account_id},
'bucketName' => $bucket_name,
},
);
# if we succeeded, load in all the found buckets to $self->{buckets}
# that will be a hash of info, keyed by name
if ($self->{current_status} eq 'OK') {
foreach my $bucket_info (@{ $self->{b2_response}{buckets} }) {
$bucket_name = $$bucket_info{bucketName};
$self->{buckets}{$bucket_name} = {
'bucket_id' => $$bucket_info{bucketId},
'bucket_type' => $$bucket_info{bucketType},
lib/Backblaze/B2V2Client.pm view on Meta::CPAN
while ($remaining_file_size >= 0) {
# how much to send?
if ($remaining_file_size < $self->{recommended_part_size} ) {
$size_sent = $remaining_file_size;
} else {
$size_sent = $self->{recommended_part_size} ;
}
# get the next upload url for this part
$self->b2_talker(
'url' => $self->{api_url}.'/b2api/v2/b2_get_upload_part_url',
'authorization' => $self->{account_authorization_token},
'post_params' => {
'fileId' => $large_file_id,
},
);
# read in that section of the file and prep the SHA
sysread FH, $file_contents_part, $size_sent;
push(@$sha1_array,sha1_hex( $file_contents_part ));
# upload that part
$self->b2_talker(
'url' => $self->{b2_response}{uploadUrl},
'authorization' => $self->{b2_response}{authorizationToken},
'special_headers' => {
'X-Bz-Content-Sha1' => $$sha1_array[-1],
'X-Bz-Part-Number' => $part_number,
'Content-Length' => $size_sent,
},
'file_contents' => $file_contents_part,
);
# advance
$part_number++;
$remaining_file_size -= $self->{recommended_part_size} ;
}
# close the file
close FH;
# and tell B2
$self->b2_talker(
'url' => $self->{api_url}.'/b2api/v2/b2_finish_large_file',
'authorization' => $self->{account_authorization_token},
'post_params' => {
'fileId' => $large_file_id,
'partSha1Array' => $sha1_array,
},
);
# phew, i'm tired...
return $self->{current_status};
}
# generic method to handle communication to B2
sub b2_talker {
my $self = shift;
# args hash must include 'url' for the target API endpoint URL
# most other requests will also include a 'post_params' hashref, and 'authorization' value for the header
# for the b2_upload_file function, there will be several other headers + a file_contents arg
my (%args) = @_;
if (!$args{url}) {
$self->error_tracker('Can not use b2_talker() without an endpoint URL.');
}
# if they sent an Authorization header, set that value
if ($args{authorization}) {
$self->{mech}->delete_header( 'Authorization' );
$self->{mech}->add_header( 'Authorization' => $args{authorization} );
}
my ($response, $response_code, $error_message, $header, @header_keys);
# short-circuit if we had difficulty logging in previously
if ($self->{b2_login_error}) {
# track the error / set current state
$self->error_tracker("Problem logging into Backblaze. Please check the 'errors' array in this object.", $args{url});
return $self->{current_status};
}
# are we uploading a file?
if ($args{url} =~ /b2_upload_file|b2_upload_part/) {
# add the special headers
@header_keys = keys %{ $args{special_headers} };
foreach $header (@header_keys) {
$self->{mech}->delete_header( $header );
$self->{mech}->add_header( $header => $args{special_headers}{$header} );
}
# now upload the file
eval {
$response = $self->{mech}->post( $args{url}, content => $args{file_contents} );
# we want this to be 200
$response_code = $response->{_rc};
$self->{b2_response} = decode_json( $self->{mech}->content() );
};
# remove those special headers, cleaned-up for next time
foreach $header (@header_keys) {
$self->{mech}->delete_header( $header );
}
# if not uploading and they sent POST params, we are doing a POST
} elsif (ref($args{post_params}) eq 'HASH') {
eval {
# send the POST
$response = $self->{mech}->post( $args{url}, content => encode_json($args{post_params}) );
# we want this to be 200
$response_code = $response->code;
# decode results
$self->{b2_response} = decode_json( $self->{mech}->content() );
};
# otherwise, we are doing a GET
} else {
( run in 0.527 second using v1.01-cache-2.11-cpan-39bf76dae61 )