| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | =encoding utf8 | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | =head1 NAME | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | WebService::CEPH | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | CEPH client for simple workflow, supporting multipart uploads. Most docs are in Russian. | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | ÐÐ»Ð¸Ð½Ñ Ð´Ð»Ñ CEPH, без низкоÑÑовневого кода Ð´Ð»Ñ Ð¾Ð±ÑÐµÐ½Ð¸Ñ Ñ Ð±Ð¸Ð±Ð»Ð¸Ð¾Ñекой Amazon S3 | 
| 12 |  |  |  |  |  |  | (она вÑнеÑена в оÑделÑнÑй клаÑÑ). | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | ÐбÑабоÑка оÑибок (иÑклÑÑÐµÐ½Ð¸Ñ Ð¸Ñ
 Ñип иÑп; повÑоÑÑ Ð½ÐµÑдаÑнÑÑ
 запÑоÑов) - на ÑовеÑÑи более низкоÑÑовневой библиоÑеки, | 
| 15 |  |  |  |  |  |  | еÑли иное не гаÑанÑиÑÑеÑÑÑ Ð² ÑÑой докÑменÑаÑии. | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | ÐаÑамеÑÑÑ ÐºÐ¾Ð½ÑÑÑÑкÑоÑа: | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | ÐбÑзаÑелÑнÑе паÑамеÑÑÑ: | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | protocol - http/https | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | host - Ñ
оÑÑ Ð±ÑкÑнда | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | key - клÑÑ Ð´Ð»Ñ Ð²Ñ
ода | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | secret - secret Ð´Ð»Ñ Ð²Ñ
ода | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | ÐеобÑзаÑелÑнÑе паÑамеÑÑÑ: | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | bucket - Ð¸Ð¼Ñ Ð±Ð°ÐºÐµÑа (не нÑжен ÑолÑко Ð´Ð»Ñ Ð¿Ð¾Ð»ÑÑÐµÐ½Ð¸Ñ ÑпиÑка бакеÑов) | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | driver_name - в даннÑй Ð¼Ð¾Ð¼ÐµÐ½Ñ ÑолÑко 'NetAmazonS3' | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | multipart_threshold - поÑле какого ÑазмеÑа Ñайла (в байÑаÑ
) наÑинаÑÑ multipart upload | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | multisegment_threshold - поÑле какого ÑазмеÑа Ñайла (в байÑаÑ
) бÑÐ´ÐµÑ multisegment download | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | query_string_authentication_host_replace - пÑоÑокол-Ñ
оÑÑ Ð½Ð° коÑоÑÑй заменÑÑÑ URL в query_string_authentication_uri | 
| 40 |  |  |  |  |  |  | должен наÑинаÑÑÑÑ Ñ Ð¿ÑоÑокола (http/https), заÑем Ñ
оÑÑ, на конÑе Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ, а Ð¼Ð¾Ð¶ÐµÑ Ð½Ðµ бÑÑÑ ÑлÑÑа. | 
| 41 |  |  |  |  |  |  | нÑжен еÑли Ð²Ñ Ñ
оÑиÑе ÑмениÑÑ Ñ
оÑÑ Ð´Ð»Ñ Ð¾ÑдаÑи клиенÑам (Ñ Ð²Ð°Ñ ÐºÐ»Ð°ÑÑеÑ) или пÑоÑокол (https внеÑним клиенÑам) | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | =cut | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | package WebService::CEPH; | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | our $VERSION = '0.015'; # VERSION | 
| 48 |  |  |  |  |  |  |  | 
| 49 | 2 |  |  | 2 |  | 114044 | use strict; | 
|  | 2 |  |  |  |  | 10 |  | 
|  | 2 |  |  |  |  | 52 |  | 
| 50 | 2 |  |  | 2 |  | 8 | use warnings; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 40 |  | 
| 51 | 2 |  |  | 2 |  | 8 | use Carp; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 90 |  | 
| 52 | 2 |  |  | 2 |  | 538 | use WebService::CEPH::NetAmazonS3; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | use Digest::MD5 qw/md5_hex/; | 
| 54 |  |  |  |  |  |  | use Fcntl qw/:seek/; | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | use constant MINIMAL_MULTIPART_PART => 5*1024*1024; | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | sub _check_ascii_key { confess "Key should be ASCII-only" unless $_[0] !~ /[^\x00-\x7f]/ } | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | =head2 new | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | ÐонÑÑÑÑкÑоÑ. ÐаÑамеÑÑÑ Ñм. вÑÑе. | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | =cut | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | sub new { | 
| 67 |  |  |  |  |  |  | my ($class, %args) = @_; | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | my $self = bless +{}, $class; | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | # mandatory | 
| 72 |  |  |  |  |  |  | $self->{$_} = delete $args{$_} // confess "Missing $_" | 
| 73 |  |  |  |  |  |  | for (qw/protocol host key secret/); | 
| 74 |  |  |  |  |  |  | # optional | 
| 75 |  |  |  |  |  |  | for (qw/bucket driver_name multipart_threshold multisegment_threshold query_string_authentication_host_replace/) { | 
| 76 |  |  |  |  |  |  | if (defined(my $val = delete $args{$_})) { | 
| 77 |  |  |  |  |  |  | $self->{$_} = $val; | 
| 78 |  |  |  |  |  |  | } | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | confess "Unused arguments: @{[ %args]}" if %args; | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | $self->{driver_name} ||= "NetAmazonS3"; | 
| 84 |  |  |  |  |  |  | $self->{multipart_threshold} ||= MINIMAL_MULTIPART_PART; | 
| 85 |  |  |  |  |  |  | $self->{multisegment_threshold}  ||= MINIMAL_MULTIPART_PART; | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | confess "multipart_threshold should be greater or eq. MINIMAL_MULTIPART_PART (5Mb) (now multipart_threshold=$self->{multipart_threshold}" | 
| 88 |  |  |  |  |  |  | if $self->{multipart_threshold} < MINIMAL_MULTIPART_PART; | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | my $driver_class = __PACKAGE__."::".$self->{driver_name}; # should be loaded via "use" at top of file | 
| 91 |  |  |  |  |  |  | $self->{driver} = $driver_class->new(map { $_ => $self->{$_} } qw/protocol host key secret bucket/ ); | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | $self; | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | =head2 upload | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | ÐагÑÑÐ¶Ð°ÐµÑ Ñайл в CEPH. ÐÑли Ñайл Ñже ÑÑÑеÑÑвÑÐµÑ - он заменÑеÑÑÑ. | 
| 102 |  |  |  |  |  |  | ÐÑли даннÑе болÑÑе опÑеделÑнного ÑазмеÑа, пÑоиÑÑ
одим multipart upload | 
| 103 |  |  |  |  |  |  | ÐиÑего не возвÑаÑÐ°ÐµÑ | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | ÐаÑамеÑÑÑ: | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | 0-й - $self | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | 1-й - Ð¸Ð¼Ñ ÐºÐ»ÑÑа | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | 2-й - ÑкалÑÑ, даннÑе клÑÑа | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | 3-й - Content-type. ÐÑли undef, иÑполÑзÑеÑÑÑ Ð´ÐµÑолÑнÑй binary/octet-stream | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | =cut | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | sub upload { | 
| 118 |  |  |  |  |  |  | my ($self, $key) = (shift, shift); # поÑле ÑÑого $_[0] - даннÑе, $_[1] - Content-type | 
| 119 |  |  |  |  |  |  | $self->_upload($key, sub { substr($_[0], $_[1], $_[2]) }, length($_[0]), md5_hex($_[0]), $_[1], $_[0]); | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | =head2 upload_from_file | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | То же, ÑÑо upload, но пÑоиÑÑ
Ð¾Ð´Ð¸Ñ ÑÑение из Ñайла. | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | ÐаÑамеÑÑÑ: | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | 0-й - $self | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | 1-й - Ð¸Ð¼Ñ ÐºÐ»ÑÑа | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | 2-й - Ð¸Ð¼Ñ Ñайла (еÑли ÑкалÑÑ), инаÑе оÑкÑÑÑÑй filehandle | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | 3-й - Content-type. ÐÑли undef, иÑполÑзÑеÑÑÑ Ð´ÐµÑолÑнÑй binary/octet-stream | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | ÐваждÑй пÑоÑ
Ð¾Ð´Ð¸Ñ Ð¿Ð¾ ÑайлÑ, вÑÑÑиÑÑÐ²Ð°Ñ md5. Файл не должен бÑÑÑ Ð¿Ð°Ð¹Ð¿Ð¾Ð¼, его ÑÐ°Ð·Ð¼ÐµÑ Ð½Ðµ должен менÑÑÑÑÑ. | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | =cut | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | sub upload_from_file { | 
| 141 |  |  |  |  |  |  | my ($self, $key, $fh_or_filename, $content_type) = @_; | 
| 142 |  |  |  |  |  |  | my $fh = do { | 
| 143 |  |  |  |  |  |  | if (ref $fh_or_filename) { | 
| 144 |  |  |  |  |  |  | $fh_or_filename | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  | else { | 
| 147 |  |  |  |  |  |  | open my $f, "<", $fh_or_filename; | 
| 148 |  |  |  |  |  |  | binmode $f; | 
| 149 |  |  |  |  |  |  | $f; | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  | }; | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | my $md5 = Digest::MD5->new; | 
| 154 |  |  |  |  |  |  | $md5->addfile($fh); | 
| 155 |  |  |  |  |  |  | seek($fh, 0, SEEK_SET); | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | $self->_upload( | 
| 158 |  |  |  |  |  |  | $key, | 
| 159 |  |  |  |  |  |  | sub { read($_[0], my $data, $_[2]) // confess "Error reading data $!\n"; $data }, | 
| 160 |  |  |  |  |  |  | -s $fh, $md5->hexdigest, $content_type, $fh | 
| 161 |  |  |  |  |  |  | ); | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | =head2 _upload | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | ÐÑиваÑнÑй меÑод Ð´Ð»Ñ upload/upload_from_file | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | ÐаÑамеÑÑÑ | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | 1) self | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | 2) клÑÑ | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | 3) иÑеÑаÑÐ¾Ñ Ñ Ð¸Ð½ÑеÑÑейÑом (даннÑе, оÑÑÑеÑ, длина). "даннÑе" Ð´Ð¾Ð»Ð¶Ð½Ñ ÑооÑвеÑÑÑвоваÑÑ Ð¿Ð¾ÑÐ»ÐµÐ´Ð½ÐµÐ¼Ñ | 
| 175 |  |  |  |  |  |  | паÑамеÑÑÑ ÑÑой ÑÑнкÑии (Ñ.е. (6)) | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | 4) длина даннÑÑ | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | 5) заÑанее вÑÑÑиÑаннÑй md5 Ð¾Ñ Ð´Ð°Ð½Ð½ÑÑ | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | 6) Content-type. ÐÑли undef, иÑполÑзÑеÑÑÑ Ð´ÐµÑолÑнÑй binary/octet-stream | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | 7) даннÑе. или ÑкалÑÑ. или filehandle | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | =cut | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | sub _upload { | 
| 189 |  |  |  |  |  |  | # after that $_[0] is data (scalar or filehandle) | 
| 190 |  |  |  |  |  |  | my ($self, $key, $iterator, $length, $md5_hex, $content_type) = (shift, shift, shift, shift, shift, shift); | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | confess "Bucket name is required" unless $self->{bucket}; | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | _check_ascii_key($key); | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | if ($length > $self->{multipart_threshold}) { | 
| 197 |  |  |  |  |  |  | my $multipart = $self->{driver}->initiate_multipart_upload($key, $md5_hex, $content_type); | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | my $len = $length; | 
| 200 |  |  |  |  |  |  | my $offset = 0; | 
| 201 |  |  |  |  |  |  | my $part = 0; | 
| 202 |  |  |  |  |  |  | while ($offset < $len) { | 
| 203 |  |  |  |  |  |  | my $chunk = $iterator->($_[0], $offset, $self->{multipart_threshold}); | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | $self->{driver}->upload_part($multipart, ++$part, $chunk); | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | $offset += $self->{multipart_threshold}; | 
| 208 |  |  |  |  |  |  | } | 
| 209 |  |  |  |  |  |  | $self->{driver}->complete_multipart_upload($multipart); | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  | else { | 
| 212 |  |  |  |  |  |  | $self->{driver}->upload_single_request($key, $iterator->($_[0], 0, $length), $content_type); | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | return; | 
| 216 |  |  |  |  |  |  | } | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | =head2 download | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | СкаÑÐ¸Ð²Ð°ÐµÑ Ð´Ð°Ð½Ð½Ñе обÑекÑа Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ $key и возвÑаÑÐ°ÐµÑ Ð¸Ñ
. | 
| 221 |  |  |  |  |  |  | ÐÑли обÑÐµÐºÑ Ð½Ðµ ÑÑÑеÑÑвÑеÑ, возвÑаÑÐ°ÐµÑ undef. | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | ÐÑли ÑÐ°Ð·Ð¼ÐµÑ Ð¾Ð±ÑекÑа по ÑакÑÑ Ð¾ÐºÐ°Ð¶ÐµÑÑÑ Ð±Ð¾Ð»ÑÑе multisegment_threshold, | 
| 224 |  |  |  |  |  |  | обÑÐµÐºÑ Ð±ÑÐ´ÐµÑ ÑкаÑан неÑколÑкими запÑоÑами Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ¾Ð¼ Range (Ñ.е. multi segment download). | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | РданнÑй Ð¼Ð¾Ð¼ÐµÐ½Ñ ÐµÑÑÑ workaround Ð´Ð»Ñ Ð±Ð°Ð³Ð° http://lists.ceph.com/pipermail/ceph-users-ceph.com/2016-June/010704.html, | 
| 227 |  |  |  |  |  |  | в ÑвÑзи Ñ Ð½Ð¸Ð¼ вÑегда делаеÑÑÑ Ð»Ð¸Ñний HTTP запÑÐ¾Ñ - запÑÐ¾Ñ Ð´Ð»Ð¸Ð½Ñ Ñайла. ÐлÑÑ Ð½Ðµ иÑклÑÑÑн Race condition. | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | =cut | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | sub download { | 
| 232 |  |  |  |  |  |  | my ($self, $key) = @_; | 
| 233 |  |  |  |  |  |  | my $data; | 
| 234 |  |  |  |  |  |  | # workaround for CEPH bug http://lists.ceph.com/pipermail/ceph-users-ceph.com/2016-June/010704.html | 
| 235 |  |  |  |  |  |  | my $cephsize = $self->size($key); | 
| 236 |  |  |  |  |  |  | if (defined($cephsize) && $cephsize == 0) { | 
| 237 |  |  |  |  |  |  | return ''; | 
| 238 |  |  |  |  |  |  | } else { | 
| 239 |  |  |  |  |  |  | # / workaround for CEPH bug | 
| 240 |  |  |  |  |  |  | _download($self, $key, sub { $data .= $_[0] }) or return; | 
| 241 |  |  |  |  |  |  | return $data; | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | =head2 download_to_file | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | СкаÑÐ¸Ð²Ð°ÐµÑ Ð´Ð°Ð½Ð½Ñе обÑекÑа Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ $key в Ñайл $fh_or_filename. | 
| 248 |  |  |  |  |  |  | ÐÑли обÑÐµÐºÑ Ð½Ðµ ÑÑÑеÑÑвÑеÑ, возвÑаÑÐ°ÐµÑ undef (пÑи ÑÑом вÑÑ
одной Ñайл вÑÑ Ñавно бÑÐ´ÐµÑ Ð¸ÑпоÑÑен и, возможно, | 
| 249 |  |  |  |  |  |  | ÑаÑÑиÑно запиÑан в ÑлÑÑае race condition - ÑдалÑйÑе ÑÑи даннÑе Ñами; еÑли ÑдалÑÑÑ ÑÑжело - полÑзÑйÑеÑÑ | 
| 250 |  |  |  |  |  |  | меÑодом download) | 
| 251 |  |  |  |  |  |  | ÐнаÑе возвÑаÑÐ°ÐµÑ ÑÐ°Ð·Ð¼ÐµÑ Ð·Ð°Ð¿Ð¸ÑаннÑÑ
 даннÑÑ
. | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | ÐÑÑ
одной Ñайл оÑкÑÑваеÑÑÑ Ð² Ñежиме пеÑезапиÑи, еÑли ÑÑо Ð¸Ð¼Ñ Ñайла, еÑли ÑÑо filehandle, | 
| 254 |  |  |  |  |  |  | Ñо обÑазаеÑÑÑ Ð½Ð° нÑлевÑÑ Ð´Ð»Ð¸Ð½Ñ Ð¸ пиÑеÑÑÑ Ñ Ð½Ð°Ñала. | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | ÐÑли ÑÐ°Ð·Ð¼ÐµÑ Ð¾Ð±ÑекÑа по ÑакÑÑ Ð¾ÐºÐ°Ð¶ÐµÑÑÑ Ð±Ð¾Ð»ÑÑе multisegment_threshold, | 
| 257 |  |  |  |  |  |  | обÑÐµÐºÑ Ð±ÑÐ´ÐµÑ ÑкаÑан неÑколÑкими запÑоÑами Ñ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ¾Ð¼ Range (Ñ.е. multi segment download). | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | РданнÑй Ð¼Ð¾Ð¼ÐµÐ½Ñ ÐµÑÑÑ workaround Ð´Ð»Ñ Ð±Ð°Ð³Ð° http://lists.ceph.com/pipermail/ceph-users-ceph.com/2016-June/010704.html, | 
| 260 |  |  |  |  |  |  | в ÑвÑзи Ñ Ð½Ð¸Ð¼ вÑегда делаеÑÑÑ Ð»Ð¸Ñний HTTP запÑÐ¾Ñ - запÑÐ¾Ñ Ð´Ð»Ð¸Ð½Ñ Ñайла. ÐлÑÑ Ð½Ðµ иÑклÑÑÑн Race condition. | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | =cut | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | sub download_to_file { | 
| 265 |  |  |  |  |  |  | my ($self, $key, $fh_or_filename) = @_; | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | my $fh = do { | 
| 268 |  |  |  |  |  |  | if (ref $fh_or_filename) { | 
| 269 |  |  |  |  |  |  | seek($fh_or_filename, SEEK_SET, 0); | 
| 270 |  |  |  |  |  |  | truncate($fh_or_filename, 0); | 
| 271 |  |  |  |  |  |  | $fh_or_filename | 
| 272 |  |  |  |  |  |  | } | 
| 273 |  |  |  |  |  |  | else { | 
| 274 |  |  |  |  |  |  | open my $f, ">", $fh_or_filename; | 
| 275 |  |  |  |  |  |  | binmode $f; | 
| 276 |  |  |  |  |  |  | $f; | 
| 277 |  |  |  |  |  |  | } | 
| 278 |  |  |  |  |  |  | }; | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | # workaround for CEPH bug http://lists.ceph.com/pipermail/ceph-users-ceph.com/2016-June/010704.html | 
| 281 |  |  |  |  |  |  | my $cephsize = $self->size($key); | 
| 282 |  |  |  |  |  |  | if (defined($cephsize) && $cephsize == 0) { | 
| 283 |  |  |  |  |  |  | return 0; | 
| 284 |  |  |  |  |  |  | } | 
| 285 |  |  |  |  |  |  | else { | 
| 286 |  |  |  |  |  |  | # / workaround for CEPH bug | 
| 287 |  |  |  |  |  |  | my $size = 0; | 
| 288 |  |  |  |  |  |  | _download($self, $key, sub { | 
| 289 |  |  |  |  |  |  | $size += length($_[0]); | 
| 290 |  |  |  |  |  |  | print $fh $_[0] or confess "Error writing to file $!" | 
| 291 |  |  |  |  |  |  | }) or return; | 
| 292 |  |  |  |  |  |  | return $size; | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  | } | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | =head2 _download | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | ÐÑиваÑнÑй меÑод Ð´Ð»Ñ download/download_to_file | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | ÐаÑамеÑÑÑ: | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | 1) self | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | 2) Ð¸Ð¼Ñ ÐºÐ»ÑÑа | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | 3) appender - замÑкание в коÑоÑое бÑдÑÑ Ð¿ÐµÑедаваÑÑÑÑ Ð´Ð°Ð½Ð½Ñе Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи. оно должно аккÑмÑлиÑоваÑÑ Ð¸Ñ
 кÑда-Ñо | 
| 307 |  |  |  |  |  |  | Ñебе или пиÑаÑÑ Ð² Ñайл, коÑоÑÑй оно Ñамо знаеÑ. | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | =cut | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | sub _download { | 
| 312 |  |  |  |  |  |  | my ($self, $key, $appender) = @_; | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | confess "Bucket name is required" unless $self->{bucket}; | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | _check_ascii_key($key); | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | my $offset = 0; | 
| 319 |  |  |  |  |  |  | my $check_md5 = undef; | 
| 320 |  |  |  |  |  |  | my $md5 =  Digest::MD5->new; | 
| 321 |  |  |  |  |  |  | my $got_etag = undef; | 
| 322 |  |  |  |  |  |  | while() { | 
| 323 |  |  |  |  |  |  | my ($dataref, $bytesleft, $etag, $custom_md5) = $self->{driver}->download_with_range($key, $offset, $offset + $self->{multisegment_threshold}); | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | # ÐÑли обÑÐµÐºÑ Ð½Ðµ найден - возвÑаÑаем undef | 
| 326 |  |  |  |  |  |  | # даже еÑли пÑи мÑлÑÑиÑегменÑном ÑкаÑивании обÑÐµÐºÑ Ð½ÐµÐ¾Ð¶Ð¸Ð´Ð°Ð½Ð½Ð¾ иÑÑез на каком-Ñо ÑегменÑе, знаÑÐ¸Ñ | 
| 327 |  |  |  |  |  |  | # его кÑо-Ñо Ñдалил, нÑжно вÑÑ Ð¶Ðµ веÑнÑÑÑ undef | 
| 328 |  |  |  |  |  |  | # ÐÑи ÑÑом, пÑи ÑкаÑивании в Ñайл, ÑаÑÑÑ Ð´Ð°Ð½Ð½ÑÑ
 Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ñже запиÑана. УдалÑйÑе иÑ
 Ñами. | 
| 329 |  |  |  |  |  |  | return unless ($dataref); | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | if (defined $got_etag) { | 
| 332 |  |  |  |  |  |  | # Ðо вÑÐµÐ¼Ñ ÑкаÑиваниÑ, кÑо-Ñо подменил Ñайл (ETag изменилÑÑ), Ð ÑооÑв. Ñ HTTP, ETag гаÑанÑиÑованно | 
| 333 |  |  |  |  |  |  | # бÑÐ´ÐµÑ ÑазнÑм Ð´Ð»Ñ ÑазнÑÑ
 Ñайлов (но не ÑÐ°ÐºÑ ÑÑо одинаковÑм Ð´Ð»Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²ÑÑ
) | 
| 334 |  |  |  |  |  |  | # Ð ÑÑом ÑлÑÑае падаем.. ÐавеÑное можно в бÑдÑÑем делаÑÑ retry запÑоÑов.. | 
| 335 |  |  |  |  |  |  | confess "File changed during download. Race condition. Please retry request" | 
| 336 |  |  |  |  |  |  | unless $got_etag eq $etag; | 
| 337 |  |  |  |  |  |  | } | 
| 338 |  |  |  |  |  |  | else { | 
| 339 |  |  |  |  |  |  | $got_etag = $etag; | 
| 340 |  |  |  |  |  |  | } | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | # ÐÑовеÑÑем md5 ÑолÑко еÑли ETag "ноÑмалÑнÑй" Ñ md5 (бÑл не multipart upload) | 
| 343 |  |  |  |  |  |  | if (!defined $check_md5) { | 
| 344 |  |  |  |  |  |  | my ($etag_md5) = $etag =~ /^([0-9a-f]+)$/; | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | confess "ETag looks like valid md5 and x-amz-meta-md5 presents but they do not match" | 
| 347 |  |  |  |  |  |  | if ($etag_md5 && $custom_md5 && $etag_md5 ne $custom_md5); | 
| 348 |  |  |  |  |  |  | if ($etag_md5) { | 
| 349 |  |  |  |  |  |  | $check_md5 = $etag_md5; | 
| 350 |  |  |  |  |  |  | } elsif ($custom_md5) { | 
| 351 |  |  |  |  |  |  | $check_md5 = $custom_md5; | 
| 352 |  |  |  |  |  |  | } else { | 
| 353 |  |  |  |  |  |  | $check_md5 = 0; | 
| 354 |  |  |  |  |  |  | } | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  | if ($check_md5) { | 
| 357 |  |  |  |  |  |  | $md5->add($$dataref); | 
| 358 |  |  |  |  |  |  | } | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | $offset += length($$dataref); | 
| 361 |  |  |  |  |  |  | $appender->($$dataref); | 
| 362 |  |  |  |  |  |  | last unless $bytesleft; | 
| 363 |  |  |  |  |  |  | }; | 
| 364 |  |  |  |  |  |  | if ($check_md5) { | 
| 365 |  |  |  |  |  |  | my $got_md5 = $md5->hexdigest; | 
| 366 |  |  |  |  |  |  | confess "MD5 missmatch, got $got_md5, expected $check_md5" unless $got_md5 eq $check_md5; | 
| 367 |  |  |  |  |  |  | } | 
| 368 |  |  |  |  |  |  | 1; | 
| 369 |  |  |  |  |  |  | } | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | =head2 size | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | ÐозвÑаÑÐ°ÐµÑ ÑÐ°Ð·Ð¼ÐµÑ Ð¾Ð±ÑекÑа Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ $key в байÑаÑ
, | 
| 374 |  |  |  |  |  |  | еÑли клÑÑ Ð½Ðµ ÑÑÑеÑÑвÑеÑ, возвÑаÑÐ°ÐµÑ undef | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | =cut | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | sub size { | 
| 379 |  |  |  |  |  |  | my ($self, $key) = @_; | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | confess "Bucket name is required" unless $self->{bucket}; | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | _check_ascii_key($key); | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | $self->{driver}->size($key); | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | =head2 delete | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | УдалÑÐµÑ Ð¾Ð±ÑÐµÐºÑ Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ $key, ниÑего не возвÑаÑаеÑ. ÐÑли обÑÐµÐºÑ | 
| 391 |  |  |  |  |  |  | не ÑÑÑеÑÑвÑеÑ, не вÑдаÑÑ Ð¾ÑÐ¸Ð±ÐºÑ | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | =cut | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | sub delete { | 
| 396 |  |  |  |  |  |  | my ($self, $key) = @_; | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | confess "Bucket name is required" unless $self->{bucket}; | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | _check_ascii_key($key); | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | $self->{driver}->delete($key); | 
| 403 |  |  |  |  |  |  | } | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | =head2 query_string_authentication_uri | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | ÐозвÑаÑÐ°ÐµÑ Query String Authentication URL Ð´Ð»Ñ ÐºÐ»ÑÑа $key, Ñ ÑкÑпайÑом $expires | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | $expires - epoch вÑемÑ. но низкоÑÑÐ¾Ð²Ð½ÐµÐ²Ð°Ñ Ð±Ð¸Ð±Ð»Ð¸Ð¾Ñека Ð¼Ð¾Ð¶ÐµÑ Ð¿ÑинимаÑÑ Ð´ÑÑгие ÑоÑмаÑÑ. ÑбедиÑеÑÑ | 
| 410 |  |  |  |  |  |  | ÑÑо вÑ
однÑе даннÑе валидиÑÐ¾Ð²Ð°Ð½Ñ Ð¸ Ð²Ñ Ð¿ÐµÑедаÑÑе именно epoch | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | ÐаменÑÐµÑ Ñ
оÑÑ, еÑли еÑÑÑ Ð¾Ð¿ÑÐ¸Ñ query_string_authentication_host_replace (Ñм. конÑÑÑÑкÑоÑ) | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | =cut | 
| 415 |  |  |  |  |  |  |  | 
| 416 |  |  |  |  |  |  | sub query_string_authentication_uri { | 
| 417 |  |  |  |  |  |  | my ($self, $key, $expires) = @_; | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | _check_ascii_key($key); | 
| 420 |  |  |  |  |  |  | $expires or confess "Missing expires"; | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | my $uri = $self->{driver}->query_string_authentication_uri($key, $expires); | 
| 423 |  |  |  |  |  |  | if ($self->{query_string_authentication_host_replace}) { | 
| 424 |  |  |  |  |  |  | my $replace = $self->{query_string_authentication_host_replace}; | 
| 425 |  |  |  |  |  |  | $replace .= '/' unless $replace =~ m!/$!; | 
| 426 |  |  |  |  |  |  | $uri =~ s!^https?://[^/]+/!$replace!; | 
| 427 |  |  |  |  |  |  | } | 
| 428 |  |  |  |  |  |  | $uri; | 
| 429 |  |  |  |  |  |  | } | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | =head2 get_buckets_list | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | Returns buckets list | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | WARNING | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | ÐеÑод Ð¿Ð°Ð´Ð°ÐµÑ c оÑибкой | 
| 438 |  |  |  |  |  |  | Attribute (owner_id) does not pass the type constraint because: Validation failed for 'OwnerId' | 
| 439 |  |  |  |  |  |  | Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð½Ð°Ð¿ÑÐ°Ð²Ð»ÐµÐ½Ñ ÑазÑабÑÑикам: | 
| 440 |  |  |  |  |  |  | http://tracker.ceph.com/issues/16806 и https://github.com/rustyconover/net-amazon-s3/issues/18 | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  | =cut | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | sub get_buckets_list { | 
| 445 |  |  |  |  |  |  | my ($self) = @_; | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | return $self->{driver}->get_buckets_list; | 
| 448 |  |  |  |  |  |  | } | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | =head2 list_multipart_uploads | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | ÐозвÑаÑÐ°ÐµÑ ÑпиÑок multipart загÑÑзок в бакеÑе | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | =cut | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | sub list_multipart_uploads { | 
| 457 |  |  |  |  |  |  | my ($self) = @_; | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | confess "Bucket name is required" unless $self->{bucket}; | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | return $self->{driver}->list_multipart_uploads(); | 
| 462 |  |  |  |  |  |  | } | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | =head2 delete_multipart_upload | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | УдалÑÐµÑ multipart загÑÑÐ·ÐºÑ Ð² бакеÑе | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | ÐаÑамеÑÑÑ Ð¿Ð¾Ð·Ð¸ÑионнÑе: $key, $upload_id | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | ÐиÑего не возвÑаÑÐ°ÐµÑ | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | =cut | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | sub delete_multipart_upload { | 
| 475 |  |  |  |  |  |  | my ( $self, $key, $upload_id ) = @_; | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | confess "Bucket name is required" unless $self->{bucket}; | 
| 478 |  |  |  |  |  |  | confess "key and upload ID is required" unless $key && $upload_id; | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | $self->{driver}->delete_multipart_upload($key, $upload_id); | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | 1; |