File Coverage

blib/lib/WebService/MODIS.pm
Criterion Covered Total %
statement 27 409 6.6
branch 0 184 0.0
condition 0 12 0.0
subroutine 9 42 21.4
pod 17 23 73.9
total 53 670 7.9


line stmt bran cond sub pod time code
1             package WebService::MODIS;
2              
3 1     1   950 use strict;
  1         2  
  1         28  
4 1     1   5 use warnings;
  1         2  
  1         32  
5 1     1   18 use Carp;
  1         2  
  1         69  
6 1     1   5 use LWP::UserAgent;
  1         2  
  1         32  
7 1     1   5 use File::Basename;
  1         1  
  1         80  
8 1     1   2442 use File::HomeDir;
  1         10937  
  1         78  
9 1     1   7 use File::Path qw(make_path);
  1         2  
  1         62  
10 1     1   835 use Date::Simple;
  1         7889  
  1         40  
11 1     1   6 use List::Util qw(any max min none);
  1         3  
  1         5743  
12              
13             require Exporter;
14             our @ISA = qw(Exporter);
15              
16             our %EXPORT_TAGS = ( 'all' => [ qw(initCache readCache writeCache getCacheState getVersions getModisProducts getModisDates getModisGlobal) ] );
17             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
18             our @EXPORT = qw(initCache readCache writeCache getCacheState getVersions isGlobal);
19              
20             our $VERSION = '1.6';
21              
22             my %modisProducts = ();
23             my %modisDates = ();
24             my %modisGlobal = ();
25             my $modisProductsFile = "ModisProducts.pl";
26             my $modisDatesFile = "ModisDates.pl";
27             my $modisGlobalFile = "ModisGlobal.pl";
28             my $cacheDir;
29              
30             my $cacheState = '';
31              
32             my $BASE_URL = "http://e4ftl01.cr.usgs.gov";
33             my @DATA_DIR = ("MOLA", "MOLT", "MOTA");
34              
35             if (lc($^O) =~ /mswin/) {
36             $cacheDir = File::HomeDir->my_home()."/AppData/Local/WebService-MODIS";
37             } else {
38             $cacheDir = File::HomeDir->my_home()."/.cache/WebService-MODIS";
39             }
40              
41             sub new {
42 0     0 1   my $class = shift;
43 0           my %options = @_;
44 0           my $self = {
45             product => '',
46             version => '',
47             dates => [],
48             h => [],
49             v => [],
50             ifExactDates => 0,
51             ifExactHV => 0,
52             targetDir => ".",
53             forceReload => 0,
54             %options,
55             url => [],
56             };
57 0           bless($self, $class);
58              
59             # User input error checks
60 0 0         $self->product($self->{product}) if ($self->{product} ne '');
61 0 0         $self->version($self->{version}) if ($self->{version} ne '');
62 0 0         $self->dates($self->{dates}) if (@{$self->{dates}} != 0);
  0            
63 0 0         $self->h($self->{h}) if (@{$self->{h}} != 0);
  0            
64 0 0         $self->v($self->{v}) if (@{$self->{v}} != 0);
  0            
65 0 0         $self->ifExactDates($self->{ifExactDates}) if ($self->{ifExactDates} != 0);
66 0 0         $self->ifExactHV($self->{ifExactHV}) if ($self->{ifExactHV} != 0);
67              
68 0           return($self);
69             }
70              
71             #######################################################################
72             ### For debuging and informations
73              
74             # returns the cached products data hash
75             sub getModisProducts() {
76 0     0 0   return(%modisProducts);
77             }
78              
79             #returns the reference to the dates array
80             sub getModisDates($) {
81 0     0 0   my $product = shift;
82 0           return($modisDates{$product});
83             }
84              
85             # returns the cached global grid check hash
86             sub getModisGlobal() {
87 0     0 0   return(%modisGlobal);
88             }
89              
90             ########################################################################
91             ### Cache not related to object. Need to create own object, which gets
92             ### part of WebService::MODIS
93              
94             ### retrieve the directory structure from the server
95             sub initCache {
96             # my $self = shift;
97 0     0 1   %modisProducts = getAvailProducts();
98 0           %modisDates = getAvailDates(\%modisProducts);
99 0           for (keys %modisProducts) {
100 0           my $check = isGlobal($_);
101 0           $modisGlobal{$_} = $check;
102             }
103 0           $cacheState = "mem";
104             }
105              
106             ### load the previously saved server side directory structure from
107             ### local files
108             sub readCache {
109             # my $self = shift;
110 0     0 1   my $arg = shift;
111 0 0         $cacheDir = $arg if ($arg);
112 0 0         croak "Cache directory '$cacheDir' does not exist. Use 'writeCache' create it first or check your parameter!" if (!-d $cacheDir);
113              
114 0 0         if (-s "$cacheDir/$modisProductsFile") {
115 0           %modisProducts = do "$cacheDir/$modisProductsFile";
116 0 0         if (-s "$cacheDir/$modisDatesFile") {
117 0           %modisDates = do "$cacheDir/$modisDatesFile";
118 0 0         if (-s "$cacheDir/$modisGlobalFile") {
119 0           %modisGlobal = do "$cacheDir/$modisGlobalFile";
120 0           $cacheState = "file";
121 0           return;
122             } else {
123 0           croak "Cache file '$cacheDir/$modisGlobalFile' is empty. Use 'writeCache' to recreate it.";
124             }
125             } else {
126 0           croak "Cache file '$cacheDir/$modisDatesFile' is empty. Use 'writeCache' to recreate it.";
127             }
128             } else {
129 0           croak "Cache file '$cacheDir/$modisProductsFile' is empty. Use 'writeCache' to recreate it.";
130             }
131 0           return(99);
132             }
133              
134             ### write the server side directory structure to local files
135             sub writeCache {
136             # my $self = shift;
137 0     0 1   my $arg = shift;
138 0 0         $cacheDir = $arg if ($arg);
139              
140 0 0         croak "Nothing cached yet!" if ($cacheState eq '');
141 0 0         carp "Rewriting old cache to file" if ($cacheState eq 'file');
142              
143 0 0         if ( ! -d $cacheDir ) {
144 0 0         make_path($cacheDir) or croak "Could not create cache dir ('$cacheDir'): $!";
145             }
146              
147 0           my $fhd;
148              
149 0 0         open($fhd, ">", "$cacheDir/$modisProductsFile") or
150             croak "cannot open '$cacheDir/$modisProductsFile' for writing: $!\n";
151 0           for (keys %modisProducts) {
152 0           print $fhd "'$_' => '$modisProducts{$_}',\n";
153             }
154 0 0         close($fhd) or carp "close '$cacheDir/$modisProductsFile' failed: $!";
155              
156 0 0         open($fhd, ">", "$cacheDir/$modisDatesFile") or
157             croak "cannot open '$cacheDir/$modisDatesFile' for writing: $!\n";
158 0           for (keys %modisDates) {
159 0           print $fhd "'$_' => ['".join("', '", @{$modisDates{$_}})."'],\n";
  0            
160             }
161 0 0         close($fhd) or carp "close '$cacheDir/$modisDatesFile' failed: $!";
162              
163 0 0         open($fhd, ">", "$cacheDir/$modisGlobalFile") or
164             croak "cannot open '$cacheDir/$modisGlobalFile' for writing: $!\n";
165 0           for (keys %modisGlobal) {
166 0           print $fhd "'$_' => $modisGlobal{$_},\n";
167             }
168              
169 0           $cacheState = "file";
170             }
171              
172             sub getCacheState {
173             # my $self = shift;
174 0     0 1   return $cacheState;
175             }
176              
177             ### return a list of available version for a given product
178             sub getVersions($) {
179 0     0 1   my $product = shift;
180 0           my @versions = ();
181 0           foreach (keys %modisProducts) {
182 0 0         next if (! /$product/);
183 0           s/$product.//;
184 0           push(@versions, $_);
185             }
186 0           return(@versions);
187             }
188              
189             ### check is Product is global or on sinusoidal grid (with h??v?? in name)
190             sub isGlobal($) {
191 0     0 1   my $product = shift;
192 0           my $global = 1;
193              
194 0 0   0     croak "'$product' is not in the MODIS product list: Check name or refresh the cache." if (! any { /$product/ } keys %modisProducts);
  0            
195              
196 0           my @flist = getDateFullURLs($product, ${$modisDates{$product}}[0]);
  0            
197 0           my $teststr = $flist[0];
198 0           $teststr =~ s/.*\///;
199 0 0         $global = 0 if ($teststr =~ /h[0-9]{2}v[0-9]{2}/);
200 0           return($global);
201             }
202              
203             ##################################################
204             ### methods for returning object informations
205             ### or setting them
206              
207             sub product {
208 0     0 1   my $self = shift;
209              
210 0 0         if (@_) {
211 0 0         if ($cacheState eq '') {
212 0           carp "Cache not initialized or loaded, cannot check availability of '$_[0]'.";
213             } else {
214 0           my $failed=1;
215 0 0   0     $failed = 0 if any { /$_[0]\.[0-9]{3}/ } (keys %modisProducts);
  0            
216 0 0         croak "Product '$_[0]' not available!" if $failed;
217             }
218 0           $self->{product} = shift;
219 0           $self->{version} = '';
220 0           $self->{url} =[];
221 0           return;
222             }
223 0           return $self->{product};
224             }
225              
226             sub version {
227 0     0 1   my $self = shift;
228 0 0         if (@_) {
229 0           $self->{version} = shift;
230 0 0         if ($self->{product} eq '') {
231 0           carp "No product specified yet, so specifying the version does not make sense.";
232             } else {
233 0           my @vers = getVersions($self->{product});
234 0 0   0     if (none {/$self->{version}/} @vers) {
  0            
235 0           carp "Version ".$self->{version}." does not exist! Resetting it to ''.";
236 0           $self->{version} = ''
237             }
238             }
239 0           $self->{url} = [];
240 0           return;
241             }
242 0           return $self->{version};
243             }
244              
245             sub dates {
246 0     0 1   my $self = shift;
247 0 0         if (@_) {
248 0           my $refDates = shift;
249 0 0         if ($self->{product} eq '') {
250 0           carp "No product specified yet, No availability check possible.";
251             } else {
252             # check availability
253             }
254 0           $self->{dates} = $refDates;
255 0           $self->{url} = [];
256 0           return;
257             }
258 0           return @{$self->{dates}};
  0            
259             }
260              
261             sub h {
262 0     0 1   my $self = shift;
263 0 0         if (@_) {
264 0           my $refH = shift;
265 0 0   0     if (any {$_ < 0 or $_ > 35} @$refH) {
  0 0          
266 0           croak "Invalid h values supplied. Valid range: 0-35.";
267             }
268 0           $self->{h} = $refH;
269 0           $self->{url} = [];
270 0           return;
271             }
272 0           return @{$self->{h}};
  0            
273             }
274              
275             sub v {
276 0     0 1   my $self = shift;
277 0 0         if (@_) {
278 0           my $refV = shift;
279 0 0   0     if (any {$_ < 0 or $_ > 17} @$refV) {
  0 0          
280 0           croak "Invalid v values supplied. Valid range: 0-17.";
281             }
282 0           $self->{v} = $refV;
283 0           $self->{url} = [];
284 0           return;
285             }
286 0           return @{$self->{v}};
  0            
287             }
288              
289             sub ifExactDates {
290 0     0 1   my $self = shift;
291 0 0         if (@_) {
292 0           my $nDates = @{$self->{dates}};
  0            
293 0 0         carp "Sure you want to set this before setting the dates!" if ($nDates==0);
294 0           $self->{ifExactDates} = shift;
295 0           $self->{url} = [];
296 0           return;
297             }
298 0           return $self->{ifExactDates};
299             }
300              
301             sub ifExactHV {
302 0     0 1   my $self = shift;
303 0 0         if (@_) {
304             # include error checks
305 0           my $nH = @{$self->{h}};
  0            
306 0           my $nV = @{$self->{v}};
  0            
307 0 0 0       carp "You are setting 'ifExactHV' before setting 'h' and/or 'v'!" if ($nH==0 or $nV==0);
308 0           $self->{ifExactHV} = shift;
309 0           $self->{url} = [];
310 0           return;
311             }
312 0 0         if ($self->{ifExactHV}) {
313 0           my $nH = @{$self->{h}};
  0            
314 0           my $nV = @{$self->{v}};
  0            
315 0 0 0       carp "You set 'ifExactHV' before setting 'h' and/or 'v'!" if ($nH==0 or $nV==0);
316 0 0         carp "If ifExactHV is set, 'h' and 'v' should have equal length!" if ($nH != $nV);
317             }
318 0           return $self->{ifExactHV};
319             }
320              
321             sub url {
322 0     0 1   my $self = shift;
323 0           return @{$self->{url}};
  0            
324             }
325              
326             ########################################################################
327             ### method initializing the URL list
328             ### does not need to be done by hand, is called from within download method.
329             ### This method also checks for all invalid combinations.
330              
331             sub createUrl {
332 0     0 1   my $self = shift;
333 0           my $product = $self->{product};
334              
335 0 0   0     croak "Product '$product' unknown!" if (! any { /$product/ } (keys %modisProducts));
  0            
336              
337 0           my $version = $self->{version};
338 0           my @availVersions = getVersions($product);
339              
340             ### check the MODIS product version
341 0 0         if ($version ne '') {
342 0 0   0     if (any { /$version/ } @availVersions) {
  0            
343 0           $product = "${product}.${version}";
344             } else {
345 0           croak "Version $version not available for $product (available: ".join(" ,", @availVersions).").\n";
346             }
347             } else {
348 0           $version='000';
349 0           foreach (@availVersions) {
350 0 0         $version = $_ if (int($_) > int($version));
351             }
352 0           $product = "${product}.${version}";
353             }
354 0           $self->{version} = $version;
355              
356             ### check the product date availability and reset the $self->{dates} array
357 0 0         if ($self->{ifExactDates}) {
358 0           my @dates = @{$self->{dates}};
  0            
359 0           my @cleanedDates = @dates;
360 0           foreach (@dates) {
361 0           my $failed = 0;
362 0 0   0     $failed=1 if none { /$_/ } @{$modisDates{$product}};
  0            
  0            
363 0 0         if ($failed) {
364 0           @cleanedDates = grep { $_ != $_ } @cleanedDates;
  0            
365 0           carp "Date '$_' not available! Removing it from list";
366             }
367             }
368 0           @dates = ();
369 0           foreach (@cleanedDates) {
370 0           s/\./\-/g;
371 0           push(@dates, Date::Simple->new($_));
372             }
373 0           $self->{dates} = \@dates;
374             } else {
375 0           my @dates = @{$self->{dates}};
  0            
376 0           my @newDates = ();
377 0           foreach (@dates) {
378 0           s/\./\-/g;
379 0           push(@newDates, Date::Simple->new($_));
380             }
381 0           my @cleanedDates = ();
382 0           foreach (@{$modisDates{$product}}) {
  0            
383 0           s/\./\-/g;
384 0           my $modisDate = Date::Simple->new($_);
385 0 0         next if ($modisDate - min(@newDates) < 0);
386 0 0         next if ($modisDate - max(@newDates) > 0);
387 0           push(@cleanedDates, $modisDate);
388             }
389 0           $self->{dates} = \@cleanedDates;
390             }
391              
392             ### check the h and v availability, but only if necessary
393 0 0         if (!$modisGlobal{$product}) {
394 0           my @h = @{$self->{h}};
  0            
395 0           my @v = @{$self->{v}};
  0            
396 0 0         if ($self->{ifExactHV}) {
397 0           my $nH = @{$self->{h}};
  0            
398 0           my $nV = @{$self->{v}};
  0            
399 0 0         if ($nH != $nV) {
400 0 0         carp "If ifExactHV is set, 'h' and 'v' should have equal length!" if ($nH != $nV);
401 0           @h = splice(@h, 0, min($nH, $nV));
402 0           @v = splice(@v, 0, min($nH, $nV));
403             }
404             } else {
405 0           my @newH = ();
406 0           my @newV = ();
407 0           foreach my $h (min(@h)..max(@h)) {
408 0           foreach my $v (min(@v)..max(@v)) {
409 0           push(@newH, $h);
410 0           push(@newV, $v);
411             }
412             }
413 0           $self->{h} = \@newH;
414 0           $self->{v} = \@newV;
415             }
416             }
417              
418 0           my @url = ();
419 0           foreach (@{$self->{dates}}) {
  0            
420 0           my @fullUrl = getDateFullURLs($product, $_->format("%Y.%m.%d"));
421 0 0         if (!$modisGlobal{$product}) {
422 0           my $nHV = @{$self->{h}};
  0            
423 0           for (my $i=0; $i < $nHV; $i++) {
424 0           my $pat = sprintf("h%02iv%02i", ${$self->{h}}[$i], ${$self->{v}}[$i]);
  0            
  0            
425 0           my @newUrl;
426 0           foreach (@fullUrl) {
427 0 0         if (/$pat/) {
428 0           push(@newUrl, $_);
429             }
430             }
431 0           my $nNewUrl = @newUrl;
432 0 0         if ($nNewUrl == 1) {
    0          
433 0           push(@url, $newUrl[0]);
434             } elsif ($nNewUrl < 1) {
435 0           carp(sprintf("$product: Missing file for %s @ %s.\n", $pat, $_->format("%Y.%m.%d")));
436             } else {
437             # check for duplicate files here and choose the latest one
438 0           carp(sprintf("$product: %i files for %s @ %s, choosing the newest.\n", $nNewUrl, $pat, $_->format("%Y.%m.%d")));
439 0           my $createDate = $newUrl[0];
440 0           $createDate =~ s/\.hdf$//;
441 0           $createDate =~ s/^.*\.//g;
442 0           $createDate = int($createDate);
443 0           my $newest = 0;
444 0           for (my $k=0; $k < $nNewUrl; $k++) {
445 0           s/\.hdf$//;
446 0           s/^.*\.//g;
447 0 0         if (int($_) > $createDate) {
448 0           $newest = $k;
449 0           $createDate = int($_);
450             }
451             }
452 0           push(@url, $newUrl[$newest]);
453             }
454             }
455             } else {
456 0           my $nUrl = @fullUrl;
457 0 0         if ($nUrl == 1) {
    0          
458 0           push(@url, $fullUrl[0]);
459             } elsif ($nUrl < 1) {
460 0           carp(sprintf("$product: Missing file @ %s.\n", $_->format("%Y.%m.%d")));
461             } else {
462             # check for duplicate files here and choose the latest one
463 0           warn(sprintf("$product: %i files @ %s, choosing the newest.\n", $nUrl, $_->format("%Y.%m.%d")));
464 0           my $createDate = $fullUrl[0];
465 0           $createDate =~ s/\.hdf$//;
466 0           $createDate =~ s/^.*\.//g;
467 0           $createDate = int($createDate);
468 0           my $newest = 0;
469 0           for (my $k=0; $k < $nUrl; $k++) {
470 0           s/\.hdf$//;
471 0           s/^.*\.//g;
472 0 0         if (int($_) > $createDate) {
473 0           $newest = $k;
474 0           $createDate = int($_);
475             }
476             }
477 0           push(@url, $fullUrl[$newest]);
478             }
479             }
480             }
481 0           $self->{url} = \@url;
482             }
483              
484             ########################################################################
485             ### method for download
486              
487             sub download {
488 0     0 1   my $self = shift;
489 0           my $arg = shift;
490 0 0         $self->{targetDir} = $arg if ($arg);
491 0           $arg = shift;
492 0 0         $self->{forceReload} = $arg if ($arg);
493              
494 0           my $nUrl = @{$self->{url}};
  0            
495              
496 0 0         $self->createUrl if ($nUrl == 0);
497 0           $nUrl = @{$self->{url}};
  0            
498              
499 0 0         if (! -d $self->{targetDir}) {
500 0           my $failed = 1;
501 0 0         make_path($self->targetDir) and $failed = 0;
502 0 0         if ($failed) {
503 0           croak "Cannot create directory '$self->{targetDir}': $!\n";
504             }
505             }
506              
507             # adjusted from http://stackoverflow.com/questions/6813726/continue-getting-a-partially-downloaded-file
508 0           my $ua = LWP::UserAgent->new();
509              
510 0           for (my $i=0; $i < $nUrl; $i++) {
511 0           my $file = $self->{targetDir}."/".basename(@{$self->{url}}[$i]);
  0            
512 0 0 0       unlink($file) if ($self->{forceReload} && -f $file);
513 0           my $failed = 1;
514 0 0         open(my $fh, '>>:raw', $file) and $failed = 0;
515 0 0         if ($failed) {
516 0           croak "Cannot open '$file': $!\n";
517             }
518 0           my $bytes = -s $file;
519 0           my $res;
520 0 0 0       if ( $bytes && ! $self->{forceReload}) {
521             #print "resume ${$self->{url}}[$i] -> $file ($bytes) " if ($verbose);
522             $res = $ua->get(
523 0           ${$self->{url}}[$i],
524             'Range' => "bytes=$bytes-",
525 0     0     ':content_cb' => sub { my ( $chunk ) = @_; print $fh $chunk; }
  0            
526 0           );
527             } else {
528             #print "$URL[$i] -> $destination[$i] " if ($verbose);
529             $res = $ua->get(
530 0           ${$self->{url}}[$i],
531 0     0     ':content_cb' => sub { my ( $chunk ) = @_; print $fh $chunk; }
  0            
532 0           );
533             }
534 0           close $fh;
535              
536 0           my $status = $res->status_line;
537 0 0         if ( $status =~ /^(200|206|416)/ ) {
538             #print "OK\n" if ($verbose && $status =~ /^20[06]/);
539             #print "already complete\n" if ($verbose && $status =~ /^416/);
540             } else {
541 0           print "DEBUG: $status what happend?";
542             }
543             }
544             }
545              
546             ###################################################
547             ###################################################
548             ### Internal functions
549              
550             ### retrieve a list of available MODIS Products
551             ### and return a hash with the name of the first subdirectory
552             sub getAvailProducts () {
553 0     0 0   my $caller = (caller)[0];
554 0 0         carp "This is an internal WebService::MODIS function. You should know what you are doing." if ($caller ne "WebService::MODIS");
555              
556 0           my %lookupTable = ();
557 0           my $ua = new LWP::UserAgent;
558 0           foreach my $subdir (@DATA_DIR) {
559 0           my $response = $ua->get("${BASE_URL}/${subdir}");
560              
561 0 0         unless ($response->is_success) {
562 0           die $response->status_line;
563             }
564              
565 0           my $content = $response->decoded_content();
566 0           my @content = split("\n", $content);
567 0           foreach (@content) {
568 0 0         next if (!/href="M/);
569 0           s/.*href="//;
570 0           s/\/.*//;
571              
572 0 0         print "Key already exists\n" if exists $lookupTable{$_};
573 0 0         print "Key already defined\n" if defined $lookupTable{$_};
574 0 0         print "True\n" if $lookupTable{$_};
575              
576 0           $lookupTable{$_} = $subdir;
577             }
578             }
579 0           return %lookupTable;
580             }
581              
582             ### get the available second level directories, named by date
583             ### (YYYY.MM.DD) under which the hdf files reside. This does
584             ### not ensure that the files are really there.
585             sub getAvailDates() {
586 0     0 0   my $caller = (caller)[0];
587 0 0         carp "This is an internal WebService::MODIS function. You should know what you are doing." if ($caller ne "WebService::MODIS");
588              
589 0           my %lookupTable = ();
590              
591 0           my $ua = new LWP::UserAgent;
592 0           foreach my $key (keys %modisProducts) {
593 0           my @dates=();
594 0           my $response = $ua->get("${BASE_URL}/$modisProducts{$key}/$key");
595              
596 0 0         unless ($response->is_success) {
597 0           die $response->status_line;
598             }
599              
600 0           my $content = $response->decoded_content();
601 0           my @content = split("\n", $content);
602 0           foreach (@content) {
603 0 0         next if (!/href="20[0-9]{2}\.[0-9]{2}\.[0-9]{2}/);
604 0           s/.*href="//;
605 0           s/\/.*//;
606 0           push(@dates, $_);
607             }
608 0           my $datesString = "['".join("', '", @dates)."']";
609 0           $lookupTable{$key} = eval $datesString;
610             }
611 0           return %lookupTable;
612             }
613              
614             ### return a file list for one product and date on the server
615             sub getDateFullURLs($$) {
616 0     0 0   my $caller = (caller)[0];
617 0 0         carp "This is an internal WebService::MODIS function. You should know what you are doing." if ($caller ne "WebService::MODIS");
618              
619 0           my $product = shift;
620 0           my $date = shift;
621              
622 0           my @flist = ();
623              
624 0           my $ua = new LWP::UserAgent;
625              
626 0           my $response = $ua->get("${BASE_URL}/$modisProducts{$product}/$product/$date");
627            
628 0 0         unless ($response->is_success) {
629 0           die $response->status_line;
630             }
631              
632 0           my $content = $response->decoded_content();
633 0           my @content = split("\n", $content);
634 0           foreach (@content) {
635 0 0         next if (!/href="M/);
636 0 0         next if (/hdf.xml/);
637 0           s/.*href="//;
638 0           s/".*//;
639 0           push(@flist, "${BASE_URL}/$modisProducts{$product}/$product/$date/$_");
640             }
641 0           return(@flist);
642             }
643              
644             1;
645              
646             __END__