File Coverage

blib/lib/WWW/BF2S.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package WWW::BF2S;
2              
3             our $VERSION = '0.03';
4              
5 1     1   27064 use XML::Simple;
  0            
  0            
6             use Data::Dumper;
7             use LWP::Simple;
8              
9             sub new {
10             my $class = shift;
11             my %options = @_;
12             my $self = {};
13             bless($self, $class); # class-ify it.
14              
15             $self->{debugLog} = $options{DebugLog} || 'debug.log';
16             $self->{debugLevel} = $options{DebugLevel} || 0;
17             $self->{storeFile} = $options{StoreFile} || 'stats.xml';
18              
19             eval { $self->{store} = XMLin($self->{storeFile}); }; # read in store XML data (it's okay if it fails/doesn't exist, I think)
20              
21             $self->__debug(7, 'Object Attributes:', Dumper($self));
22              
23             return $self;
24             }
25              
26             sub __debug {
27             my $self = shift || return undef;
28             return undef unless $self->{debugLog}; # skip unless log file is defined
29             my $level = int(shift);
30             return undef unless $self->{debugLevel} >= $level; # skip unless log level is as high as this item
31             if (open(BF2SDEBUG, ">>$self->{debugLog}")) {
32             my $time = localtime();
33             foreach my $group (@_) { # roll through many items if they are passed in as an array
34             foreach my $line (split(/\r?\n/, $group)) { # roll through items that are multiline, converting to multiple separate lines
35             print BF2SDEBUG "[$time] $line\n";
36             }
37             }
38             close(BF2SDEBUG);
39             }
40             return undef;
41             }
42              
43             sub __fetchData {
44             my $self = shift || return undef;
45              
46             my $urlBase = 'http://bf2s.com/xml.php?pids=';
47             my $pidList = {};
48             foreach my $pid (@_) {
49             next if ($pid =~ m|[^0-9]|); # check for validity
50             next if ($self->{store}->{'pid'.$pid}->{updated} + 7200 > time()); # make sure the cached copy is old enough (2 hours)
51             $pidList->{$pid}++; # add it to the queue
52             }
53             $self->__debug(6, 'PIDS REQUESTED:', keys(%{$pidList}));
54              
55             my @candidates;
56             # TODO: make a list of candidates from the data store (even ones we're not asking for) in order of best to worst
57              
58             while (scalar(keys(%{$pidList})) < 64) { # if the request list is shorter than 64 pids (the max per request), we should add more from the cache that need refreshed instead of wasting the opportunity
59             my $candidate = shift(@candidates) || last; # get the next candidate from the list (or exit the loop because we've run out of candidates)
60             next if ($pidList->{$candidate}); # if it's already in the list, skip it
61             $pidList->{$candidate}++; # seems okay, add it to the pidList
62             }
63             $self->__debug(6, 'PIDS WITH AUTO:', keys(%{$pidList}));
64             my $pids = join(',', sort(keys(%{$pidList}))); # join the queue in a proper format
65              
66             return $response unless $pids; # only proceed if there is something to fetch
67              
68             my $response = get($urlBase.$pids); # fetch the data from the source (bf2s feed)
69             #use IO::All; my $response; $response < io('test.xml'); # for testing only (an XML file that has a sample of raw returned data from the feed source)
70             return undef unless $response; # if it failed, don't continue
71              
72             my $parsedResponse = XMLin($response); # parse the XML into a hashref
73             $self->__debug(7, 'PARSEDRESPONSE:', Dumper($parsedResponse));
74              
75             $parsedResponse->{player} = $self->__forceArray($parsedResponse->{player});
76              
77             my $stats = {};
78             foreach my $player (@{$parsedResponse->{player}}) { # store in a normalized structure
79             next unless ($pidList->{$player->{pid}}); # probably not necessary, but don't parse things we didn't ask for
80             $player->{updated} = time();
81             $stats->{$player->{pid}} = $player;
82             }
83             $self->__debug(7, 'NORMALIZEDRESPONSE:', Dumper($stats));
84             $self->__injectIntoDataStore($stats);
85              
86             return $stats; # return the response content
87             }
88              
89             sub __forceArray {
90             my $self = shift;
91             my $input = shift;
92             return $input if (ref($input) eq 'ARRAY'); # return if already an arrayref
93             my $output;
94             $output->[0] = $input; # force it to be an item in an arrayref
95             return $output; # return the arrayref
96             }
97              
98             sub __injectIntoDataStore {
99             my $self = shift;
100             my $stats = shift;
101              
102             foreach my $pid (keys(%{$stats})) {
103             next if ($pid =~ m|[^0-9]|); # ensure only numerical pids (is this necessary?)
104             $self->{store}->{'pid'.$pid} = $stats->{$pid}; # insert/replace into data store
105             }
106              
107             my $storeOut = XMLout($self->{store}); # convert hashref data into XML structure
108             if ($storeOut) { # only if storeOut is valid/existing (wouldn't want to wipe out our only cache/store with null)
109             if (open(STOREFH, '>'.$self->{storeFile})) { # overwrite old store file with new store file
110             print STOREFH $storeOut;
111             close(STOREFH);
112             }
113             }
114              
115             return undef;
116             }
117              
118             sub getStats {
119             my $self = shift;
120             my @pids = @_;
121             my $stats = {};
122              
123             $self->__fetchData(@pids); # get fresh data when if necessary
124              
125             foreach my $pid (@pids) { # prep the requested data for return
126             $stats->{$pid} = $self->{store}->{'pid'.$pid};
127             }
128              
129             return $stats; # return the requested data
130             }
131              
132             1;
133              
134             __END__