6 eval q{ use Linux::UserXAttr ':all'; };
7 $use_xattr = $@ ?
0 : 1;
11 use constant
{EC_DOWNLOADED
=>0, EC_ERROR
=>1, EC_CACHED
=>2, EC_NOTOK
=>3, EC_NOTMODIFIED
=>4, EC_NOTUPDATED
=>5,};
12 use constant
{XATTR_URL
=>"user.xdg.origin.url", XATTR_CACHECONTROL
=>"user.cache.control", XATTR_MAXAGE
=>"user.cache.control.max_age", XATTR_EXPIRES
=>"user.cache.expires",};
17 'C!' => sub{ $use_caching = 0; },
20 ($url, $file) = @ARGV;
21 ($file) = $url =~ /([^\/]+)$/ if not $file;
23 exit main
($url, $file);
29 if(not removexattr
(@_))
31 return 0 unless $!{ENOTSUP
} or $!{ENOATTR
} or $!{ENODATA
};
36 sub file_mtime
{ return((stat $_[0])[9]); }
37 sub file_atime
{ return((stat $_[0])[8]); }
41 my ($data, $rsp, $prt) = @_;
42 my $file = $GLOB{file
};
43 my $chunk_n = ++$GLOB{chunk_n
};
45 sub cb_return
{ die join(' ', @_)."\n"; }
51 if(not $rsp->is_success)
57 $file_mtime = file_mtime
($file);
60 my $url_mtime = str2time
($rsp->header("Last-Modified"));
61 if(defined $url_mtime)
63 if($url_mtime <= $file_mtime)
65 cb_return EC_NOTUPDATED
;
67 $GLOB{url_mtime
} = $url_mtime;
70 my $expires = $rsp->header("Expires");
71 my $cache_control = $rsp->header("Cache-Control");
74 if($cache_control =~ /\b(s-maxage)=(\d+)/i)
78 elsif($cache_control =~ /\b(max-age)=(\d+)/i)
83 open $GLOB{'fh'}, '>', $file or cb_return EC_ERROR
, "$file: $!";
87 # https://www.freedesktop.org/wiki/CommonExtendedAttributes/
88 setxattr
($GLOB{'fh'}, XATTR_URL
, $rsp->request()->uri) or warn "setxattr: $!\n";
89 if(defined $cache_control)
91 setxattr
($GLOB{'fh'}, XATTR_CACHECONTROL
, $cache_control) or warn "setxattr: $!\n";
95 delxattr
($GLOB{'fh'}, XATTR_CACHECONTROL
) or warn "removexattr: $!\n";
99 setxattr
($GLOB{'fh'}, XATTR_MAXAGE
, $maxage) or warn "setxattr: $!\n";
103 delxattr
($GLOB{'fh'}, XATTR_MAXAGE
) or warn "removexattr: $!\n";
107 setxattr
($GLOB{'fh'}, XATTR_EXPIRES
, $expires) or warn "setxattr: $!\n";
111 delxattr
($GLOB{'fh'}, XATTR_EXPIRES
) or warn "removexattr: $!\n";
116 print {$GLOB{'fh'}} $data or cb_return EC_ERROR
, "$file: $!";
121 my ($localtime) = @_;
122 my $lc_time = POSIX
::setlocale
(POSIX
::LC_TIME
);
123 POSIX
::setlocale
(POSIX
::LC_TIME
, "C");
124 my $str = POSIX
::strftime
("%a, %d %b %Y %T %Z", localtime $localtime);
125 POSIX
::setlocale
(POSIX
::LC_TIME
, $lc_time);
131 my ($url, $file) = @_;
138 my $file_mtime = file_mtime
($file);
139 if($use_xattr and $use_caching)
141 my $local_maxage = getxattr
($file, XATTR_MAXAGE
);
142 my $local_expires = getxattr
($file, XATTR_EXPIRES
);
143 if(defined $local_maxage)
145 if($file_mtime + $local_maxage > $now)
150 elsif(defined $local_expires)
152 if(str2time
($local_expires) > $now)
158 $headers{"If-Modified-Since"} = time2rfc
($file_mtime);
161 my $ua = LWP
::UserAgent
->new;
162 %GLOB = (file
=> $file);
163 my $rsp = $ua->get($url, ':content_cb' => \
&process_response
, %headers);
167 my ($rc, $msg) = split /\s+/, $rsp->header("X-Died"), 2;
170 warn "$msg\n" if $msg;
174 if(defined $GLOB{url_mtime
})
176 utime file_atime
($file), $GLOB{url_mtime
}, $file or warn "$file: $!\n";
178 return EC_DOWNLOADED
;
182 warn "HTTP " . $rsp->code . " " . $rsp->message . "\n";
183 if($rsp->code == 304)
185 return EC_NOTMODIFIED
;
196 dlnew - Download web resource if local copy is older
200 dlnew [-C] <B<url>> <B<file>>
204 Download content from web if newer than local copy (based on Last-Modified and caching headers).
212 Bypass validating cache.
216 URL to be downloaded. Schema can be HTTP or HTTPS.
220 Local file data have to written in. If omitted, last component (basename) of B<url> will be used.
230 B<Url> ia found and downloaded.
234 General error, system errors.
238 Local B<file>'s freshness validated by saved cache metadata, not downloaded.
242 Download not OK. (usually Not Found)
246 B<Url> found but not modified. (HTTP 304)
250 B<Url> found but not updated, based on Last-Modified header.