File: Fetch.rakumod

package info (click to toggle)
raku-zef 0.13.8-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 664 kB
  • sloc: perl: 22; makefile: 8
file content (121 lines) | stat: -rw-r--r-- 4,704 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use Zef;
use Zef::Utils::FileSystem;

class Zef::Fetch does Fetcher does Pluggable {

    =begin pod

    =title class Zef::Fetch

    =subtitle A configurable implementation of the Fetcher interface

    =head1 Synopsis

    =begin code :lang<raku>

        use Zef;
        use Zef::Fetch;

        # Setup with a single fetcher backend
        my $fetcher = Zef::Fetch.new(
            backends => [
                { module => "Zef::Service::Shell::curl" },
                { module => "Zef::Service::Shell::PowerShell::download" },
            ],
        );

        # Save the content of $uri to $save-to
        my $uri      = "https://httpbin.org/ip";
        my $save-to  = $*CWD.child("output.txt");
        my $saved-to = $fetcher.fetch(Candidate.new(:$uri), $save-to);

        say $saved-to ?? $saved-to.slurp !! "Failed to download and save";

    =end code

    =head1 Description

    A C<Fetcher> class that uses 1 or more other C<Fetcher> instances as backends. It abstracts the logic
    to do 'grab this uri with the first backend that supports the given uri'.

    =head1 Methods

    =head2 method fetch-matcher

        method fetch-matcher($path --> Bool:D)

    Returns C<True> if any of the probeable C<self.plugins> know how to fetch C<$path>.

    =head2 method fetch

        method fetch(Candidate $candi, IO() $save-to, Supplier :$logger, Int :$timeout --> IO::Path)

    Fetches the files for C<$candi> (usually as C<$candi.uri>) to C<$save-to>. If a backend fails to fetch
    for some reason (such as going over its C<:$timeout>) the next matching backend will be used. Failure occurs
    when no backend was able to fetch the C<$candi>.

    An optional C<:$logger> can be supplied to receive events about what is occurring.

    An optional C<:$timeout> can be passed to denote the number of seconds after which we'll assume failure.

    On success it returns the C<IO::Path> where the data was actually fetched to. On failure it returns C<Nil>.

    Note this differs from other 'Fetcher' adapters C<method fetch> (i.e. the fetchers this uses as backends) which
    take a C<Str $uri> as the first parameter, not a C<Candidate $candi>.

    =end pod


    submethod TWEAK(|) {
        @ = self.plugins; # preload plugins
    }

    #| Returns true if any of the backends 'fetch-matcher' understand the given uri/path
    method fetch-matcher($uri --> Bool:D) { return so self!fetch-matcher($uri) }

    #| Returns the backends that understand the given uri based on their fetch-matcher result
    method !fetch-matcher($uri --> Array[Fetcher]) {
        my @matching-backends = self.plugins.grep(*.fetch-matcher($uri));

        my Fetcher @results = @matching-backends;
        return @results;
    }

    #| Fetch the given url.
    #| Will return the first successful result while attempting to fetch the given $candi.
    method fetch(Candidate $candi, IO() $save-to, Supplier :$logger, Int :$timeout --> IO::Path) {
        my $uri      = $candi.uri;
        my @fetchers = self!fetch-matcher($uri).cache;

        unless +@fetchers {
            my @report_enabled  = self.plugins.map(*.short-name);
            my @report_disabled = self.backends.map(*.<short-name>).grep({ $_ ~~ none(@report_enabled) });

            die "Enabled fetching backends [{@report_enabled}] don't understand $uri\n"
            ~   "You may need to configure one of the following backends, or install its underlying software - [{@report_disabled}]";
        }

        my $got := @fetchers.map: -> $fetcher {
            if ?$logger {
                $logger.emit({ level => DEBUG, stage => FETCH, phase => START, candi => $candi, message => "Fetching $uri with plugin: {$fetcher.^name}" });
                $fetcher.stdout.Supply.act: -> $out { $logger.emit({ level => VERBOSE, stage => FETCH, phase => LIVE, candi => $candi, message => $out }) }
                $fetcher.stderr.Supply.act: -> $err { $logger.emit({ level => ERROR,   stage => FETCH, phase => LIVE, candi => $candi, message => $err }) }
            }

            my $ret = lock-file-protect("{$save-to}.lock", -> {
                my $todo    = start { try $fetcher.fetch($uri, $save-to) };
                my $time-up = ($timeout ?? Promise.in($timeout) !! Promise.new);
                await Promise.anyof: $todo, $time-up;
                $logger.emit({ level => DEBUG, stage => FETCH, phase => LIVE, candi => $candi, message => "Fetching $uri timed out" })
                    if ?$logger && $time-up.so && $todo.not;
                $todo.so ?? $todo.result !! Nil;
            });

            $ret;
        }

        my IO::Path $result = $got.grep(*.so).map(*.IO).head;
        return $result;
    }
}