File Coverage

blib/lib/App/Chart/DownloadHandler.pm
Criterion Covered Total %
statement 33 35 94.2
branch n/a
condition n/a
subroutine 12 12 100.0
pod n/a
total 45 47 95.7


line stmt bran cond sub pod time code
1             # Download data handlers.
2              
3             # Copyright 2007, 2008, 2009, 2010, 2011, 2014, 2016 Kevin Ryde
4              
5             # This file is part of Chart.
6             #
7             # Chart is free software; you can redistribute it and/or modify it under the
8             # terms of the GNU General Public License as published by the Free Software
9             # Foundation; either version 3, or (at your option) any later version.
10             #
11             # Chart is distributed in the hope that it will be useful, but WITHOUT ANY
12             # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13             # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14             # details.
15             #
16             # You should have received a copy of the GNU General Public License along
17             # with Chart. If not, see <http://www.gnu.org/licenses/>.
18              
19             package App::Chart::DownloadHandler;
20 1     1   419 use 5.010;
  1         3  
21 1     1   4 use strict;
  1         2  
  1         18  
22 1     1   5 use warnings;
  1         1  
  1         26  
23 1     1   378 use sort 'stable'; # lexical in 5.10
  1         408  
  1         6  
24 1     1   33 use Carp;
  1         2  
  1         46  
25 1     1   338 use Encode;
  1         13092  
  1         78  
26 1     1   296 use Encode::Locale; # for coding system "locale"
  1         2797  
  1         52  
27 1     1   7 use List::Util qw(min max);
  1         3  
  1         118  
28 1     1   435 use List::MoreUtils;
  1         9807  
  1         6  
29 1     1   1200 use POSIX::Wide;
  1         7646  
  1         48  
30 1     1   368 use Locale::TextDomain ('App-Chart');
  1         5987  
  1         6  
31              
32 1     1   5725 use App::Chart;
  0            
  0            
33             use App::Chart::Database;
34              
35             use constant DEBUG => 0;
36              
37              
38             #------------------------------------------------------------------------------
39              
40             our @handler_list = ();
41              
42             sub new {
43             my ($class, %self) = @_;
44             $self{'pred'} or croak __PACKAGE__,": missing pred";
45             $self{'proc'} or croak __PACKAGE__,": missing proc";
46             App::Chart::Sympred::validate ($self{'pred'});
47              
48             my $self = bless \%self, $class;
49             push @handler_list, $self;
50              
51             $self{'name'} ||= do { my ($package,$filename,$line) = caller();
52             "$package:"
53             . Glib::filename_to_unicode($filename)
54             . ":$line" };
55              
56             # highest priority first and 'stable' above for order added for equals
57             @handler_list
58             = sort { ($b->{'priority'}||0) <=> ($a->{'priority'}||0) }
59             @handler_list;
60              
61             return $self;
62             }
63              
64             sub name {
65             my ($self) = @_;
66             return $self->{'name'};
67             }
68              
69             #------------------------------------------------------------------------------
70              
71             sub handlers_for_symbol {
72             my ($class, $symbol) = @_;
73             App::Chart::symbol_setups ($symbol);
74             if (DEBUG) { print "total ", scalar(@handler_list), " handlers\n"; }
75             return grep { $_->match($symbol) } @handler_list;
76             }
77              
78             #------------------------------------------------------------------------------
79              
80             sub match {
81             my ($self, $symbol) = @_;
82             return $self->{'pred'}->match($symbol);
83             }
84              
85             #------------------------------------------------------------------------------
86              
87             sub download {
88             my ($self, $symbol_list) = @_;
89             if (DEBUG) { print "Download ",@$symbol_list,"\n"; }
90             if (! @$symbol_list) { return; }
91              
92             if (my $key = $self->{'recheck_key'}) {
93             my $recheck_seconds = 86400 * $self->{'recheck_days'};
94             my ($timestamp_lo, $timestamp_hi)
95             = App::Chart::Download::timestamp_range ($recheck_seconds);
96              
97             my $min_timestamp = '9999';
98             my $any_wanted = 0;
99             foreach my $symbol (@$symbol_list) {
100             my $symbol_timestamp = App::Chart::Database->read_extra ($symbol, $key);
101             if (defined $symbol_timestamp
102             && $symbol_timestamp ge $timestamp_lo
103             && $symbol_timestamp le $timestamp_hi) {
104             if (DEBUG) { print "recheck not for $symbol: $symbol_timestamp\n"; }
105             $min_timestamp = List::Util::minstr($min_timestamp, $symbol_timestamp);
106             } else {
107             if (DEBUG) { print "recheck want $symbol: $symbol_timestamp\n"; }
108             $any_wanted = 1;
109             last;
110             }
111             }
112              
113             if (! $any_wanted) {
114             my $t = App::Chart::Download::timestamp_to_timet ($min_timestamp)
115             + $recheck_seconds;
116             my $fmt = $App::Chart::option{'d_fmt'} . ' %H:%M';
117             my $datetime = POSIX::Wide::strftime ($fmt, localtime($t));
118             App::Chart::Download::verbose_message
119             (__x("{name}: next check {datetime}",
120             name => $self->name,
121             datetime => $datetime));
122             return;
123             }
124             }
125              
126             App::Chart::Download::verbose_message ($self->name, @$symbol_list);
127             App::Chart::Download::status ($self->name);
128              
129             my $tsproc = $self->{'available_tdate_by_symbol'};
130             {
131             my $avail;
132             if (my $tproc = $self->{'available_tdate'}) {
133             $avail = $tproc->();
134             }
135             if (my $tproc = $self->{'available_date_time'}) {
136             my ($iso_date, $time) = $tproc->();
137             $avail = App::Chart::Download::iso_to_tdate_floor ($iso_date);
138             }
139             if (defined $avail) {
140             $tsproc = sub { return $avail };
141             }
142             }
143             if ($tsproc) {
144             my @new;
145             foreach my $symbol (@$symbol_list) {
146             my $avail = $tsproc->($symbol);
147             my $start = App::Chart::Download::start_tdate_for_update ($symbol);
148             if ($avail >= $start) {
149             push @new, $symbol;
150             } else {
151             App::Chart::Download::verbose_message
152             (__x('{symbol} already got data to {date}',
153             symbol => $symbol,
154             date => App::Chart::Download::tdate_range_string ($avail)));
155             }
156             }
157             $symbol_list = \@new;
158             }
159             if (! @$symbol_list) { return; }
160              
161             my $proc = $self->{'proc'};
162             my @symbol_groups;
163              
164             if ($self->{'by_commodity'}) {
165             require Tie::IxHash;
166             my %byc;
167             tie %byc, 'Tie::IxHash';
168             foreach my $symbol (@$symbol_list) {
169             my $commodity = App::Chart::symbol_commodity ($symbol);
170             push @{$byc{$commodity}}, $symbol;
171             }
172             @symbol_groups = [ values %byc ];
173              
174             } elsif (my $max_symbols = $self->{'max_symbols'}) {
175             my $i = 0;
176             @symbol_groups = List::MoreUtils::part
177             {int(($i++) / $max_symbols)} @$symbol_list;
178              
179             } else {
180             @symbol_groups = ($symbol_list);
181             }
182              
183             if (! eval {
184             foreach my $symbol_group (@symbol_groups) {
185             if ($self->{'proc_with_self'}) {
186             $proc->($self, $symbol_group);
187             } else {
188             $proc->($symbol_group);
189             }
190             }
191             1;
192             }) {
193             my $err = $@;
194             unless (utf8::is_utf8($err)) { $err = Encode::decode('locale',$err); }
195             $err = App::Chart::collapse_whitespace ($err);
196             App::Chart::Download::download_message ("Download error: $err\n");
197             return 0;
198             }
199             return 1;
200             }
201              
202             sub available_tdate_for_symbol {
203             my ($self, $symbol) = @_;
204              
205             if (my $tsproc = $self->{'available_tdate_by_symbol'}) {
206             return $tsproc->($symbol);
207              
208             } elsif (my $tproc = $self->{'available_tdate'}) {
209             return $tproc->();
210              
211             } else {
212             return undef;
213             }
214             }
215              
216             1;
217             __END__
218              
219             # =for stopwords Eg tdate upto
220             #
221             # =head1 NAME
222             #
223             # App::Chart::DownloadHandler -- database download handler objects
224             #
225             # =head1 SYNOPSIS
226             #
227             # use App::Chart::DownloadHandler;
228             #
229             # =head1 FUNCTIONS
230             #
231             # =over 4
232             #
233             # =item C<< App::Chart::DownloadHandler->new (key=>value,...) >>
234             #
235             # Create and register a new data download handler. The return is a
236             # C<App::Chart::DownloadHandler> object, though usually this is not of interest
237             # (only all the handlers later with C<handlers_for_symbol> below). Eg.
238             #
239             # my $pred = App::Chart::Sympred::Suffix->new ('.NZ');
240             #
241             # sub my_download {
242             # my ($symbol_list_ref) = @_;
243             # ...
244             # }
245             #
246             # App::Chart::DownloadHandler->new
247             # (pred => $pred,
248             # proc => \&my_download);
249             #
250             # The mandatory keys are
251             #
252             # pred App::Chart::Sympred
253             # proc subroutine to call
254             #
255             # The optional keys are
256             #
257             # available_tdate subroutine returning data tdate
258             # available_tdate_by_symbol subroutine, taking a symbol
259             #
260             # C<available_tdate> returns the tdate of the newest data available from this
261             # source. C<available_tdate_by_symbol> similarly, but it's passed the stock
262             # symbol; this can be used if availability is symbol-dependent. In either
263             # case the download procedures check what's available against where the
264             # prospective symbols are already upto, and don't call C<proc> at all unless
265             # there should be new data.
266             #
267             # =item C<< App::Chart::DownloadHandler->handlers_for_symbol ($symbol) >>
268             #
269             # Return a list of C<App::Chart::DownloadHandler> objects which are the available
270             # latest quote download handlers for C<$symbol>. This is an empty list if
271             # there's nothing available for C<$symbol>.
272             #
273             # =item C<< $handler->match ($symbol) >>
274             #
275             # Return true if C<$handler> is for use on C<$symbol>.
276             #
277             # =item C<< $handler->download ($symbol_list) >>
278             #
279             # Run C<$handler> on the symbols in C<$symbol_list> (an array reference).
280             # Those symbols must have already been checked as being for use with
281             # C<$handler>.
282             #
283             # =back