Skip to content

Commit d8ddcb3

Browse files
committed
Add download methods to Mojo::UserAgent and Mojo::UserAgent::Transactor
1 parent bd690df commit d8ddcb3

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

lib/Mojo/UserAgent.pm

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use Mojo::Base 'Mojo::EventEmitter';
33

44
# "Fry: Since when is the Internet about robbing people of their privacy?
55
# Bender: August 6, 1991."
6+
use Carp qw(croak);
67
use Mojo::IOLoop;
78
use Mojo::Promise;
89
use Mojo::Util qw(monkey_patch term_escape);
@@ -48,6 +49,24 @@ sub DESTROY { shift->_cleanup unless ${^GLOBAL_PHASE} eq 'DESTRUCT' }
4849
sub build_tx { shift->transactor->tx(@_) }
4950
sub build_websocket_tx { shift->transactor->websocket(@_) }
5051

52+
sub download {
53+
my ($self, $url, $path, $options) = @_;
54+
$options //= {};
55+
56+
my $tx = _download_error($self->transactor->download($self->head($url => $options->{headers} // {}), $path));
57+
return $tx ? !!_download_error($self->start($tx)) : 1;
58+
}
59+
60+
sub _download_error {
61+
my $tx = shift;
62+
if (my $err = $tx->error) {
63+
return undef if $err->{incomplete_download} || $err->{complete_download};
64+
croak "$err->{code} response: $err->{message}" if $err->{code};
65+
croak "Connection error: $err->{message}";
66+
}
67+
return $tx;
68+
}
69+
5170
sub start {
5271
my ($self, $tx, $cb) = @_;
5372

@@ -748,6 +767,14 @@ a callback.
748767
warn "Connection error: $err";
749768
})->wait;
750769
770+
=head2 download
771+
772+
my $bool = $ua->download('https://example.com/test.tar.gz', '/home/sri/test.tar.gz');
773+
my $bool = $ua->download('https://example.com/test.tar.gz', '/home/sri/test.tar.gz', {headers => {Accept => '*/*'}});
774+
775+
Download file from URL to local file, returns true once the file has been downloaded completely. Incomplete downloads
776+
are resumed. Note that this method is B<EXPERIMENTAL> and might change without warning!
777+
751778
=head2 get
752779
753780
my $tx = $ua->get('example.com');

lib/Mojo/UserAgent/Transactor.pm

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package Mojo::UserAgent::Transactor;
22
use Mojo::Base -base;
33

4+
use Carp qw(croak);
45
use Mojo::Asset::File;
56
use Mojo::Asset::Memory;
67
use Mojo::Content::MultiPart;
@@ -20,6 +21,44 @@ has name => 'Mojolicious (Perl)';
2021

2122
sub add_generator { $_[0]->generators->{$_[1]} = $_[2] and return $_[0] }
2223

24+
sub download {
25+
my ($self, $head, $path) = @_;
26+
27+
my $req = $head->req;
28+
my $tx = $self->tx(GET => $req->url->clone => $req->headers->to_hash);
29+
my $res = $tx->res;
30+
if (my $error = $head->error) { $res->error($error) and return $tx }
31+
32+
my $headers = $head->res->headers;
33+
my $accept_ranges = $headers->accept_ranges =~ /bytes/;
34+
croak 'Download error: Unknown file size' unless my $size = $headers->content_length;
35+
36+
my $current_size = 0;
37+
my $file = path($path);
38+
if (-f $file) {
39+
$current_size = -s $file;
40+
$res->error({message => 'Download error', complete_download => 1}) and return $tx if $current_size >= $size;
41+
croak "Download error: Server does not support partial requests" unless $accept_ranges;
42+
$tx->req->headers->range("bytes=$current_size-$size");
43+
}
44+
my $fh = $file->open('+>>');
45+
$res->content->unsubscribe('read')->on(
46+
read => sub {
47+
my ($content, $bytes) = @_;
48+
$current_size += length $bytes;
49+
$fh->syswrite($bytes) or croak qq/Can't write to file "$path": $!/;
50+
}
51+
);
52+
$res->on(
53+
finish => sub {
54+
my $res = shift;
55+
$res->error({message => 'Download error', incomplete_download => 1}) if $current_size < $size;
56+
}
57+
);
58+
59+
return $tx;
60+
}
61+
2362
sub endpoint {
2463
my ($self, $tx) = @_;
2564

@@ -371,6 +410,14 @@ Register a content generator.
371410
372411
$t->add_generator(foo => sub ($t, $tx, @args) {...});
373412
413+
=head2 download
414+
415+
my $tx = $ua->download(Mojo::Transaction::HTTP->new, '/home/sri/test.tar.gz');
416+
my $tx = $ua->download(Mojo::Transaction::HTTP->new, '/home/sri/test.tar.gz', {headers => {Accept => '*/*'}});
417+
418+
Build L<Mojo::Transaction::HTTP> follow-up request for C<HEAD> request to allow for resumable file downloads. Note that
419+
this method is B<EXPERIMENTAL> and might change without warning!
420+
374421
=head2 endpoint
375422
376423
my ($proto, $host, $port) = $t->endpoint(Mojo::Transaction::HTTP->new);

0 commit comments

Comments
 (0)