File Coverage

blib/lib/Net/Twitter/Loader.pm
Criterion Covered Total %
statement 105 126 83.3
branch 34 52 65.3
condition 2 3 66.6
subroutine 25 29 86.2
pod 9 10 90.0
total 175 220 79.5


line stmt bran cond sub pod time code
1             package Net::Twitter::Loader;
2 4     4   161190 use strict;
  4         11  
  4         147  
3 4     4   26 use warnings;
  4         6  
  4         132  
4 4     4   4803 use JSON qw(decode_json encode_json);
  4         72483  
  4         26  
5 4     4   4173 use Try::Tiny;
  4         4771  
  4         249  
6 4     4   24 use Carp;
  4         8  
  4         220  
7 4     4   3746 use Time::HiRes qw(sleep);
  4         7899  
  4         23  
8              
9             our $VERSION = "0.04";
10              
11             our @CARP_NOT = qw(Try::Tiny Net::Twitter Net::Twitter::Lite Net::Twitter::Lite::WithAPIv1_1);
12              
13             sub new {
14 7     7 1 65955 my ($class, %params) = @_;
15 7         31 my $self = bless {}, $class;
16 7 50       30 croak "backend parameter is mandatory" if not defined $params{backend};
17 7         35 $self->{backend} = $params{backend};
18 7         32 $self->_set_param(\%params, 'filepath', undef);
19 7         19 $self->_set_param(\%params, 'page_max', 10);
20 7         19 $self->_set_param(\%params, 'page_max_no_since_id', 1);
21 7         18 $self->_set_param(\%params, 'page_next_delay', 0);
22 7         23 $self->_set_param(\%params, "logger", undef);
23 7         26 return $self;
24             }
25              
26 2     2 1 29 sub backend { $_[0]->{backend} }
27              
28             sub _set_param {
29 35     35   59 my ($self, $params_ref, $key, $default) = @_;
30 35 100       104 $self->{$key} = defined($params_ref->{$key}) ? $params_ref->{$key} : $default;
31             }
32              
33             sub _load_next_since_id_file {
34 29     29   43 my ($self) = @_;
35 29 50       145 return {} if not defined($self->{filepath});
36 0 0       0 open my $file, "<", $self->{filepath} or return undef;
37 0         0 my $json_text = do { local $/ = undef; <$file> };
  0         0  
  0         0  
38 0         0 close $file;
39             my $since_ids = try {
40 0     0   0 decode_json($json_text);
41             }catch {
42 0     0   0 my $e = shift;
43 0         0 $self->_log("warn", "failed to decode_json");
44 0         0 return {};
45 0         0 };
46 0 0       0 $since_ids = {} if not defined $since_ids;
47 0         0 return $since_ids;
48             }
49              
50             sub _log {
51 53     53   82 my ($self, $level, $msg) = @_;
52 53 100       166 $self->{logger}->($level, $msg) if defined $self->{logger};
53             }
54              
55             sub _save_next_since_id_file {
56 14     14   16 my ($self, $since_ids) = @_;
57 14 50       42 return if not defined($self->{filepath});
58 0 0       0 open my $file, ">", $self->{filepath} or die "Cannot open $self->{filepath} for write: $!";
59             try {
60 0     0   0 print $file encode_json($since_ids);
61             }catch {
62 0     0   0 my $e = shift;
63 0         0 $self->_log("error", $e);
64 0         0 };
65 0         0 close $file;
66             }
67              
68             sub _log_query {
69 51     51   99 my ($self, $method, $params) = @_;
70 143 50       679 $self->_log("debug", sprintf(
71             "%s: method: %s, args: %s", __PACKAGE__, $method,
72 51         145 join(", ", map {"$_: " . (defined($params->{$_}) ? $params->{$_} : "[undef]")} keys %$params)
73             ));
74             }
75              
76             sub _normalize_search_result {
77 50     50   4690 my ($self, $nt_result) = @_;
78 50 50       159 if(!ref($nt_result)) {
    100          
    50          
79 0         0 confess "Scalar is returned by the backend. Something is wrong.";
80             }elsif(ref($nt_result) eq 'ARRAY') {
81 47         121 return $nt_result;
82             }elsif(ref($nt_result) eq 'HASH') {
83 3 50       11 if(ref($nt_result->{statuses}) eq 'ARRAY') { ## REST API v1.1
    50          
84 0         0 return $nt_result->{statuses};
85             }elsif(ref($nt_result->{results}) eq 'ARRAY') { ## REST API v1.0
86 3         9 return $nt_result->{results};
87             }
88             }
89 0         0 confess "Unknown type of data returned by the backend. Something is wrong.";
90             }
91              
92             sub _load_timeline {
93 15     15   36 my ($self, $nt_params, $method, @label_params) = @_;
94 15 100       74 my %params = defined($nt_params) ? %$nt_params : ();
95 15 50       60 if(not defined $method) {
96 15         83 $method = (caller(1))[3];
97 15         93 $method =~ s/^.*:://g;
98             }
99 34 100       116 my $label = "$method," .
100 15         37 join(",", map { "$_:" . (defined($params{$_}) ? $params{$_} : "") } @label_params);
101 15         51 my $since_ids = $self->_load_next_since_id_file();
102 15         29 my $since_id = $since_ids->{$label};
103 15 50 66     68 $params{since_id} = $since_id if !defined($params{since_id}) && defined($since_id);
104 15 100       46 my $page_max = defined($params{since_id}) ? $self->{page_max} : $self->{page_max_no_since_id};
105 15 100       33 if($method eq 'public_timeline') {
106 1         2 $page_max = 1;
107             }
108 15         20 my $max_id = undef;
109 15         19 my @result = ();
110 15         19 my $load_count = 0;
111 15         20 my %loaded_ids = ();
112 15         23 my $next_since_id;
113 15         39 while($load_count < $page_max) {
114 51 100       140 $params{max_id} = $max_id if defined $max_id;
115 51         172 $self->_log_query($method, \%params);
116 51         140 my $loaded;
117             try {
118 51     51   1668 $loaded = $self->_normalize_search_result($self->{backend}->$method({%params}));
119             }catch {
120 1     1   65 my $e = shift;
121 1         3 $self->_log("error", $e);
122 1         12 die $e;
123 51         387 };
124 50 50       696 return undef if not defined $loaded;
125 50         81 @$loaded = grep { !$loaded_ids{$_->{id}} } @$loaded;
  520         1001  
126 50 100       121 last if !@$loaded;
127 41         503 $loaded_ids{$_->{id}} = 1 foreach @$loaded;
128 41         86 $max_id = $loaded->[-1]{id};
129 41 100       84 $next_since_id = $loaded->[0]{id} if not defined $next_since_id;
130 41         135 push(@result, @$loaded);
131 41         44 $load_count++;
132 41         4320 sleep($self->{page_next_delay});
133             }
134 14 100       41 if($load_count == $self->{page_max}) {
135 1         5 $self->_log("notice", "page has reached the max value of " . $self->{page_max});
136             }
137 14 50       40 if(defined($next_since_id)) {
138 14         29 $since_ids = $self->_load_next_since_id_file();
139 14         43 $since_ids->{$label} = $next_since_id;
140 14         36 $self->_save_next_since_id_file($since_ids);
141             }
142 14         210 return \@result;
143             }
144              
145             sub user_timeline {
146 8     8 1 43751 my ($self, $nt_params) = @_;
147 8         31 return $self->_load_timeline($nt_params, undef, qw(id user_id screen_name));
148             }
149              
150             sub public_timeline {
151 1     1 0 16 my ($self, $nt_params) = @_;
152 1         4 return $self->_load_timeline($nt_params);
153             }
154              
155             sub home_timeline {
156 1     1 1 218 my ($self, $nt_params) = @_;
157 1         4 return $self->_load_timeline($nt_params);
158             }
159              
160             sub list_statuses {
161 1     1 1 8904 my ($self, $nt_params) = @_;
162 1         5 return $self->_load_timeline($nt_params, undef, qw(list_id slug owner_screen_name owner_id));
163             }
164              
165             sub search {
166 1     1 1 9437 my ($self, $nt_params) = @_;
167 1         6 return $self->_load_timeline($nt_params, undef, qw(q lang locale));
168             }
169              
170             sub favorites {
171 1     1 1 8780 my ($self, $nt_params) = @_;
172 1         5 return $self->_load_timeline($nt_params, undef, qw(id user_id screen_name))
173             }
174              
175             sub mentions {
176 1     1 1 8699 my ($self, $nt_params) = @_;
177 1         4 return $self->_load_timeline($nt_params);
178             }
179              
180             sub retweets_of_me {
181 1     1 1 8593 my ($self, $nt_params) = @_;
182 1         3 return $self->_load_timeline($nt_params);
183             }
184              
185              
186             1;
187             __END__