line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mobile::UserAgentFactory;
|
2
|
1
|
|
|
1
|
|
12859
|
use strict;
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
89
|
|
3
|
1
|
|
|
1
|
|
2373
|
use Mobile::UserAgent;
|
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
59
|
|
4
|
1
|
|
|
1
|
|
14
|
use base qw(Class::Singleton);
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6052
|
|
5
|
|
|
|
|
|
|
our $VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ m/ (\d+) \. (\d+) /xg;
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
# Contructor called by Class::Singleton to initialize a new instance.
|
9
|
|
|
|
|
|
|
sub _new_instance {
|
10
|
0
|
|
|
0
|
|
|
my $proto = shift;
|
11
|
0
|
|
|
|
|
|
my $options = shift;
|
12
|
0
|
|
0
|
|
|
|
my $class = ref($proto) || $proto;
|
13
|
0
|
|
|
|
|
|
my %cache_options;
|
14
|
0
|
0
|
0
|
|
|
|
if (defined($options) && (ref($options) eq 'HASH')) {
|
15
|
0
|
0
|
|
|
|
|
if (defined($options->{'cache_expires_in'})) {
|
16
|
0
|
|
|
|
|
|
$cache_options{'expires_in'} = $options->{'cache_expires_in'};
|
17
|
|
|
|
|
|
|
}
|
18
|
0
|
0
|
|
|
|
|
if (defined($options->{'cache_purge_interval'})) {
|
19
|
0
|
|
|
|
|
|
$cache_options{'purge_interval'} = $options->{'cache_purge_interval'};
|
20
|
|
|
|
|
|
|
}
|
21
|
0
|
0
|
|
|
|
|
if (defined($options->{'cache_max_age'})) {
|
22
|
0
|
|
|
|
|
|
$cache_options{'max_age'} = $options->{'cache_max_age'};
|
23
|
|
|
|
|
|
|
}
|
24
|
0
|
0
|
|
|
|
|
if (defined($options->{'cache_max_objects'})) {
|
25
|
0
|
|
|
|
|
|
$cache_options{'max_objects'} = $options->{'cache_max_objects'};
|
26
|
|
|
|
|
|
|
}
|
27
|
|
|
|
|
|
|
}
|
28
|
0
|
|
|
|
|
|
my $self = {
|
29
|
|
|
|
|
|
|
'cache' => Mobile::UserAgentFactoryCache->new(\%cache_options), # internal class
|
30
|
|
|
|
|
|
|
};
|
31
|
0
|
|
|
|
|
|
bless $self,$class;
|
32
|
0
|
|
|
|
|
|
return $self;
|
33
|
|
|
|
|
|
|
}
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Uses the given useragent string to return a Mobile::UserAgent object if a match can be found.
|
38
|
|
|
|
|
|
|
sub getMobileUserAgent {
|
39
|
0
|
|
|
0
|
1
|
|
my $self = shift;
|
40
|
0
|
|
|
|
|
|
my $useragent;
|
41
|
0
|
|
|
|
|
|
my $debug = 0;
|
42
|
0
|
0
|
|
|
|
|
if (@_) {
|
43
|
0
|
0
|
|
|
|
|
if (ref($_[0]) eq '') {
|
|
|
0
|
|
|
|
|
|
44
|
0
|
|
|
|
|
|
$useragent = shift;
|
45
|
|
|
|
|
|
|
}
|
46
|
|
|
|
|
|
|
elsif (UNIVERSAL::isa(ref($_[0]), 'CGI')) {
|
47
|
0
|
|
|
|
|
|
my $q = shift;
|
48
|
0
|
|
|
|
|
|
$useragent = $q->user_agent();
|
49
|
|
|
|
|
|
|
}
|
50
|
|
|
|
|
|
|
}
|
51
|
0
|
0
|
0
|
|
|
|
if (@_ && (ref($_[0]) eq 'HASH')) {
|
52
|
0
|
|
|
|
|
|
my $options = shift;
|
53
|
0
|
0
|
|
|
|
|
if (defined($options->{'debug'})) {
|
54
|
0
|
|
|
|
|
|
$debug = $options->{'debug'};
|
55
|
|
|
|
|
|
|
}
|
56
|
|
|
|
|
|
|
}
|
57
|
0
|
0
|
|
|
|
|
unless(defined($useragent)) {
|
58
|
0
|
|
|
|
|
|
$useragent = $ENV{'HTTP_USER_AGENT'};
|
59
|
0
|
0
|
|
|
|
|
unless(defined($useragent)) {
|
60
|
0
|
0
|
|
|
|
|
$debug && print("Returning undef, because no user-agent was found in env vars.\n");
|
61
|
0
|
|
|
|
|
|
return undef;
|
62
|
|
|
|
|
|
|
}
|
63
|
|
|
|
|
|
|
}
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# Try to fetch object from internal cache.
|
66
|
0
|
|
|
|
|
|
my $cache = $self->{'cache'};
|
67
|
0
|
|
|
|
|
|
my $mua = $cache->get($useragent);
|
68
|
0
|
0
|
0
|
|
|
|
if (defined($mua) || $cache->key_exists($useragent)) {
|
69
|
0
|
0
|
|
|
|
|
$debug && print("Returning Mobile::UserAgent object found in internal cache.\n");
|
70
|
0
|
|
|
|
|
|
return $mua;
|
71
|
|
|
|
|
|
|
}
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# Create new Mobile::UserAgent object, cache it, and return it.
|
74
|
0
|
|
|
|
|
|
$mua = Mobile::UserAgent->new($useragent);
|
75
|
0
|
|
|
|
|
|
$cache->set($useragent, $mua);
|
76
|
0
|
|
|
|
|
|
return $mua;
|
77
|
|
|
|
|
|
|
}
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
1;
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
#### end of Mobile::UserAgentFactory ####
|
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
# Internal cache manager class.
|
94
|
|
|
|
|
|
|
package Mobile::UserAgentFactoryCache;
|
95
|
1
|
|
|
1
|
|
1029
|
use strict;
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
948
|
|
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# Contructor. Accepts an optional hash ref of options.
|
99
|
|
|
|
|
|
|
sub new {
|
100
|
0
|
|
|
0
|
|
|
my $proto = shift;
|
101
|
0
|
|
|
|
|
|
my $options = shift;
|
102
|
0
|
|
0
|
|
|
|
my $class = ref($proto) || $proto;
|
103
|
0
|
|
|
|
|
|
my $expires_in = 86400; # 1 day
|
104
|
0
|
|
|
|
|
|
my $purge_interval = 3600; # 1 hour
|
105
|
0
|
|
|
|
|
|
my $max_age = 604800; # 1 week
|
106
|
0
|
|
|
|
|
|
my $max_objects = 1000;
|
107
|
0
|
0
|
0
|
|
|
|
if (defined($options) && (ref($options) eq 'HASH')) {
|
108
|
0
|
0
|
0
|
|
|
|
if (defined($options->{'expires_in'}) && $options->{'expires_in'}) {
|
109
|
0
|
|
|
|
|
|
$expires_in = $options->{'expires_in'};
|
110
|
|
|
|
|
|
|
}
|
111
|
0
|
0
|
0
|
|
|
|
if (defined($options->{'purge_interval'}) && $options->{'purge_interval'}) {
|
112
|
0
|
|
|
|
|
|
$purge_interval = $options->{'purge_interval'};
|
113
|
|
|
|
|
|
|
}
|
114
|
0
|
0
|
0
|
|
|
|
if (defined($options->{'max_age'}) && $options->{'max_age'}) {
|
115
|
0
|
|
|
|
|
|
$max_age = $options->{'max_age'};
|
116
|
|
|
|
|
|
|
}
|
117
|
0
|
0
|
0
|
|
|
|
if (defined($options->{'max_objects'}) && $options->{'max_objects'}) {
|
118
|
0
|
|
|
|
|
|
$max_objects = $options->{'max_objects'};
|
119
|
|
|
|
|
|
|
}
|
120
|
|
|
|
|
|
|
}
|
121
|
0
|
|
|
|
|
|
my $self = {
|
122
|
|
|
|
|
|
|
'objects' => {}, # Cache of key => [object, create-time, last-access-time]
|
123
|
|
|
|
|
|
|
'expires_in' => $expires_in,
|
124
|
|
|
|
|
|
|
'purge_interval' => $purge_interval,
|
125
|
|
|
|
|
|
|
'max_age' => $max_age,
|
126
|
|
|
|
|
|
|
'max_objects' => $max_objects,
|
127
|
|
|
|
|
|
|
'last_purge' => time,
|
128
|
|
|
|
|
|
|
'max_objects_check_interval' => int($max_objects / 10), # after this many set() calls, the limit_max_objects() call will be executed.
|
129
|
|
|
|
|
|
|
'max_objects_set_counter' => 0, # increases with each set() method call and is reset with with each limit_max_objects() call.
|
130
|
|
|
|
|
|
|
};
|
131
|
0
|
|
|
|
|
|
bless $self,$class;
|
132
|
0
|
|
|
|
|
|
return $self;
|
133
|
|
|
|
|
|
|
}
|
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# Checks if a key exists in the cache.
|
137
|
|
|
|
|
|
|
sub key_exists {
|
138
|
0
|
|
|
0
|
|
|
my $self = shift;
|
139
|
0
|
|
|
|
|
|
my $key = shift;
|
140
|
0
|
|
|
|
|
|
return exists($self->{'objects'}->{$key});
|
141
|
|
|
|
|
|
|
}
|
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# Gets a cached object.
|
145
|
|
|
|
|
|
|
sub get {
|
146
|
0
|
|
|
0
|
|
|
my $self = shift;
|
147
|
0
|
|
|
|
|
|
my $key = shift;
|
148
|
0
|
|
|
|
|
|
my $objects = $self->{'objects'};
|
149
|
0
|
|
|
|
|
|
my $result;
|
150
|
0
|
0
|
|
|
|
|
if (exists($objects->{$key})) {
|
151
|
0
|
|
|
|
|
|
my $object = $objects->{$key};
|
152
|
0
|
|
|
|
|
|
$result = $object->[0];
|
153
|
0
|
|
|
|
|
|
$object->[2] = time;
|
154
|
|
|
|
|
|
|
}
|
155
|
0
|
|
|
|
|
|
$self->_purge();
|
156
|
0
|
|
|
|
|
|
return $result;
|
157
|
|
|
|
|
|
|
}
|
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# Simply calls purge() if it's time to do so.
|
161
|
|
|
|
|
|
|
sub _purge {
|
162
|
0
|
|
|
0
|
|
|
my $self = shift;
|
163
|
0
|
0
|
|
|
|
|
if ($self->{'last_purge'} + $self->{'purge_interval'} <= time) {
|
164
|
0
|
|
|
|
|
|
return $self->purge();
|
165
|
|
|
|
|
|
|
}
|
166
|
0
|
|
|
|
|
|
return 0;
|
167
|
|
|
|
|
|
|
}
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# Purges all cached objects that have not been accessed recently or are too old.
|
171
|
|
|
|
|
|
|
sub purge {
|
172
|
0
|
|
|
0
|
|
|
my $self = shift;
|
173
|
0
|
|
|
|
|
|
my $objects = $self->{'objects'};
|
174
|
0
|
|
|
|
|
|
my $now = time;
|
175
|
0
|
|
|
|
|
|
my $max_age = $self->{'max_age'};
|
176
|
0
|
|
|
|
|
|
my $expires = $self->{'expires_in'};
|
177
|
0
|
|
|
|
|
|
my $result = 0;
|
178
|
0
|
|
|
|
|
|
foreach my $key (keys %{$objects}) {
|
|
0
|
|
|
|
|
|
|
179
|
0
|
|
|
|
|
|
my $object = $objects->{$key};
|
180
|
0
|
0
|
0
|
|
|
|
if (($object->[2] + $expires <= $now) || ($object->[1] + $max_age <= $now)) {
|
181
|
0
|
|
|
|
|
|
print "About to purge key: $key\n";
|
182
|
0
|
|
|
|
|
|
delete($objects->{$key});
|
183
|
0
|
|
|
|
|
|
$result++;
|
184
|
|
|
|
|
|
|
}
|
185
|
|
|
|
|
|
|
}
|
186
|
0
|
|
|
|
|
|
$self->{'last_purge'} = $now;
|
187
|
0
|
|
|
|
|
|
return $result;
|
188
|
|
|
|
|
|
|
}
|
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# Sets a new object.
|
192
|
|
|
|
|
|
|
sub set {
|
193
|
0
|
|
|
0
|
|
|
my $self = shift;
|
194
|
0
|
|
|
|
|
|
my $key = shift;
|
195
|
0
|
|
|
|
|
|
my $object = shift;
|
196
|
0
|
|
|
|
|
|
my $now = time;
|
197
|
0
|
|
|
|
|
|
$self->{'objects'}->{$key} = [$object, $now, $now];
|
198
|
0
|
0
|
|
|
|
|
if (++$self->{'max_objects_set_counter'} >= $self->{'max_objects_check_interval'}) {
|
199
|
0
|
|
|
|
|
|
return $self->limit_max_objects();
|
200
|
|
|
|
|
|
|
}
|
201
|
|
|
|
|
|
|
}
|
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# Shrinks the cache to 10% below max if max has been exceeded by 10%.
|
205
|
|
|
|
|
|
|
sub limit_max_objects {
|
206
|
0
|
|
|
0
|
|
|
my $self = shift;
|
207
|
0
|
|
|
|
|
|
$self->_purge();
|
208
|
0
|
|
|
|
|
|
my $objects = $self->{'objects'};
|
209
|
0
|
|
|
|
|
|
my $size = scalar(keys(%{$objects}));
|
|
0
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
|
my $max_objects = $self->{'max_objects'};
|
211
|
0
|
0
|
|
|
|
|
if ($size <= $max_objects) {
|
212
|
0
|
|
|
|
|
|
return 0;
|
213
|
|
|
|
|
|
|
}
|
214
|
|
|
|
|
|
|
# sort keys on last-access-time descending
|
215
|
0
|
|
|
|
|
|
my @sorted_keys = sort { $objects->{$b}->[2] <=> $objects->{$a}->[2] } keys(%{$objects});
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
216
|
0
|
|
|
|
|
|
my @expired_keys = splice(@sorted_keys, $max_objects - 1 - int(0.2 * $max_objects)); # shrink to 20% below max
|
217
|
|
|
|
|
|
|
#print 'About to delete keys: ' . join(' ', @expired_keys) . "\n";
|
218
|
0
|
|
|
|
|
|
foreach my $key (@expired_keys) {
|
219
|
0
|
|
|
|
|
|
delete($objects->{$key});
|
220
|
|
|
|
|
|
|
}
|
221
|
0
|
|
|
|
|
|
$self->{'max_objects_set_counter'} = 0;
|
222
|
0
|
|
|
|
|
|
return scalar(@expired_keys);
|
223
|
|
|
|
|
|
|
}
|
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
#### end of Mobile::UserAgentFactoryCache ####
|
226
|
|
|
|
|
|
|
1;
|
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
__END__
|