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 )