Astro-SpaceTrack
view release on metacpan or search on metacpan
lib/Astro/SpaceTrack.pm view on Meta::CPAN
{
my %valid_format = map { $_ => 1 } qw{ TLE 3LE 2LE XML KVN JSON CSV };
sub _celestrak_validate_format {
my ( $self, $format ) = @_;
$format = defined $format ? uc( $format ) : 'TLE';
$valid_format{$format}
or return HTTP::Response->new(
HTTP_PRECONDITION_FAILED,
"Format '$format' is not valid" );
$format eq 'JSON'
and $self->getv( 'pretty' )
and $format = 'JSON-PRETTY';
return $format;
}
}
sub _celestrak_validate_query {
my ( undef, $query, $name, $valid, $dflt ) = @_;
$query = defined $query ? uc( $query ) :
$name =~ m/ \A [0-9]+ \z /smx ? 'CATNR' :
$name =~ m/ \A [0-9]{4}-[0-9]+ \z /smx ? 'INTDES' :
defined $dflt ? uc( $dflt ) : $dflt;
defined $query
or return $query;
$valid->{$query}
or return HTTP::Response->new(
HTTP_PRECONDITION_FAILED,
"Query '$query' is not valid" );
return $query;
}
sub _celestrak_repack_iridium {
my ( $resp ) = @_;
local $_ = $resp->content();
s/ \s+ [[] . []] [ \t]* (?= \r? \n | \z ) //smxg;
$resp->content( $_ );
return;
}
{ # Local symbol block.
my %valid_type = map { $_ => 1 }
qw{ text/plain text/text application/json application/xml };
sub _celestrak_response_check {
my ($self, $resp, $source, $name, @args) = @_;
# As of 2023-10-17, celestrak( 'fubar' ) gives 200 OK, with
# content
# Invalid query: "GROUP=fubar&FORMAT=TLE" (GROUP=fubar not found)
unless ( $resp->is_success() ) {
$resp->code == HTTP_NOT_FOUND
and return $self->_no_such_catalog(
$source => $name, @args);
return $resp;
}
my $content = $resp->decoded_content();
if ( $content =~ m/ \A Invalid \s+ query: /smx ) {
$content =~ m/ \b (?: GROUP | FILE ) =\Q$name\E \s not \s found \b /smx
and return $self->_no_such_catalog(
$source => $name, @args);
$resp->code( HTTP_BAD_REQUEST );
$resp->message( HTTP::Status::status_message(
HTTP_BAD_REQUEST ) );
return $resp;
}
if (my $loc = $resp->header('Content-Location')) {
if ($loc =~ m/ redirect [.] htm [?] ( \d{3} ) ; /smx) {
my $msg = "redirected $1";
@args and $msg = "@args; $msg";
$1 == HTTP_NOT_FOUND
and return $self->_no_such_catalog(
$source => $name, $msg);
return HTTP::Response->new (+$1, "$msg\n")
}
}
my $type = lc $resp->header('Content-Type')
or do {
my $msg = 'No Content-Type header found';
@args and $msg = "@args; $msg";
return $self->_no_such_catalog(
$source => $name, $msg);
};
foreach my $type ( _trim( split ',', $type ) ) {
$type =~ s/ ; .* //smx;
$valid_type{$type}
or next;
local $_ = $resp->decoded_content();
# As of February 12 2022 Celestrak does this
# As of July 23 2022 this is not at the beginning of the
# string
m/^No GP data found\b/sm
and last;
# As of July 25 2022 Celestrak does this.
m/^(?:GROUP|FILE) "[^"]+" does not exist/sm
and last;
return;
}
my $msg = "Content-Type: $type";
@args and $msg = "@args; $msg";
return $self->_no_such_catalog(
$source => $name, $msg);
}
} # End local symbol block.
=item $bool = $st->cache_hit( $resp );
This method takes the given HTTP::Response object and returns the cache
hit indicator specified by the 'Pragma: spacetrack-cache-hit =' header.
This will be true if the response came from cache, false if it did not,
and C<undef> if cache was not available.
If the response object is not provided, it returns the data type
from the last method call that returned an HTTP::Response object.
=cut
sub cache_hit {
$_[2] = 'spacetrack-cache-hit';
goto &_get_pragma_value;
}
=item $source = $st->content_source($resp);
This method takes the given HTTP::Response object and returns the data
source specified by the 'Pragma: spacetrack-source =' header. What
values you can expect depend on the content_type (see below) as follows:
If the C<content_type()> method returns C<'box_score'>, you can expect
a content-source value of C<'spacetrack'>.
If the content_type method returns C<'iridium-status'>, you can expect
content_source values of C<'kelso'>, C<'mccants'>, or C<'sladen'>,
corresponding to the main source of the data.
If the content_type method returns C<'molczan'>, you can expect a
content_source value of C<'mccants'>.
If the C<content_type()> method returns C<'orbit'>, you can expect
content-source values of C<'amsat'>, C<'celestrak'>, C<'mccants'>,
or C<'spacetrack'>, corresponding to the actual source
of the TLE data.
If the content_type method returns C<'quicksat'>, you can expect a
content_source value of C<'mccants'>.
If the C<content_type()> method returns C<'search'>, you can expect a
lib/Astro/SpaceTrack.pm view on Meta::CPAN
$resp->content( $encode{$format}->( $json, $data ) );
return $resp;
}
}
####
#
# Private methods.
#
# $self->_add_pragmata ($resp, $name => $value, ...);
#
# This method adds pragma headers to the given HTTP::Response
# object, of the form pragma => "$name = $value". The pragmata are
# also cached in $self.
#
# Pragmata names are normalized by converting them to lower case
# and converting underscores to dashes.
sub _add_pragmata {
my ($self, $resp, @args) = @_;
while (@args) {
my ( $name, $value ) = splice @args, 0, 2;
$name = lc $name;
$name =~ s/ _ /-/smxg;
$self->{_pragmata}{$name} = $value;
$resp->push_header(pragma => "$name = $value");
}
return;
}
{
my %format_map = qw{
3le tle
};
# $accumulator = _accumulator_for( $format, \%opt )
#
# This subroutine manufactires and returns an accumulator for the
# named format. The reference to the options hash is itself
# optional. The supported options are:
# file => true if the data contains a FILE key and the caller
# requests that a _file_of_record key be generated if
# possible and appropriate. Individual accumulators are at
# liberty to ignore this.
# pretty => true if the caller requests that the returned data be
# nicely formatted. This normally comes from the 'pretty'
# attribute. Individual accumulators are at liberty to
# ignore this.
#
# The return is a code reference. This reference is intended to be
# called as
# $accumulator->( $self, $resp )
# for each successful HTTP response. After all responses have been
# processed, the accumulated data are retrieved using
# ( $content, $data ) = $accumulator( $self )
# The first return is the text representation of the accumulated
# data. The second is the decoded data, and is returned at the
# accumulator's option. In scalar context only $content is returned.
sub _accumulator_for {
my ( $format, $opt ) = @_;
my $name = $format_map{$format} || $format;
my $accumulator = __PACKAGE__->can( "_accumulate_${name}_data" )
|| \&_accumulate_unknown_data;
my $returner = __PACKAGE__->can( "_accumulate_${name}_return" )
|| sub {
my ( undef, $context ) = @_;
return $context->{data};
};
my $context = {
format => $format,
opt => $opt || {},
};
return sub {
my ( $self, $resp ) = @_;
defined $resp
or return $returner->( $self, $context );
my $content = $resp->content();
defined $content
and $content ne ''
or return;
my $data = $accumulator->( $self, $content, $context );
$context->{opt}{file}
and $data
and _accumulate_file_of_record( $self, $context, $data );
return;
}
}
}
sub _accumulate_file_of_record {
my ( undef, $context, $data ) = @_; # Invocant unused
if ( defined $context->{file} ) {
foreach my $datum ( @{ $data } ) {
defined $datum->{FILE}
and $datum->{FILE} > $context->{file}
and $datum->{_file_of_record} = $context->{file};
}
} else {
$context->{file} = List::Util::max( -1,
map { $_->{FILE} }
grep { defined $_->{FILE} }
@{ $data }
);
}
return;
}
# The data accumulators. The conventions which must be followed are
# that, given a format named 'fmt':
#
# 1) There MUST be an accumulator named _accumulate_fmt_data(). Its
# arguments are the invocant, the content of the return, and the
# context hash. It must accumulate data in $context->{data}, in any
# format it likes.
# 2) If _accumulate_fmt_data() decodes the data, it SHOULD return a
# reference to the decoded array. Otherwise it MUST return nothing.
# 3) There MAY be a returner named _accumulate_fmt_return(). If it
# exists its arguments are the invocant and the context hash. It MUST
# return a valid representation of the accumulated data in the
# desired format.
# 4) If _accumulate_fmt_return() does not exist, the return will be the
# contents of $context->{data}, which MUST have been maintained by
# _accumulate_fmt_data() as a valid representation of the data in the
# desired format.
# 5) Note that if _accumulate_fmt_return() exists,
# _accumulate_fmt_data need not maintain $context->{data} as a valid
# representation of the accumulated data.
# Accessed via __PACKAGE__->can( "accumulate_${name}_data" ) in
# _accumulator_for(), above
sub _accumulate_csv_data { ## no critic (ProhibitUnusedPrivateSubroutines)
my ( undef, $content, $context ) = @_; # Invocant unused
if ( defined $context->{data} ) {
$context->{data} =~ s{ (?<! \n ) \z }{\n}smx;
$content =~ s{ .* \n }{}smx;
$context->{data} .= $content;
} else {
$context->{data} = $content;
}
return;
}
# Accessed via __PACKAGE__->can( "accumulate_${name}_data" ) in
# _accumulator_for(), above
sub _accumulate_html_data { ## no critic (ProhibitUnusedPrivateSubroutines)
my ( undef, $content, $context ) = @_; # Invocant unused
if ( defined $context->{data} ) {
$context->{data} =~ s{ \s* </tbody> \s* </table> \s* \z }{}smx;
$content =~ s{ .* <tbody> \s* }{}smx;
$context->{data} .= $content;
} else {
$context->{data} = $content;
}
return;
}
# Accessed via __PACKAGE__->can( "accumulate_${name}_data" ) in
# _accumulator_for(), above
sub _accumulate_json_data { ## no critic (ProhibitUnusedPrivateSubroutines)
my ( $self, $content, $context ) = @_;
my $json = $context->{json} ||= $self->_get_json_object(
pretty => $context->{opt}{pretty},
);
my $data = $json->decode( $content );
ARRAY_REF eq ref $data
or $data = [ $data ];
@{ $data }
or return;
if ( $context->{data} ) {
push @{ $context->{data} }, @{ $data };
} else {
( run in 0.867 second using v1.01-cache-2.11-cpan-39bf76dae61 )