line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package WebService::Rackspace::CloudFiles::Object; |
2
|
3
|
|
|
3
|
|
48
|
use Moo; |
|
3
|
|
|
|
|
22
|
|
|
3
|
|
|
|
|
18
|
|
3
|
3
|
|
|
3
|
|
1020
|
use MooX::StrictConstructor; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
30
|
|
4
|
3
|
|
|
3
|
|
2596
|
use Types::Standard qw(Bool Str StrMatch Num Int HashRef InstanceOf); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
33
|
|
5
|
3
|
|
|
3
|
|
3871
|
use Digest::MD5 qw(md5_hex); |
|
3
|
|
|
|
|
57
|
|
|
3
|
|
|
|
|
235
|
|
6
|
3
|
|
|
3
|
|
1213
|
use Digest::MD5::File qw(file_md5_hex); |
|
3
|
|
|
|
|
29161
|
|
|
3
|
|
|
|
|
38
|
|
7
|
3
|
|
|
3
|
|
1668
|
use File::stat; |
|
3
|
|
|
|
|
13845
|
|
|
3
|
|
|
|
|
19
|
|
8
|
3
|
|
|
3
|
|
211
|
use Carp qw(confess); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
133
|
|
9
|
3
|
|
|
3
|
|
1343
|
use WebService::Rackspace::CloudFiles::DateTime; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
7875
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
has 'cloudfiles' => |
12
|
|
|
|
|
|
|
( is => 'ro', isa => InstanceOf['WebService::Rackspace::CloudFiles'], required => 1 ); |
13
|
|
|
|
|
|
|
has 'container' => |
14
|
|
|
|
|
|
|
( is => 'ro', isa => InstanceOf['WebService::Rackspace::CloudFiles::Container'], required => 1 ); |
15
|
|
|
|
|
|
|
has 'name' => ( is => 'ro', isa => Str, required => 1 ); |
16
|
|
|
|
|
|
|
has 'etag' => ( is => 'rw', isa => StrMatch[qr/^[a-z0-9]{32}$/] ); |
17
|
|
|
|
|
|
|
has 'size' => ( is => 'rw', isa => Int ); |
18
|
|
|
|
|
|
|
has 'content_type' => |
19
|
|
|
|
|
|
|
( is => 'rw', isa => Str, default => 'binary/octet-stream' ); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
has 'last_modified' => ( |
22
|
|
|
|
|
|
|
is => 'rw', |
23
|
|
|
|
|
|
|
isa => InstanceOf['WebService::Rackspace::CloudFiles::DateTime'], |
24
|
|
|
|
|
|
|
coerce => sub { |
25
|
|
|
|
|
|
|
my $val = shift; |
26
|
|
|
|
|
|
|
$val = DateTime::Format::HTTP->parse_datetime($val) unless ref $val; |
27
|
|
|
|
|
|
|
bless $val, 'WebService::Rackspace::CloudFiles::DateTime'; |
28
|
|
|
|
|
|
|
return $val; |
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
has 'cache_value' => ( |
33
|
|
|
|
|
|
|
is => 'rw', |
34
|
|
|
|
|
|
|
isa => Bool, |
35
|
|
|
|
|
|
|
required => 1, |
36
|
|
|
|
|
|
|
default => 0 |
37
|
|
|
|
|
|
|
); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
has 'always_check_etag' => ( |
40
|
|
|
|
|
|
|
is => 'rw', |
41
|
|
|
|
|
|
|
isa => Bool, |
42
|
|
|
|
|
|
|
required => 1, |
43
|
|
|
|
|
|
|
default => 1 |
44
|
|
|
|
|
|
|
); |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
has 'object_metadata' => ( |
48
|
|
|
|
|
|
|
is => 'rw', |
49
|
|
|
|
|
|
|
isa => HashRef, |
50
|
|
|
|
|
|
|
required => 0, |
51
|
|
|
|
|
|
|
default => sub { |
52
|
|
|
|
|
|
|
return {}; |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
has 'value' => ( |
57
|
|
|
|
|
|
|
is => 'rw', |
58
|
|
|
|
|
|
|
required => 0, |
59
|
|
|
|
|
|
|
default => undef, |
60
|
|
|
|
|
|
|
); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
has 'local_filename' => ( |
63
|
|
|
|
|
|
|
is => 'rw', |
64
|
|
|
|
|
|
|
isa => Str, |
65
|
|
|
|
|
|
|
required => 0 |
66
|
|
|
|
|
|
|
); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
sub _url { |
74
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
75
|
0
|
|
|
|
|
|
my $url |
76
|
|
|
|
|
|
|
= $self->cloudfiles->storage_url . '/' |
77
|
|
|
|
|
|
|
. $self->container->name . '/' |
78
|
|
|
|
|
|
|
. $self->name; |
79
|
0
|
|
|
|
|
|
utf8::downgrade($url); |
80
|
0
|
|
|
|
|
|
return $url; |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# get the CDN management url for this object (for edge purge) |
84
|
|
|
|
|
|
|
sub _management_url { |
85
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
86
|
0
|
|
|
|
|
|
my $url |
87
|
|
|
|
|
|
|
= $self->cloudfiles->cdn_management_url . "/" |
88
|
|
|
|
|
|
|
. $self->container->name . '/' |
89
|
|
|
|
|
|
|
. $self->name; |
90
|
0
|
|
|
|
|
|
utf8::downgrade($url); |
91
|
0
|
|
|
|
|
|
return $url; |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
sub _cdn_url { |
96
|
0
|
|
|
0
|
|
|
my($self,$ssl) = @_; |
97
|
0
|
|
0
|
|
|
|
$ssl ||= 0; |
98
|
0
|
0
|
|
|
|
|
return sprintf('%s/%s', |
99
|
|
|
|
|
|
|
$ssl ? $self->container->cdn_ssl_uri : $self->container->cdn_uri, |
100
|
|
|
|
|
|
|
$self->name); |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub cdn_url { |
104
|
0
|
|
|
0
|
1
|
|
return shift->_cdn_url; |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub cdn_ssl_url { |
108
|
0
|
|
|
0
|
1
|
|
return shift->_cdn_url(1); |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub head { |
112
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
113
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new( 'HEAD', $self->_url, |
114
|
|
|
|
|
|
|
[ 'X-Auth-Token' => $self->cloudfiles->token ] ); |
115
|
0
|
|
|
|
|
|
my $response = $self->cloudfiles->_request($request); |
116
|
0
|
0
|
|
|
|
|
confess 'Object ' . $self->name . ' not found' if $response->code == 404; |
117
|
0
|
0
|
|
|
|
|
confess 'Unknown error' unless $response->is_success; |
118
|
0
|
|
|
|
|
|
$self->_set_attributes_from_response($response); |
119
|
0
|
|
|
|
|
|
return $response->content; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub get { |
123
|
0
|
|
|
0
|
1
|
|
my ($self, $force_retrieval) = @_; |
124
|
|
|
|
|
|
|
|
125
|
0
|
0
|
0
|
|
|
|
if (!$force_retrieval && $self->cache_value() && defined($self->value()) ) { |
|
|
|
0
|
|
|
|
|
126
|
0
|
|
|
|
|
|
return $self->value(); |
127
|
|
|
|
|
|
|
} else { |
128
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new( 'GET', $self->_url, |
129
|
|
|
|
|
|
|
[ 'X-Auth-Token' => $self->cloudfiles->token ] ); |
130
|
0
|
|
|
|
|
|
my $response = $self->cloudfiles->_request($request); |
131
|
0
|
0
|
|
|
|
|
confess 'Object ' . $self->name . ' not found' if $response->code == 404; |
132
|
0
|
0
|
|
|
|
|
confess 'Unknown error' if !$response->is_success; |
133
|
0
|
0
|
|
|
|
|
confess 'Data corruption error' |
134
|
|
|
|
|
|
|
if $response->header('ETag') ne md5_hex( $response->content ); |
135
|
0
|
|
|
|
|
|
$self->_set_attributes_from_response($response); |
136
|
0
|
0
|
|
|
|
|
if ($self->cache_value()) { |
137
|
0
|
|
|
|
|
|
$self->value($response->content); |
138
|
|
|
|
|
|
|
} |
139
|
0
|
|
|
|
|
|
return $response->content(); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub get_filename { |
144
|
0
|
|
|
0
|
1
|
|
my ( $self, $filename, $force_retrieval ) = @_; |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
## if we aren't forcing retrieval, and we are caching values, and we have a local_filename |
147
|
|
|
|
|
|
|
## defined and it matches the filename we were just given, and the local_filename actually |
148
|
|
|
|
|
|
|
## exists on the filesystem... then we can think about using the cached value. |
149
|
|
|
|
|
|
|
|
150
|
0
|
0
|
0
|
|
|
|
if (!$force_retrieval && $self->cache_value() && defined($self->local_filename()) && |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
151
|
|
|
|
|
|
|
$self->local_filename() eq $filename && -e $self->local_filename() ) { |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
## in order to do this, we have to at least verify that the file we have matches |
154
|
|
|
|
|
|
|
## the file on cloud-files. Best way to do that is to load the metadata and |
155
|
|
|
|
|
|
|
## compare the etags. |
156
|
0
|
|
|
|
|
|
$self->head(); |
157
|
0
|
0
|
|
|
|
|
if ($self->etag() eq file_md5_hex($filename)) { |
158
|
|
|
|
|
|
|
## our local data matches what's in the cloud, we don't have to re-download |
159
|
0
|
|
|
|
|
|
return $self->local_filename(); |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
## if we are here, we have to download the file. |
164
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new( 'GET', $self->_url, |
165
|
|
|
|
|
|
|
[ 'X-Auth-Token' => $self->cloudfiles->token ] ); |
166
|
0
|
|
|
|
|
|
my $response = $self->cloudfiles->_request( $request, $filename ); |
167
|
|
|
|
|
|
|
|
168
|
0
|
0
|
|
|
|
|
confess 'Object ' . $self->name . ' not found' if $response->code == 404; |
169
|
0
|
0
|
|
|
|
|
confess 'Unknown error' if !$response->is_success; |
170
|
0
|
0
|
|
|
|
|
confess 'Data corruption error' unless $self->_validate_local_file( $filename, |
171
|
|
|
|
|
|
|
$response->header('Content-Length'), |
172
|
|
|
|
|
|
|
$response->header('ETag') ); |
173
|
0
|
|
|
|
|
|
$self->_set_attributes_from_response($response); |
174
|
0
|
|
|
|
|
|
my $last_modified = $self->last_modified->epoch; |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# make sure the file has the same last modification time |
177
|
0
|
|
|
|
|
|
utime $last_modified, $last_modified, $filename; |
178
|
0
|
0
|
|
|
|
|
if ($self->cache_value()) { |
179
|
0
|
|
|
|
|
|
$self->local_filename($filename); |
180
|
|
|
|
|
|
|
} |
181
|
0
|
|
|
|
|
|
return $filename; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub delete { |
187
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
188
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new( 'DELETE', $self->_url, |
189
|
|
|
|
|
|
|
[ 'X-Auth-Token' => $self->cloudfiles->token ] ); |
190
|
0
|
|
|
|
|
|
my $response = $self->cloudfiles->_request($request); |
191
|
0
|
0
|
|
|
|
|
confess 'Object ' . $self->name . ' not found' if $response->code == 404; |
192
|
0
|
0
|
|
|
|
|
confess 'Unknown error' if !$response->is_success; |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub purge_cdn { |
196
|
0
|
|
|
0
|
1
|
|
my ($self, @emails) = @_; |
197
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new( 'DELETE', $self->_management_url, |
198
|
|
|
|
|
|
|
[ 'X-Auth-Token' => $self->cloudfiles->token, |
199
|
|
|
|
|
|
|
'X-Purge-Email' => join ', ', @emails] ); |
200
|
0
|
|
|
|
|
|
my $response = $self->cloudfiles->_request($request); |
201
|
0
|
0
|
|
|
|
|
confess 'Not Found' if $response->code == 404; |
202
|
0
|
0
|
|
|
|
|
confess 'Unauthorized request' if $response->code == 403; |
203
|
0
|
0
|
|
|
|
|
confess 'Unknown error' if !$response->is_success; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
sub put { |
207
|
0
|
|
|
0
|
1
|
|
my ( $self, $value ) = @_; |
208
|
0
|
|
|
|
|
|
my $name = $self->name; |
209
|
0
|
|
|
|
|
|
my $md5_hex = md5_hex($value); |
210
|
0
|
|
|
|
|
|
my $size = length($value); |
211
|
|
|
|
|
|
|
|
212
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new( |
213
|
|
|
|
|
|
|
'PUT', |
214
|
|
|
|
|
|
|
$self->_url, |
215
|
|
|
|
|
|
|
$self->_prepare_headers($md5_hex, $size), |
216
|
|
|
|
|
|
|
$value |
217
|
|
|
|
|
|
|
); |
218
|
0
|
|
|
|
|
|
my $response = $self->cloudfiles->_request($request); |
219
|
|
|
|
|
|
|
|
220
|
0
|
0
|
|
|
|
|
if ($response->is_success) { |
221
|
|
|
|
|
|
|
## since the value was set successfully, we can set all our instance data appropriately. |
222
|
|
|
|
|
|
|
|
223
|
0
|
|
|
|
|
|
$self->etag($md5_hex); |
224
|
0
|
|
|
|
|
|
$self->size($size); |
225
|
0
|
0
|
|
|
|
|
if ($self->cache_value) { |
226
|
0
|
|
|
|
|
|
$self->value($value); |
227
|
|
|
|
|
|
|
} |
228
|
0
|
|
|
|
|
|
return; |
229
|
|
|
|
|
|
|
} |
230
|
0
|
0
|
|
|
|
|
confess 'Missing Content-Length or Content-Type header' |
231
|
|
|
|
|
|
|
if $response->code == 412; |
232
|
0
|
0
|
|
|
|
|
confess 'Data corruption error' if $response->code == 422; |
233
|
0
|
0
|
|
|
|
|
confess 'Data corruption error' if $response->header('ETag') ne $md5_hex; |
234
|
0
|
0
|
|
|
|
|
confess 'Unknown error' unless $response->is_success; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub put_filename { |
238
|
0
|
|
|
0
|
1
|
|
my ( $self, $filename ) = @_; |
239
|
0
|
|
|
|
|
|
my $name = $self->name; |
240
|
|
|
|
|
|
|
|
241
|
0
|
|
|
|
|
|
my $md5_hex = file_md5_hex($filename); |
242
|
0
|
|
0
|
|
|
|
my $stat = stat($filename) || confess("No $filename: $!"); |
243
|
0
|
|
|
|
|
|
my $size = $stat->size; |
244
|
|
|
|
|
|
|
|
245
|
0
|
|
|
|
|
|
my $request = HTTP::Request->new( |
246
|
|
|
|
|
|
|
'PUT', |
247
|
|
|
|
|
|
|
$self->_url, |
248
|
|
|
|
|
|
|
$self->_prepare_headers($md5_hex, $size), |
249
|
|
|
|
|
|
|
$self->_content_sub($filename), |
250
|
|
|
|
|
|
|
); |
251
|
0
|
|
|
|
|
|
my $response = $self->cloudfiles->_request($request); |
252
|
|
|
|
|
|
|
|
253
|
0
|
0
|
|
|
|
|
if ($response->is_success) { |
254
|
0
|
|
|
|
|
|
$self->etag($md5_hex); |
255
|
0
|
|
|
|
|
|
$self->size($size); |
256
|
0
|
0
|
|
|
|
|
if ($self->cache_value) { |
257
|
0
|
|
|
|
|
|
$self->local_filename($filename); |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
0
|
0
|
|
|
|
|
confess 'Missing Content-Length or Content-Type header' |
262
|
|
|
|
|
|
|
if $response->code == 412; |
263
|
0
|
0
|
|
|
|
|
confess 'Data corruption error' if $response->code == 422; |
264
|
0
|
0
|
0
|
|
|
|
confess 'Data corruption error' if !defined($response->header('ETag')) || |
265
|
|
|
|
|
|
|
($response->header('ETag') ne $md5_hex); |
266
|
0
|
0
|
|
|
|
|
confess 'Unknown error' unless $response->is_success; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
my %Supported_headers = ( |
270
|
|
|
|
|
|
|
map { $_ => 1 } |
271
|
|
|
|
|
|
|
'Content-Encoding', |
272
|
|
|
|
|
|
|
'Content-Disposition', |
273
|
|
|
|
|
|
|
'X-Object-Manifest', |
274
|
|
|
|
|
|
|
'Access-Control-Allow-Origin', |
275
|
|
|
|
|
|
|
'Access-Control-Allow-Credentials', |
276
|
|
|
|
|
|
|
'Access-Control-Expose-Headers', |
277
|
|
|
|
|
|
|
'Access-Control-Max-Age', |
278
|
|
|
|
|
|
|
'Access-Control-Allow-Methods', |
279
|
|
|
|
|
|
|
'Access-Control-Allow-Headers', |
280
|
|
|
|
|
|
|
'Origin', |
281
|
|
|
|
|
|
|
'Access-Control-Request-Method', |
282
|
|
|
|
|
|
|
'Access-Control-Request-Headers', |
283
|
|
|
|
|
|
|
); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
sub _prepare_headers { |
286
|
0
|
|
|
0
|
|
|
my ($self, $etag, $size) = @_; |
287
|
0
|
|
|
|
|
|
my $headers = HTTP::Headers->new(); |
288
|
|
|
|
|
|
|
|
289
|
0
|
|
|
|
|
|
$headers->header('X-Auth-Token' => $self->cloudfiles->token ); |
290
|
0
|
|
|
|
|
|
$headers->header('Content-length' => $size ); |
291
|
0
|
|
|
|
|
|
$headers->header('ETag' => $etag ); |
292
|
0
|
|
|
|
|
|
$headers->header('Content-Type' => $self->content_type); |
293
|
|
|
|
|
|
|
|
294
|
0
|
|
|
|
|
|
my $header_field; |
295
|
0
|
|
|
|
|
|
foreach my $key (keys %{$self->object_metadata}) { |
|
0
|
|
|
|
|
|
|
296
|
0
|
|
|
|
|
|
$header_field = $key; |
297
|
|
|
|
|
|
|
$header_field = 'X-Object-Meta-' . $header_field |
298
|
0
|
0
|
|
|
|
|
unless $Supported_headers{$header_field}; |
299
|
|
|
|
|
|
|
# make _'s -'s for header sending. |
300
|
0
|
|
|
|
|
|
$header_field =~ s/_/-/g; |
301
|
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
|
$headers->header($header_field => $self->object_metadata->{$key}); |
303
|
|
|
|
|
|
|
} |
304
|
0
|
|
|
|
|
|
return $headers; |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub _content_sub { |
309
|
0
|
|
|
0
|
|
|
my $self = shift; |
310
|
0
|
|
|
|
|
|
my $filename = shift; |
311
|
0
|
|
|
|
|
|
my $stat = stat($filename); |
312
|
0
|
|
|
|
|
|
my $remaining = $stat->size; |
313
|
0
|
|
0
|
|
|
|
my $blksize = $stat->blksize || 4096; |
314
|
|
|
|
|
|
|
|
315
|
0
|
0
|
0
|
|
|
|
confess "$filename not a readable file with fixed size" |
|
|
|
0
|
|
|
|
|
316
|
|
|
|
|
|
|
unless -r $filename and ( -f _ || $remaining ); |
317
|
0
|
0
|
|
|
|
|
my $fh = IO::File->new( $filename, 'r' ) |
318
|
|
|
|
|
|
|
or confess "Could not open $filename: $!"; |
319
|
0
|
|
|
|
|
|
$fh->binmode; |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
return sub { |
322
|
0
|
|
|
0
|
|
|
my $buffer; |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
# upon retries the file is closed and we must reopen it |
325
|
0
|
0
|
|
|
|
|
unless ( $fh->opened ) { |
326
|
0
|
0
|
|
|
|
|
$fh = IO::File->new( $filename, 'r' ) |
327
|
|
|
|
|
|
|
or confess "Could not open $filename: $!"; |
328
|
0
|
|
|
|
|
|
$fh->binmode; |
329
|
0
|
|
|
|
|
|
$remaining = $stat->size; |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# warn "read remaining $remaining"; |
333
|
0
|
0
|
|
|
|
|
unless ( my $read = $fh->read( $buffer, $blksize ) ) { |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
# warn "read $read buffer $buffer remaining $remaining"; |
336
|
0
|
0
|
0
|
|
|
|
confess |
337
|
|
|
|
|
|
|
"Error while reading upload content $filename ($remaining remaining) $!" |
338
|
|
|
|
|
|
|
if $! and $remaining; |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# otherwise, we found EOF |
341
|
0
|
0
|
|
|
|
|
$fh->close |
342
|
|
|
|
|
|
|
or confess "close of upload content $filename failed: $!"; |
343
|
0
|
|
0
|
|
|
|
$buffer ||= '' |
344
|
|
|
|
|
|
|
; # LWP expects an emptry string on finish, read returns 0 |
345
|
|
|
|
|
|
|
} |
346
|
0
|
|
|
|
|
|
$remaining -= length($buffer); |
347
|
0
|
|
|
|
|
|
return $buffer; |
348
|
0
|
|
|
|
|
|
}; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
sub _set_attributes_from_response { |
352
|
0
|
|
|
0
|
|
|
my ( $self, $response ) = @_; |
353
|
|
|
|
|
|
|
|
354
|
0
|
|
|
|
|
|
$self->etag( $response->header('ETag') ); |
355
|
0
|
|
|
|
|
|
$self->size( $response->header('Content-Length') ); |
356
|
0
|
|
|
|
|
|
$self->content_type( $response->header('Content-Type') ); |
357
|
0
|
|
|
|
|
|
$self->last_modified( $response->header('Last-Modified') ); |
358
|
0
|
|
|
|
|
|
my $metadata = {}; |
359
|
0
|
|
|
|
|
|
foreach my $headername ($response->headers->header_field_names) { |
360
|
0
|
0
|
|
|
|
|
if ($headername =~ /^x-object-meta-(.*)/i) { |
361
|
0
|
|
|
|
|
|
my $key = $1; |
362
|
|
|
|
|
|
|
## undo our _ to - translation |
363
|
0
|
|
|
|
|
|
$key =~ s/-/_/g; |
364
|
0
|
|
|
|
|
|
$metadata->{lc($key)} = $response->header($headername); |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
} |
367
|
0
|
|
|
|
|
|
$self->object_metadata($metadata); |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
sub _validate_local_file { |
371
|
0
|
|
|
0
|
|
|
my ($self, $localfile, $size, $md5) = @_; |
372
|
|
|
|
|
|
|
|
373
|
0
|
|
|
|
|
|
my $stat = stat($localfile); |
374
|
0
|
|
|
|
|
|
my $localsize = $stat->size; |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
# first check size, if they are different, we don't need to bother with |
377
|
|
|
|
|
|
|
# an expensive md5 calculation on the whole file. |
378
|
0
|
0
|
|
|
|
|
if ($size != $localsize ) { |
379
|
0
|
|
|
|
|
|
return 0; |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
|
382
|
0
|
0
|
0
|
|
|
|
if ($self->always_check_etag && ($md5 ne file_md5_hex($localfile))) { |
383
|
0
|
|
|
|
|
|
return 0; |
384
|
|
|
|
|
|
|
} |
385
|
0
|
|
|
|
|
|
return 1; |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
1; |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
__END__ |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=head1 NAME |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
WebService::Rackspace::CloudFiles::Object - Represent a Cloud Files object |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
=head1 SYNOPSIS |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
# To create a new object |
399
|
|
|
|
|
|
|
my $xxx = $container->object( name => 'XXX' ); |
400
|
|
|
|
|
|
|
$xxx->put('this is the value'); |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# To create a new object with the contents of a local file |
403
|
|
|
|
|
|
|
my $yyy = $container->object( name => 'YYY', content_type => 'text/plain' ); |
404
|
|
|
|
|
|
|
$yyy->put_filename('README'); |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
# To fetch an object: |
407
|
|
|
|
|
|
|
my $xxx2 = $container->object( name => 'XXX' ); |
408
|
|
|
|
|
|
|
my $value = $xxx2->get; |
409
|
|
|
|
|
|
|
say 'has name ' . $xxx2->name; |
410
|
|
|
|
|
|
|
say 'has md5 ' . $xxx2->etag; |
411
|
|
|
|
|
|
|
say 'has size ' . $xxx2->size; |
412
|
|
|
|
|
|
|
say 'has content type ' . $xxx2->content_type; |
413
|
|
|
|
|
|
|
say 'has last_modified ' . $xxx2->last_modified; |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# To download an object to a local file |
416
|
|
|
|
|
|
|
$yyy->get_filename('README.downloaded'); |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
=head1 DESCRIPTION |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
This class represents an object in Cloud Files. It is created by |
421
|
|
|
|
|
|
|
calling object or objects on a L<WebService::Rackspace::CloudFiles::Container> object. |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=head1 METHODS |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
=head2 name |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
Returns the name of the object. |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
say 'has name ' . $object->name; |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
=head2 head |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
Fetches the metadata of the object: |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
$object->head; |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
=head2 always_check_etag |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
When set to true, forces md5 calculation on every file download and |
441
|
|
|
|
|
|
|
compares it to the provided etag. This can be a very expensive operation, |
442
|
|
|
|
|
|
|
especially on larger files. Setting always_check_etag to false will avoid the |
443
|
|
|
|
|
|
|
checksum on the file and will validate the file transfer was complete by |
444
|
|
|
|
|
|
|
comparing the file sizes after download. Defaults to true. |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=head2 cache_value |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
When set to true, any values retrieved from the server will be cached |
449
|
|
|
|
|
|
|
within the object, this allows you to continue to use the value |
450
|
|
|
|
|
|
|
without re-retrieving it from CloudFiles repeatedly. Defaults to false. |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=head2 get |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
Fetches the metadata and content of an object: |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
my $value = $object->get; |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
If cache_value is enabled, will not re-retrieve the value from CloudFiles. |
459
|
|
|
|
|
|
|
To force re-retrieval, pass true to the get routine: |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
my $value = $object->get(1); |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=head2 get_filename |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
Downloads the content of an object to a local file, |
466
|
|
|
|
|
|
|
checks the integrity of the file, sets metadata in the object |
467
|
|
|
|
|
|
|
and sets the last modified time of the file to the same as the object. |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
$object->get_filename('README.downloaded'); |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
If cache_value is enabled and the file has already been retrieved and is |
472
|
|
|
|
|
|
|
present on the filesystem with the filename provided, and the file size and |
473
|
|
|
|
|
|
|
md5 hash of the local file match what is in CloudFiles, the file will not |
474
|
|
|
|
|
|
|
be re-retrieved and the local file will be returned as-is. To force a |
475
|
|
|
|
|
|
|
re-fetch of the file, pass a true value as the second arg to get_filename(): |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
$object->get_filename('README.downloaded',1); |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=head2 delete |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
Deletes an object: |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
$object->delete; |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head2 purge_cdn |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
Purges an object in a CDN enabled container without having to wait for the TTL |
488
|
|
|
|
|
|
|
to expire. |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
$object->purge_cdn; |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
Purging an object in a CDN enabled container may take long time. So you can |
493
|
|
|
|
|
|
|
optionally provide one or more emails to be notified after the object is |
494
|
|
|
|
|
|
|
fully purged. |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
my @emails = ('foo@example.com', 'bar@example.com'); |
497
|
|
|
|
|
|
|
$object->purge_cdn(@emails); |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
=head2 put |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
Creates a new object: |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
my $xxx = $container->object( name => 'XXX' ); |
504
|
|
|
|
|
|
|
$xxx->put('this is the value'); |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=head2 put_filename |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
Creates a new object with the contents of a local file: |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
my $yyy = $container->object( name => 'YYY', content_type => 'text/plain' ); |
511
|
|
|
|
|
|
|
$yyy->put_filename('README'); |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
=head2 etag |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
Returns the entity tag of the object, which is its MD5: |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
say 'has md5 ' . $object->etag; |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
=head2 size |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
Return the size of an object in bytes: |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
say 'has size ' . $object->size; |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
=head2 content_type |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
Return the content type of an object: |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
say 'has content type ' . $object->content_type; |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
=head2 last_modified |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
Return the last modified time of an object as a L<DateTime> object: |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
say 'has last_modified ' . $object->last_modified; |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
=head2 object_metadata |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
Sets or returns a hashref of metadata to be stored along with the file |
540
|
|
|
|
|
|
|
in CloudFiles. This hashref must containe key => value pairs and values |
541
|
|
|
|
|
|
|
must be scalar type, if you require storage of complex data, you will need |
542
|
|
|
|
|
|
|
to flatten it in some way prior to setting it here. Also, due to the way |
543
|
|
|
|
|
|
|
that CloudFiles works with metadata, when retrieved from CloudFiles, your |
544
|
|
|
|
|
|
|
keys will be lowercase. Note that since underscores are not permitted in |
545
|
|
|
|
|
|
|
keys within CloudFiles, any underscores are translated to dashes when |
546
|
|
|
|
|
|
|
transmitted to CloudFiles. They are re-translated when they are retrieved. |
547
|
|
|
|
|
|
|
This is mentioned only because if you access your data through a different |
548
|
|
|
|
|
|
|
language or interface, your metadata keys will contain dashes instead of |
549
|
|
|
|
|
|
|
underscores. |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=head2 cdn_url |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
Retrieve HTTP CDN url to object. |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
=head2 cdn_ssl_url |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
Retrieve HTTPS CDN url to object. |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=head2 cloudfiles |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
=head2 container |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=head2 value |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
=head1 SEE ALSO |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
L<WebService::Rackspace::CloudFiles>, L<WebService::Rackspace::CloudFiles::Container>. |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=head1 AUTHORS |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
Christiaan Kras <ckras@cpan.org>. |
572
|
|
|
|
|
|
|
Leon Brocard <acme@astray.com>. |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
=head1 COPYRIGHT |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
Copyright (C) 2010-2011, Christiaan Kras |
577
|
|
|
|
|
|
|
Copyright (C) 2008-9, Leon Brocard |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=head1 LICENSE |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
This module is free software; you can redistribute it or modify it |
582
|
|
|
|
|
|
|
under the same terms as Perl itself. |