Skip to content

Commit e7c60d5

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

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed

lib/Mojo/UserAgent.pm

Lines changed: 41 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,22 @@ 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) = (shift, shift, shift, shift // {});
54+
55+
my $tx = _download_error($self->transactor->download($self->head($url => $options->{headers} // {}), $path));
56+
return $tx ? !!_download_error($self->start($tx)) : 1;
57+
}
58+
59+
sub download_p {
60+
my ($self, $url, $path, $options) = (shift, shift, shift, shift // {});
61+
62+
return $self->head_p($url => $options->{headers} // {})->then(sub {
63+
my $tx = _download_error($self->transactor->download(shift, $path));
64+
return $tx ? $self->start_p($tx) : 1;
65+
})->then(sub { ref $_[0] ? !!_download_error($_[0]) : $_[0] });
66+
}
67+
5168
sub start {
5269
my ($self, $tx, $cb) = @_;
5370

@@ -213,6 +230,15 @@ sub _dequeue {
213230
return $found;
214231
}
215232

233+
sub _download_error {
234+
my $tx = shift;
235+
236+
return $tx unless my $err = $tx->error;
237+
return undef if $err->{incomplete_download} || $err->{complete_download};
238+
croak "$err->{code} response: $err->{message}" if $err->{code};
239+
croak "Connection error: $err->{message}";
240+
}
241+
216242
sub _error {
217243
my ($self, $id, $err) = @_;
218244
my $tx = $self->{connections}{$id}{tx};
@@ -748,6 +774,21 @@ a callback.
748774
warn "Connection error: $err";
749775
})->wait;
750776
777+
=head2 download
778+
779+
my $bool = $ua->download('https://example.com/test.tar.gz', '/home/sri/test.tar.gz');
780+
my $bool = $ua->download('https://example.com/test.tar.gz', '/home/sri/test.tar.gz', {headers => {Accept => '*/*'}});
781+
782+
Download file from URL to local file, returns true once the file has been downloaded completely. Incomplete downloads
783+
are resumed. Note that this method is B<EXPERIMENTAL> and might change without warning!
784+
785+
=head2 download_p
786+
787+
my $promise = $ua->download_p('https://example.com/test.tar.gz', '/home/sri/test.tar.gz');
788+
789+
Same as L</"download">, but performs all requests non-blocking and returns a L<Mojo::Promise> object. Note that this
790+
method is B<EXPERIMENTAL> and might change without warning!
791+
751792
=head2 get
752793
753794
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) == length $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 = $t->download(Mojo::Transaction::HTTP->new, '/home/sri/test.tar.gz');
416+
my $tx = $t->download(Mojo::Transaction::HTTP->new, '/home/sri/test.tar.gz', {headers => {Accept => '*/*'}});
417+
418+
Build L<Mojo::Transaction::HTTP> resumable file download request as follow-up to a C<HEAD> request. Note that this
419+
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)