File Coverage

blib/lib/WebService/CEPH/FileShadow.pm
Criterion Covered Total %
statement 24 24 100.0
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 32 32 100.0


line stmt bran cond sub pod time code
1             =encoding utf8
2              
3              
4              
5             =head1 WebService::CEPH::FileShadow
6              
7             Потомок WebService::CEPH.
8              
9             Опции конструктора те же самые, что и у WebService::CEPH, плюс ещё есть:
10              
11             mode - 's3' или 's3-fs' или 'fs'
12              
13             fs_shadow_path - путь к файловой системе, указывает на директорию, финальный слэш не обязателен.
14              
15             Ð’ режиме s3 всё работает, как WebService::CEPH, файлы скачиваются и закачиваются в CEPH по протоколу s3.
16             Ð’ режиме 's3-fs' при закачке файла, создаётся его копия в файловой системе. Сначала файл закачивается в s3, потом
17             в файловую систему, если в это время случится исключение, предыдущий шаг не отменяется.
18             Скачивание файлов происходит с 's3', при ошибке скачивания, никакого фейловера на файловую систему не производится.
19             Ð’ режиме 'fs' закачивание и скачивание файлов происходит только в файловую систему.
20              
21             Метаинформация (x-amz-meta-md5, content-type в файловой системе не сохраняется).
22              
23             Ð’ методах download_to_file, upload_from_file в режиме файловой системы, работа с локальными файлами
24             делается максимально совместимо с режимом 's3' (права (umask) при создании файла, режимы truncate и seek при работе
25             с filehandles)
26              
27             Имя ключа объекта не должно содержать символов, опасных для файловой системы (например '../', '/..') иначе
28             будет исключение. Однако по-настоящему заботится о безопасности должен вызывающий.
29              
30             =cut
31              
32             package WebService::CEPH::FileShadow;
33              
34             our $VERSION = '0.015'; # VERSION
35              
36 1     1   139532 use strict;
  1         9  
  1         23  
37 1     1   5 use warnings;
  1         1  
  1         19  
38 1     1   4 use Carp;
  1         2  
  1         41  
39 1     1   4 use Fcntl qw/:seek/;
  1         2  
  1         82  
40 1     1   250 use File::Copy;
  1         1670  
  1         46  
41 1     1   308 use File::Slurp qw/read_file/;
  1         4442  
  1         55  
42 1     1   7 use File::Path qw(make_path);
  1         1  
  1         41  
43 1     1   5 use parent qw( WebService::CEPH );
  1         2  
  1         7  
44              
45              
46             sub new {
47             my ($class, %options) = @_;
48             my %new_options;
49              
50             $new_options{$_} = delete $options{$_} for (qw/fs_shadow_path mode/);
51              
52             !defined or m!/$! or $_ .= '/' for $new_options{fs_shadow_path};
53             confess "mode should be 's3', 's3-fs' or 'fs', but it is '$new_options{mode}'"
54             unless $new_options{mode} =~ /^(s3|s3\-fs|fs)$/;
55              
56             confess "you specified mode to work with filesystem ($new_options{mode}), please define fs_shadow_path then"
57             if $new_options{mode} =~ /fs/ && !$new_options{fs_shadow_path};
58              
59             my $self = $class->SUPER::new(%options);
60              
61             $self->{$_} = $new_options{$_} for keys %new_options;
62              
63             $self;
64             }
65              
66             sub _filepath {
67             my ($self, $key, $should_mkpath) = @_;
68             confess "key expected" unless defined $key;
69             confess "unsecure key" if $key eq '.';
70             confess "unsecure key" if $key =~ m!\.\./!;
71             confess "unsecure key" if $key =~ m!/\.\.!;
72             confess "constructor should normalize path" unless $self->{fs_shadow_path} =~ m!/$!;
73             my $dir = $self->{fs_shadow_path}.$self->{bucket}."/";
74             make_path($dir) if ($should_mkpath);
75             $dir.$key;
76             }
77             sub upload {
78             my ($self, $key) = (shift, shift);
79              
80             if ($self->{mode} =~ /s3/) {
81             $self->SUPER::upload($key, $_[0], $_[1]);
82             }
83             if ($self->{mode} =~ /fs/) {
84             my $path = $self->_filepath($key, 1);
85             open my $f, ">", $path or confess;
86             binmode $f;
87             print $f $_[0] or confess;
88             close $f or confess;
89             }
90             }
91              
92             sub upload_from_file {
93             my ($self, $key, $fh_or_filename, $content_type) = @_;
94              
95             if ($self->{mode} =~ /s3/) {
96             $self->SUPER::upload_from_file($key, $fh_or_filename, $content_type);
97             }
98             if ($self->{mode} =~ /fs/) {
99             my $path = $self->_filepath($key, 1);
100             seek($fh_or_filename, 0, SEEK_SET) if (ref $fh_or_filename);
101             copy($fh_or_filename, $path);
102             }
103             }
104              
105             sub download {
106             my ($self, $key) = @_;
107              
108             if ($self->{mode} =~ /s3/) {
109             return $self->SUPER::download($key);
110             }
111             elsif ($self->{mode} =~ /fs/) {
112             return scalar read_file( $self->_filepath($key), binmode => ':raw' )
113             }
114             }
115              
116             sub download_to_file {
117             my ($self, $key, $fh_or_filename) = @_;
118              
119             if ($self->{mode} =~ /s3/) {
120             $self->SUPER::download_to_file($key, $fh_or_filename);
121             }
122             elsif ($self->{mode} =~ /fs/) {
123             copy( $self->_filepath($key), $fh_or_filename );
124             }
125             }
126              
127             sub size {
128             my ($self, $key) = @_;
129              
130             if ($self->{mode} =~ /s3/) {
131             return $self->SUPER::size($key);
132             }
133             elsif ($self->{mode} =~ /fs/) {
134             return -s $self->_filepath($key);
135             }
136             }
137              
138             sub delete {
139             my ($self, $key) = @_;
140              
141             if ($self->{mode} =~ /s3/) {
142             $self->SUPER::delete($key);
143             }
144             elsif ($self->{mode} =~ /fs/) {
145             unlink($self->_filepath($key));
146             }
147             }
148              
149             sub query_string_authentication_uri {
150             my ($self, $key, $expires) = @_;
151              
152             if ($self->{mode} =~ /s3/) {
153             $self->SUPER::query_string_authentication_uri($key, $expires);
154             }
155             else {
156             confess "Unimplemented in fs mode";
157             }
158             }
159              
160             1;