File Coverage

blib/lib/Catmandu/Store/File/Simple/Bag.pm
Criterion Covered Total %
statement 56 57 98.2
branch 3 4 75.0
condition n/a
subroutine 16 17 94.1
pod 0 5 0.0
total 75 83 90.3


line stmt bran cond sub pod time code
1             package Catmandu::Store::File::Simple::Bag;
2              
3 8     8   61 use Catmandu::Sane;
  8         87  
  8         54  
4              
5             our $VERSION = '1.14';
6              
7 8     8   1531 use Moo;
  8         17  
  8         50  
8 8     8   3045 use Carp;
  8         29  
  8         524  
9 8     8   56 use IO::File;
  8         15  
  8         1255  
10 8     8   60 use Path::Tiny;
  8         16  
  8         377  
11 8     8   54 use File::Spec;
  8         25  
  8         261  
12 8     8   3971 use File::Copy;
  8         18879  
  8         497  
13 8     8   68 use Catmandu::Util qw(content_type);
  8         19  
  8         367  
14 8     8   3954 use URI::Escape;
  8         11542  
  8         502  
15 8     8   60 use namespace::clean;
  8         16  
  8         50  
16              
17             with 'Catmandu::Bag';
18             with 'Catmandu::FileBag';
19             with 'Catmandu::Droppable';
20              
21             has _path => (is => 'lazy');
22              
23             sub _build__path {
24 44     44   363 my $self = shift;
25 44         702 $self->store->directory_index->add($self->name)->{_path};
26             }
27              
28             sub generator {
29 13     13 0 2281 my ($self) = @_;
30 13         247 my $path = $self->_path;
31              
32             sub {
33 32     32   522 state $children = [path($path)->children];
34              
35 32         2292 my $child = shift @$children;
36              
37 32 100       276 return undef unless $child;
38              
39 19         181 my ($volume, $directories, $file) = File::Spec->splitpath($child);
40              
41 19 50       252 next if index($file, ".") == 0;
42              
43 19         59 my $unpacked_key = $self->unpack_key($file);
44              
45 19         405 return $self->get($unpacked_key);
46 13         145 };
47             }
48              
49             sub exists {
50 4     4 0 2903 my ($self, $id) = @_;
51 4         87 my $path = $self->_path;
52              
53 4         37 my $packed_key = $self->pack_key($id);
54              
55 4         76 my $file = File::Spec->catfile($path, $packed_key);
56              
57 4         93 -f $file;
58             }
59              
60             sub get {
61             my ($self, $id) = @_;
62             my $path = $self->_path;
63              
64             my $packed_key = $self->pack_key($id);
65              
66             my $file = File::Spec->catfile($path, $packed_key);
67              
68             return undef unless -f $file;
69              
70             my $stat = [stat($file)];
71              
72             my $size = $stat->[7];
73             my $modified = $stat->[9];
74             my $created = $stat->[10]; # no real creation time exists on Unix
75              
76             my $content_type = content_type($id);
77              
78             return {
79             _id => $id,
80             size => $size,
81             md5 => '',
82             content_type => $content_type,
83             created => $created,
84             modified => $modified,
85             _stream => sub {
86             my $out = $_[0];
87             my $bytes = 0;
88             my $data = IO::File->new($file, "r")
89             || Catmandu::Error->throw("$file not readable");
90              
91             Catmandu::Error->throw("no io defined or not writable")
92             unless defined($out);
93              
94             while (!$data->eof) {
95             my $buffer;
96             $data->read($buffer, 1024);
97              
98             my $n = $out->syswrite($buffer);
99              
100             if (!defined($n) && $!{EAGAIN}) {
101             # would block
102             $n = 0;
103             }
104             elsif ($n != length $buffer) {
105             $self->log->error("incomplete write");
106             }
107             else {
108             # all is ok
109             }
110              
111             $bytes += $n;
112             }
113              
114             $out->close();
115             $data->close();
116              
117             $bytes;
118             }
119             };
120             }
121              
122             sub add {
123             my ($self, $data) = @_;
124             my $path = $self->_path;
125              
126             my $id = $data->{_id};
127             my $io = $data->{_stream};
128              
129             return $self->get($id) unless $io;
130              
131             my $packed_key = $self->pack_key($id);
132              
133             my $file = File::Spec->catfile($path, $packed_key);
134              
135             if (Catmandu::Util::is_invocant($io)) {
136             copy($io, $file)
137             || Catmandu::Error->throw("failed to write file : $!");
138             }
139             else {
140             Catmandu::Util::write_file($file, $io)
141             || Catmandu::Error->throw("failed to write file : $!");
142             }
143              
144             my $new_data = $self->get($id);
145              
146             $data->{$_} = $new_data->{$_} for keys %$new_data;
147              
148             1;
149             }
150              
151             sub delete {
152             my ($self, $id) = @_;
153             my $path = $self->_path;
154              
155             my $packed_key = $self->pack_key($id);
156              
157             my $file = File::Spec->catfile($path, $packed_key);
158              
159             return undef unless -f $file;
160              
161             unlink $file;
162             }
163              
164             sub delete_all {
165             my ($self) = @_;
166              
167             $self->each(
168             sub {
169             my $key = shift->{_id};
170             $self->delete($key);
171             }
172             );
173              
174             1;
175             }
176              
177             sub drop {
178 0     0 0 0 $_[0]->delete_all;
179             }
180              
181             sub commit {
182             return 1;
183             }
184              
185             sub pack_key {
186 93     93 0 159 my $self = shift;
187 93         140 my $key = shift;
188 93         288 utf8::encode($key);
189 93         270 uri_escape($key);
190             }
191              
192             sub unpack_key {
193 19     19 0 33 my $self = shift;
194 19         32 my $key = shift;
195 19         59 my $str = uri_unescape($key);
196 19         179 utf8::decode($str);
197 19         33 $str;
198             }
199              
200             1;
201              
202             __END__
203              
204             =pod
205              
206             =head1 NAME
207              
208             Catmandu::Store::File::Simple::Bag - Index of all "files" in a Catmandu::Store::File::Simple "folder"
209              
210             =head1 SYNOPSIS
211              
212             use Catmandu;
213              
214             my $store = Catmandu->store('File::Simple' , root => 't/data');
215              
216             my $index = $store->index;
217              
218             # List all containers
219             $index->each(sub {
220             my $container = shift;
221              
222             print "%s\n" , $container->{_id};
223             });
224              
225             # Add a new folder
226             $index->add({_id => '1234'});
227              
228             # Delete a folder
229             $index->delete(1234);
230              
231             # Get a folder
232             my $folder = $index->get(1234);
233              
234             # Get the files in an folder
235             my $files = $index->files(1234);
236              
237             $files->each(sub {
238             my $file = shift;
239              
240             my $name = $file->{_id};
241             my $size = $file->{size};
242             my $content_type = $file->{content_type};
243             my $created = $file->{created};
244             my $modified = $file->{modified};
245              
246             $file->stream(IO::File->new(">/tmp/$name"), file);
247             });
248              
249             # Add a file
250             $files->upload(IO::File->new("<data.dat"),"data.dat");
251              
252             # Retrieve a file
253             my $file = $files->get("data.dat");
254              
255             # Stream a file to an IO::Handle
256             $files->stream(IO::File->new(">data.dat"),$file);
257              
258             # Delete a file
259             $files->delete("data.dat");
260              
261             # Delete a folders
262             $index->delete("1234");
263              
264             =head1 DESCRIPTION
265              
266             A L<Catmandu::Store::File::Simple::Bag> contains all "files" available in a
267             L<Catmandu::Store::File::Simple> FileStore "folder". All methods of L<Catmandu::Bag>,
268             L<Catmandu::FileBag::Index> and L<Catmandu::Droppable> are
269             implemented.
270              
271             Every L<Catmandu::Bag> is also an L<Catmandu::Iterable>.
272              
273             =head1 FOLDERS
274              
275             All files in a L<Catmandu::Store::File::Simple> are organized in "folders". To add
276             a "folder" a new record needs to be added to the L<Catmandu::Store::File::Simple::Index> :
277              
278             $index->add({_id => '1234'});
279              
280             The C<_id> field is the only metadata available in Simple stores. To add more
281             metadata fields to a Simple store a L<Catmandu::Plugin::SideCar> is required.
282              
283             =head1 FILES
284              
285             Files can be accessed via the "folder" identifier:
286              
287             my $files = $index->files('1234');
288              
289             Use the C<upload> method to add new files to a "folder". Use the C<download> method
290             to retrieve files from a "folder".
291              
292             $files->upload(IO::File->new("</tmp/data.txt"),'data.txt');
293              
294             my $file = $files->get('data.txt');
295              
296             $files->download(IO::File->new(">/tmp/data.txt"),$file);
297              
298             =head1 INHERITED METHODS
299              
300             This Catmandu::Bag implements:
301              
302             =over 3
303              
304             =item L<Catmandu::Bag>
305              
306             =item L<Catmandu::FileBag>
307              
308             =item L<Catmandu::Droppable>
309              
310             =back
311              
312             =cut