File Coverage

blib/lib/Catmandu/BagIt.pm
Criterion Covered Total %
statement 645 679 94.9
branch 160 202 79.2
condition 33 58 56.9
subroutine 85 85 100.0
pod 26 30 86.6
total 949 1054 90.0


line stmt bran cond sub pod time code
1             package Catmandu::BagIt;
2              
3             our $VERSION = '0.240';
4              
5 7     7   249135 use Catmandu::Sane;
  7         357821  
  7         45  
6 7     7   3594 use Catmandu;
  7         428237  
  7         40  
7 7     7   1701 use Moo;
  7         15  
  7         39  
8 7     7   6208 use Encode;
  7         90712  
  7         581  
9 7     7   59 use Digest::MD5;
  7         14  
  7         242  
10 7     7   42 use IO::File qw();
  7         16  
  7         128  
11 7     7   37 use IO::Handle qw();
  7         14  
  7         140  
12 7     7   3425 use File::Copy;
  7         16462  
  7         499  
13 7     7   4027 use List::MoreUtils qw(first_index uniq);
  7         61677  
  7         64  
14 7     7   8711 use Path::Tiny;
  7         17  
  7         345  
15 7     7   2484 use Path::Iterator::Rule;
  7         37692  
  7         202  
16 7     7   3722 use Catmandu::BagIt::Payload;
  7         22  
  7         239  
17 7     7   3143 use Catmandu::BagIt::Fetch;
  7         28  
  7         265  
18 7     7   49 use POSIX qw(strftime);
  7         15  
  7         102  
19 7     7   8816 use LWP::UserAgent;
  7         199564  
  7         274  
20 7     7   57 use utf8;
  7         16  
  7         61  
21 7     7   202 use Catmandu::Util qw(is_string);
  7         17  
  7         419  
22 7     7   49 use namespace::clean;
  7         14  
  7         72  
23              
24             # Flags indicating which operations are needed to create a valid bag
25             use constant {
26 7         68454 FLAG_BAGIT => 0x001 , # Flag indicates updating the bagit.txt file required
27             FLAG_BAG_INFO => 0x002 , # Flag indicates updating the bag-info.txt file required
28             FLAG_FETCH => 0x004 , # Flag indicates updating the fetch.txt file required
29             FLAG_DATA => 0x008 , # Flag indicating new payload data available
30             FLAG_TAG_MANIFEST => 0x016 , # Flag indicates updateing tag-manifest-manifest.txt required
31             FLAG_MANIFEST => 0x032 , # Flag indicates updating manifest-md5.txt required
32             FLAG_DIRTY => 0x064 , # Flag indicates payload file that hasn't been serialized
33 7     7   3910 };
  7         21  
34              
35             with 'Catmandu::Logger';
36              
37             # Array containing all errors when reading/writing bags
38             has '_error' => (
39             is => 'rw',
40             default => sub { [] },
41             );
42              
43             # Integer containing a combinatation of FLAG_* set for this bag
44             has 'dirty' => (
45             is => 'ro',
46             writer => '_dirty',
47             default => 0,
48             );
49              
50             # Path to a directory containing a bag
51             has 'path' => (
52             is => 'ro',
53             writer => '_path',
54             init_arg => undef,
55             );
56              
57             # Version number of the bag specification
58             has 'version' => (
59             is => 'ro',
60             writer => '_version',
61             default => '0.97',
62             init_arg => undef,
63             );
64              
65             # Encoding of all tag manifests
66             has 'encoding' => (
67             is => 'ro',
68             writer => '_encoding',
69             default => 'UTF-8',
70             init_arg => undef,
71             );
72              
73             # User agent used to fetch payloads from the Internet
74             has user_agent => (is => 'lazy');
75              
76             # An array of a tag file names
77             has '_tags' => (
78             is => 'rw',
79             default => sub { [] },
80             init_arg => undef,
81             );
82              
83             # An array of Catmandu::BagIt::Payloads
84             has '_files' => (
85             is => 'rw',
86             default => sub { [] },
87             init_arg => undef,
88             );
89              
90             # An array of Catmandu::BagIt::Fetch
91             has '_fetch' => (
92             is => 'rw',
93             default => sub { [] },
94             init_arg => undef,
95             );
96              
97             # A lookup hash of md5 checksums for the tag files
98             has '_tag_sums' => (
99             is => 'rw',
100             default => sub { {} },
101             init_arg => undef,
102             );
103              
104             # A lookup hahs of md5 checksums for the payload files
105             has '_sums' => (
106             is => 'rw',
107             default => sub { {} },
108             init_arg => undef,
109             );
110              
111             # An array of hashes of all name/value pairs in the bag-info.txt file
112             has '_info' => (
113             is => 'rw',
114             default => sub { [] },
115             init_arg => undef,
116             );
117              
118             sub _build_user_agent {
119 1     1   14 my ($self) = @_;
120 1         20 my $ua = LWP::UserAgent->new;
121 1         2948 $ua->agent('Catmandu-BagIt/' . $Catmandu::BagIt::VERSION);
122 1         71 $ua;
123             }
124              
125             # Settings requires when creating a new bag from scratch
126             sub BUILD {
127 26     26 0 327 my $self = shift;
128              
129 26         460 $self->log->debug("initializing bag");
130              
131             # Intialize the in memory settings of the bag-info
132 26         5829 $self->_update_info;
133              
134             # Initialize the in memory settings of the tag-manifests
135 26         100 $self->_update_tag_manifest;
136              
137             # Intialize the names of the basic tag files
138 26         102 $self->_tags([qw(
139             bagit.txt
140             bag-info.txt
141             manifest-md5.txt
142             )]);
143              
144             # Set this bag as dirty requiring an update of all the files
145 26         198 $self->_dirty($self->dirty | FLAG_BAG_INFO | FLAG_TAG_MANIFEST | FLAG_DATA | FLAG_BAGIT);
146             }
147              
148             # Return all the arrors as an array
149             sub errors {
150 26     26 0 54 my ($self) = @_;
151 26         46 @{$self->_error};
  26         293  
152             }
153              
154             # Return an array of tag file names
155             sub list_tags {
156 39     39 0 64 my ($self) = @_;
157 39         66 @{$self->_tags};
  39         119  
158             }
159              
160             # Return an array of all Catmandu::BagIt::Payload-s
161             sub list_files {
162 273     273 1 744 my ($self) = @_;
163 273         387 @{$self->_files};
  273         764  
164             }
165              
166             # Return a Catmandu::BagIt::Payload given a file name
167             sub get_file {
168 23     23 1 2179 my ($self,$filename) = @_;
169 23 100       62 die "usage: get_file(filename)" unless $filename;
170              
171 22         50 for ($self->list_files) {
172 32 100       118 return $_ if $_->filename eq $filename;
173             }
174 1         5 return undef;
175             }
176              
177             # Return a Catmandu::BagIt::Fetch given a file name
178             sub get_fetch {
179 3     3 1 377 my ($self,$filename) = @_;
180 3 100       20 die "usage: get_fetch(filename)" unless $filename;
181              
182 2         6 for ($self->list_fetch) {
183 2 100       11 return $_ if $_->filename eq $filename;
184             }
185 1         6 return undef;
186             }
187              
188             # Return true when this bag is dirty
189             sub is_dirty {
190 16     16 1 919 my ($self) = @_;
191 16         90 $self->dirty != 0;
192             }
193              
194             # Return true when this bag is holey (and requires fetching data from the Internet
195             # to be made complete)
196             sub is_holey {
197 3     3 1 8 my ($self) = @_;
198 3         7 @{$self->_fetch} > 0;
  3         19  
199             }
200              
201             # Return an array of Catmandu::BagIt::Fetch
202             sub list_fetch {
203 352     352 1 961 my ($self) = @_;
204 352         479 @{$self->_fetch};
  352         1008  
205             }
206              
207             # Return an array of tag file
208             sub list_tagsum {
209 50     50 1 1228 my ($self) = @_;
210 50         71 keys %{$self->_tag_sums};
  50         277  
211             }
212              
213             # Return the md5 checksum of a file
214             sub get_tagsum {
215 119     119 1 1201 my ($self,$file) = @_;
216              
217 119 100       286 die "usage: get_tagsum(file)" unless $file;
218              
219 118         371 $self->_tag_sums->{$file};
220             }
221              
222             # Return an array of payload files
223             sub list_checksum {
224 92     92 1 479 my ($self) = @_;
225 92         126 keys %{$self->_sums};
  92         448  
226             }
227              
228             # Return the md5 checksum of of a file name
229             sub get_checksum {
230 195     195 1 461 my ($self,$file) = @_;
231              
232 195 100       381 die "usage: get_checksum(file)" unless $file;
233              
234 194         909 $self->_sums->{$file};
235             }
236              
237             # Read the content of a bag
238             sub read {
239 11     11 1 4293 my ($class,$path) = @_;
240              
241 11 100       49 die "usage: read(path)" unless $path;
242              
243 10         274 my $self = $class->new;
244              
245 10 100       221 if (! -d $path ) {
246 1         28 $self->log->error("$path doesn't exist");
247 1         18 $self->_push_error("$path doesn't exist");
248 1         10 return;
249             }
250              
251 9         250 $self->log->info("reading: $path");
252              
253 9         155 $self->_path($path);
254              
255 9         22 my $ok = 0;
256              
257 9         40 $ok += $self->_read_version($path);
258 9         40 $ok += $self->_read_info($path);
259 9         37 $ok += $self->_read_tag_manifest($path);
260 9         35 $ok += $self->_read_manifest($path);
261 9         37 $ok += $self->_read_tags($path);
262 9         41 $ok += $self->_read_files($path);
263 9         95 $ok += $self->_read_fetch($path);
264              
265 9         96 $self->_dirty(0);
266              
267 9 50       32 if ( wantarray ) {
268 0 0       0 return $ok == 7 ? ($self) : (undef, $self->errors);
269             }
270             else {
271 9 100       64 return $ok == 7 ? $self : undef;
272             }
273             }
274              
275             # Write the content of a bag back to disk
276             sub write {
277 25     25 1 1376 my ($self,$path,%opts) = @_;
278              
279 25         89 $self->_error([]);
280              
281 25 100       88 die "usage: write(path[, overwrite => 1])" unless $path;
282              
283             # Check if other processes are writing or previous processes died
284 24 50       75 if ($self->locked($path)) {
285 0         0 $self->log->error("$path is locked");
286 0         0 $self->_push_error("$path is locked");
287 0         0 return undef;
288             }
289              
290 24 100 100     408 if (defined($self->path) && $path ne $self->path) {
    100 66        
    100 100        
291             # If the bag is copied from to a new location than all the tag files and
292             # files should be flagged as dirty and need to be overwritten
293 1         28 $self->log->info("copying from old path: " . $self->path);
294 1         22 $self->_dirty($self->dirty | FLAG_BAGIT | FLAG_BAG_INFO | FLAG_TAG_MANIFEST | FLAG_MANIFEST | FLAG_DATA);
295              
296 1         3 foreach my $item ($self->list_files) {
297 0         0 $item->flag($item->flag ^ FLAG_DIRTY);
298             }
299             }
300             elsif (defined($self->path) && $path eq $self->path) {
301             # we are ok the path exists and don't need to remove anything
302             # updates are possible when overwrite => 1
303             }
304             elsif ($opts{overwrite} && -d $path) {
305             # Remove existing bags
306 4         113 $self->log->info("removing: $path");
307 4         70 path($path)->remove_tree;
308             }
309              
310 24 100       2749 if (-f $self->_bagit_file($path)) {
311 14 100       56 if ($opts{overwrite}) {
312 13         348 $self->log->info("overwriting: $path");
313             }
314             else {
315 1         28 $self->log->error("$path already exists");
316 1         18 $self->_push_error("$path already exists");
317 1         7 return undef;
318             }
319             }
320             else {
321 10         294 $self->log->info("creating: $path");
322 10         159 path($path)->mkpath;
323 10         2284 $self->_dirty($self->dirty | FLAG_BAGIT);
324             }
325              
326 23 50       231 unless ($self->touch($self->_lock_file($path))) {
327 0         0 $self->log->error("failed to lock in $path");
328 0         0 return undef;
329             }
330              
331 23         126 $self->_path($path);
332              
333 23         49 my $ok = 0;
334              
335 23         88 $ok += $self->_write_bagit($path);
336 23         88 $ok += $self->_write_info($path);
337 23         88 $ok += $self->_write_data($path);
338 23         81 $ok += $self->_write_fetch($path);
339 23         139 $ok += $self->_write_manifest($path);
340 23         81 $ok += $self->_write_tag_manifest($path);
341              
342 23 50       80 return undef unless $ok == 6;
343              
344 23         54 $self->_dirty(0);
345              
346 23         67 unlink($self->_lock_file($path));
347              
348 23         106 $ok = 0;
349              
350             # Reread the contents of the bag
351 23         99 $ok += $self->_read_version($path);
352 23         88 $ok += $self->_read_info($path);
353 23         93 $ok += $self->_read_tag_manifest($path);
354 23         80 $ok += $self->_read_manifest($path);
355 23         82 $ok += $self->_read_tags($path);
356 23         91 $ok += $self->_read_files($path);
357 23         105 $ok += $self->_read_fetch($path);
358              
359 23         182 $ok == 7;
360             }
361              
362             sub _bagit_file {
363 97     97   219 my ($self,$path) = @_;
364              
365 97         1796 File::Spec->catfile($path,'bagit.txt');
366             }
367              
368             sub _bag_info_file {
369 86     86   226 my ($self,$path) = @_;
370              
371 86         1554 File::Spec->catfile($path,'bag-info.txt');
372             }
373              
374             sub _package_info_file {
375 1     1   12 my ($self,$path) = @_;
376              
377 1         11 File::Spec->catfile($path,'package-info.txt');
378             }
379              
380             sub _manifest_md5_file {
381 86     86   183 my ($self,$path) = @_;
382              
383 86         1515 File::Spec->catfile($path,'manifest-md5.txt');
384             }
385              
386             sub _tagmanifest_md5_file {
387 86     86   175 my ($self,$path) = @_;
388              
389 86         1453 File::Spec->catfile($path,'tagmanifest-md5.txt');
390             }
391              
392             sub _fetch_file {
393 61     61   131 my ($self,$path) = @_;
394              
395 61         1577 File::Spec->catfile($path,'fetch.txt');
396             }
397              
398             sub _tag_file {
399 31     31   72 my ($self,$path,$file) = @_;
400              
401 31         458 File::Spec->catfile($path,$file);
402             }
403              
404             sub _payload_file {
405 130     130   265 my ($self,$path,$file) = @_;
406              
407 130         2640 File::Spec->catfile($path,'data',$file);
408             }
409              
410             sub _lock_file {
411 72     72   153 my ($self,$path) = @_;
412              
413 72         2273 File::Spec->catfile($path,'.lock');
414             }
415              
416             sub locked {
417 26     26 1 63 my ($self,$path) = @_;
418 26   66     78 $path //= $self->path;
419              
420 26 50       65 return undef unless defined($path);
421              
422 26         68 -f $self->_lock_file($path);
423             }
424              
425             sub touch {
426 24     24 0 59 my ($self,$path) = @_;
427              
428 24 50       67 die "usage: touch(path)"
429             unless defined($path);
430              
431 24         95 path("$path")->spew("");
432              
433 24         9816 1;
434             }
435              
436             sub add_file {
437 22     22 1 3134 my ($self, $filename, $data, %opts) = @_;
438              
439 22 50 33     162 die "usage: add_file(filename, data [, overwrite => 1])"
440             unless defined($filename) && defined($data);
441              
442 22         95 $self->_error([]);
443              
444 22 100       77 unless ($self->_is_legal_file_name($filename)) {
445 2         47 $self->log->error("illegal file name $filename");
446 2         31 $self->_push_error("illegal file name $filename");
447 2         9 return;
448             }
449              
450 20         465 $self->log->info("adding file $filename");
451              
452 20 100       282 if ($opts{overwrite}) {
453 7         81 $self->remove_file($filename);
454             }
455              
456 20 100       78 if ($self->get_checksum("$filename")) {
457 1         18 $self->log->error("$filename already exists in bag");
458 1         16 $self->_push_error("$filename already exists in bag");
459 1         6 return;
460             }
461              
462 19         164 my $payload = Catmandu::BagIt::Payload->from_any($filename,$data);
463 19         737 $payload->flag(FLAG_DIRTY);
464              
465 19         41 my $sum;
466              
467 19 100 100     239 if( is_string( $opts{md5} ) && $opts{md5} !~ /^[0-9a-f]{32}$/ ){
    100          
468              
469 1         21 $self->log->error("supplied md5 sum for $filename does not look like an md5 sum");
470 1         21 $self->_push_error("supplied md5 sum for $filename does not look like an md5 sum");
471 1         8 return;
472              
473             }
474             elsif( is_string( $opts{md5} ) ){
475              
476 1         5 $sum = $opts{md5};
477              
478             }
479             else {
480              
481 17         76 my $fh = $payload->open;
482              
483 17         1498 binmode($fh,":raw");
484              
485 17         65 $sum = $self->_md5_sum($fh);
486              
487 17         215 close($fh);
488              
489             }
490              
491 18         60 push @{ $self->_files }, $payload;
  18         86  
492              
493 18         91 $self->_sums->{"$filename"} = $sum;
494              
495             # Total size changes, therefore tag manifest changes
496 18         70 $self->_update_info;
497 18         58 $self->_update_tag_manifest; # Try to update the manifest .. but it is dirty
498             # Until we serialize the bag
499              
500 18         86 $self->_dirty($self->dirty | FLAG_DATA | FLAG_MANIFEST | FLAG_BAG_INFO | FLAG_TAG_MANIFEST);
501              
502 18         112 1;
503             }
504              
505             sub remove_file {
506 14     14 1 875 my ($self, $filename) = @_;
507              
508 14 50       36 die "usage: remove_file(filename)" unless defined($filename);
509              
510 14         49 $self->_error([]);
511              
512 14 100       45 unless ($self->get_checksum($filename)) {
513 5         103 $self->log->error("$filename doesn't exist in bag");
514 5         79 $self->_push_error("$filename doesn't exist in bag");
515 5         17 return;
516             }
517              
518 9         193 $self->log->info("removing file $filename");
519              
520 9     9   142 my $idx = first_index { $_->{filename} eq $filename } @{ $self->_files };
  9         37  
  9         89  
521              
522 9 50       46 unless ($idx != -1) {
523 0         0 $self->_push_error("$filename doesn't exist in bag");
524 0         0 return;
525             }
526              
527 9         17 my @files = grep { $_->{filename} ne $filename } @{ $self->_files };
  14         49  
  9         26  
528              
529 9         40 $self->_files(\@files);
530              
531 9         30 delete $self->_sums->{$filename};
532              
533 9         31 $self->_update_info;
534 9         32 $self->_update_tag_manifest;
535              
536 9         47 $self->_dirty($self->dirty | FLAG_DATA | FLAG_MANIFEST | FLAG_BAG_INFO | FLAG_TAG_MANIFEST);
537              
538 9         31 1;
539             }
540              
541             sub add_fetch {
542 4     4 1 604 my ($self, $url, $size, $filename) = @_;
543              
544 4 50 33     50 die "usage add_fetch(url,size,filename)"
      33        
545             unless defined($url) && $size =~ /^[0-9]+$/ && defined($filename);
546              
547 4 50       15 die "illegal file name $filename"
548             unless $self->_is_legal_file_name($filename);
549              
550 4         91 $self->log->info("adding fetch $url -> $filename");
551              
552 4         51 my (@old) = grep { $_->{filename} ne $filename} @{$self->_fetch};
  0         0  
  4         17  
553              
554 4         14 $self->_fetch(\@old);
555              
556 4         6 push @{$self->_fetch} , Catmandu::BagIt::Fetch->new(url => $url , size => $size , filename => $filename);
  4         82  
557              
558 4         1553 $self->_update_info;
559 4         15 $self->_update_tag_manifest;
560              
561 4         17 $self->_dirty($self->dirty | FLAG_FETCH | FLAG_TAG_MANIFEST);
562              
563 4         30 1;
564             }
565              
566             sub remove_fetch {
567 2     2 1 2287 my ($self, $filename) = @_;
568              
569 2 50       7 die "usage remove_fetch(filename)" unless defined($filename);
570              
571 2         52 $self->log->info("removing fetch for $filename");
572              
573 2         28 my (@old) = grep { $_->filename ne $filename} @{$self->_fetch};
  2         12  
  2         9  
574              
575 2         9 $self->_fetch(\@old);
576 2         6 $self->_update_info;
577 2         7 $self->_update_tag_manifest;
578 2         11 $self->_dirty($self->dirty | FLAG_FETCH | FLAG_TAG_MANIFEST);
579              
580 2         10 1;
581             }
582              
583             sub mirror_fetch {
584 1     1 1 4 my ($self, $fetch) = @_;
585              
586 1 50 33     18 die "usage mirror_fetch(<Catmandu::BagIt::Fetch>)"
      33        
587             unless defined($fetch) && ref($fetch) && ref($fetch) =~ /^Catmandu::BagIt::Fetch/;
588              
589 1         9 my $tmp_filename = Path::Tiny->tempfile;
590              
591 1         641 my $url = $fetch->url;
592 1         5 my $filename = $fetch->filename;
593 1         5 my $path = $self->path;
594              
595 1         24 $self->log->info("mirroring $url -> $tmp_filename...");
596              
597 1         38 my $response = $self->user_agent->mirror($url,$tmp_filename);
598              
599 1 50       10042 if ($response->is_success) {
600 1         36 $self->log->info("mirror is a success");
601             }
602             else {
603 0         0 $self->log->error("mirror $url -> $tmp_filename failed : $response->status_line");
604 0         0 return undef;
605             }
606              
607 1         29 $self->log->info("updating file listing...");
608 1         25 $self->log->debug("add new $filename");
609 1         19 $self->add_file($filename, IO::File->new($tmp_filename,'r'), overwrite => 1);
610             }
611              
612             sub add_info {
613 188     188 1 788 my ($self,$name,$values) = @_;
614              
615 188 50 33     850 die "usage add_info(name,values)"
616             unless defined($name) && defined($values);
617              
618 188 100       948 if ($name =~ /^(Bag-Size|Bagging-Date|Payload-Oxum)$/) {
619 178         303 for my $part (@{$self->_info}) {
  178         505  
620 277 100       640 if ($part->[0] eq $name) {
621 100         186 $part->[1] = $values;
622 100         206 return;
623             }
624             }
625 78         127 push @{$self->_info} , [ $name , $values ];
  78         263  
626 78         172 return;
627             }
628              
629 10         232 $self->log->info("adding info $name");
630              
631 10 50       145 if (ref($values) eq 'ARRAY') {
632 0         0 foreach my $value (@$values) {
633 0         0 push @{$self->_info} , [ $name , $value ];
  0         0  
634             }
635             }
636             else {
637 10         18 push @{$self->_info} , [ $name , $values ];
  10         62  
638             }
639              
640 10         34 $self->_update_tag_manifest;
641              
642 10         46 $self->_dirty($self->dirty | FLAG_BAG_INFO | FLAG_TAG_MANIFEST);
643              
644 10         33 1;
645             }
646              
647             sub remove_info {
648 5     5 1 535 my ($self,$name) = @_;
649              
650 5 50       17 die "usage remove_info(name)"
651             unless defined($name);
652              
653 5 100       33 if ($name =~ /^(Bag-Size|Bagging-Date|Payload-Oxum)$/) {
654 1         26 $self->log->error("removing info $name - is read-only");
655 1         15 return undef;
656             }
657              
658 4         98 $self->log->info("removing info $name");
659              
660 4         52 my (@old) = grep { $_->[0] ne $name } @{$self->_info};
  17         40  
  4         15  
661              
662 4         18 $self->_info(\@old);
663              
664 4         14 $self->_update_tag_manifest;
665              
666 4         21 $self->_dirty($self->dirty | FLAG_BAG_INFO | FLAG_TAG_MANIFEST);
667              
668 4         13 1;
669             }
670              
671             sub list_info_tags {
672 125     125 1 2066 my ($self) = @_;
673 125         201 uniq map { $_->[0] } @{$self->_info};
  452         1707  
  125         345  
674             }
675              
676             sub get_info {
677 447     447 1 1649 my ($self,$field,$join) = @_;
678 447   100     1729 $join //= '; ';
679              
680 447 50       794 die "usage: get_info(field[,$join])" unless $field;
681              
682 447         613 my @res = map { $_->[1] } grep { $_->[0] eq $field } @{$self->_info};
  449         958  
  1644         3145  
  447         905  
683              
684 447 100       1182 wantarray ? @res : join $join, @res;
685             }
686              
687             sub size {
688 62     62 1 1909 my $self = shift;
689              
690 62         195 my $total = $self->_size;
691              
692 62 50       258 if ($total > 100*1000**3) {
    50          
    100          
693             # 100's of GB
694 0         0 sprintf "%-.3f TB" , $total/(1000**4);
695             }
696             elsif ($total > 100*1024**2) {
697             # 100's of MB
698 0         0 sprintf "%-.3f GB" , $total/(1000**3);
699             }
700             elsif ($total > 100*1024) {
701             # 100's of KB
702 2         25 sprintf "%-.3f MB" , $total/(1000**2);
703             }
704             else {
705 60         784 sprintf "%-.3f KB" , $total/1000;
706             }
707             }
708              
709             sub payload_oxum {
710 64     64 1 931 my $self = shift;
711              
712 64         129 my $size = $self->_size;
713 64         163 my $count = $self->list_files;
714              
715 64   50     157 my $fetches = $self->list_fetch // 0;
716              
717 64         113 $count += $fetches;
718              
719 64         252 return "$size.$count";
720             }
721              
722             sub complete {
723 10     10 1 1651 my $self = shift;
724 10   100     48 my $path = $self->path || '';
725              
726 10         37 $self->_error([]);
727              
728 10         251 $self->log->info("checking complete");
729              
730 10 50 33     209 unless ($self->version and $self->version =~ /^[0-9]+\.[0-9]+$/) {
731 0         0 $self->log->error("Tag 'BagIt-Version' not available in bagit.txt");
732 0         0 $self->_push_error("Tag 'BagIt-Version' not available in bagit.txt");
733             }
734              
735 10 50 33     75 unless ($self->encoding and $self->encoding eq 'UTF-8') {
736 0         0 $self->log->error("Tag 'Tag-File-Character-Encoding' not available in bagit.txt");
737 0         0 $self->_push_error("Tag 'Tag-File-Character-Encoding' not available in bagit.txt");
738             }
739              
740 10         26 my @missing = ();
741              
742 10         32 foreach my $file ($self->list_checksum) {
743 23 100 50     50 unless (grep { (my $filename = $_->{filename} || '') =~ /^$file$/ } $self->list_files) {
  42         419  
744 19         46 push @missing , $file;
745             }
746             }
747              
748 10         35 foreach my $file ($self->list_tagsum) {
749 31 50       70 unless (grep { /^$file$/ } $self->list_tags) {
  100         744  
750 0         0 push @missing , $file;
751             }
752             }
753              
754 10         29 foreach my $file (@missing) {
755 19 50       34 unless (grep { $_->filename =~ /^$file$/ } $self->list_fetch) {
  0         0  
756 19         294 $self->log->error("file $file doesn't exist in bag and fetch.txt");
757 19         208 $self->_push_error("file $file doesn't exist in bag and fetch.txt");
758             }
759             }
760              
761 10 100       28 my $has_fetch = $self->list_fetch > 0 ? 1 : 0;
762              
763 10 100 66     27 $self->errors == 0 && @missing == 0 && $has_fetch == 0;
764             }
765              
766             sub valid {
767 11     11 1 30 my $self = shift;
768              
769 11         268 $self->log->info("checking valid");
770              
771             my $validator = sub {
772 74     74   145 my ($file, $tag) = @_;
773 74         178 my $path = $self->path;
774              
775             # To keep things very simple right now we require at least the
776             # bag to be serialized somewhere before we start our validation process
777 74 50 33     1297 unless (defined $path && -d $path) {
778 0         0 $self->log->error("sorry, only serialized (write) bags allowed when validating");
779 0         0 return (1,"sorry, only serialized (write) bags allowed when validating");
780             }
781              
782 74 100       322 my $md5 = $tag == 0 ? $self->get_checksum($file) : $self->get_tagsum($file);
783 74 100       240 my $fh = $tag == 0 ?
784             new IO::File $self->_payload_file($path,$file), "r" :
785             new IO::File $self->_tag_file($path,$file) , "r";
786              
787 74 100       6348 unless ($fh) {
788 38         877 $self->log->error("can't read $file");
789 38         500 return (0,"can't read $file");
790             }
791              
792 36         175 binmode($fh,':raw');
793              
794 36         171 my $md5_check = $self->_md5_sum($fh);
795              
796 36         370 close($fh);
797              
798 36 100       138 unless ($md5 eq $md5_check) {
799 2         51 $self->log->error("$file checksum fails $md5 <> $md5_check");
800 2         41 return (0,"$file checksum fails $md5 <> $md5_check");
801             }
802              
803 34         174 (1);
804 11         229 };
805              
806 11         52 $self->_error([]);
807              
808 11 100       51 if ($self->dirty) {
809 1         23 $self->log->error("bag is dirty : first serialize (write) then try again");
810 1         15 $self->_push_error("bag is dirty : first serialize (write) then try again");
811 1         17 return 0;
812             }
813              
814 10         37 foreach my $file ($self->list_checksum) {
815 43         96 my ($code,$msg) = $validator->($file,0);
816              
817 43 100       112 if ($code == 0) {
818 38         81 $self->_push_error($msg);
819             }
820             }
821              
822 10         42 foreach my $file ($self->list_tagsum) {
823 31         74 my ($code,$msg) = $validator->($file,1);
824              
825 31 100       100 if ($code == 0) {
826 2         8 $self->_push_error($msg);
827             }
828             }
829              
830 10         43 $self->errors == 0;
831             }
832              
833             #-----------------------------------------
834              
835             sub _push_error {
836 77     77   147 my ($self,$msg) = @_;
837 77   50     239 my $errors = $self->_error // [];
838 77         166 push @$errors , $msg;
839 77         186 $self->_error($errors);
840             }
841              
842             sub _size {
843 126     126   182 my $self = shift;
844 126         280 my $path = $self->path;
845              
846 126         186 my $total = 0;
847              
848 126         285 foreach my $file ($self->list_files) {
849 86         302 my $fh = $file->open;
850 86         6292 my $size = [ $fh->stat ]->[7];
851 86         1604 $fh->close;
852 86         1455 $total += $size;
853             }
854              
855 126         338 foreach my $item ($self->list_fetch) {
856 11         35 my $size = $item->size;
857 11         30 $total += $size;
858             }
859              
860 126         277 $total;
861             }
862              
863             sub _update_info {
864 59     59   123 my $self = shift;
865              
866 59         1171 $self->log->debug("updating the default info");
867              
868             # Add some goodies to the info file...
869 59         3416 $self->add_info('Bagging-Date', strftime "%Y-%m-%d", gmtime);
870 59         236 $self->add_info('Bag-Size',$self->size);
871 59         168 $self->add_info('Payload-Oxum',$self->payload_oxum);
872             }
873              
874             sub _update_tag_manifest {
875 96     96   178 my $self = shift;
876              
877 96         2295 $self->log->debug("updating the tag manifest");
878              
879             {
880 96         328 my $sum = $self->_md5_sum($self->_bagit_as_string);
881 96         367 $self->_tag_sums->{'bagit.txt'} = $sum;
882             }
883              
884             {
885 96         1039 my $sum = $self->_md5_sum($self->_baginfo_as_string);
  96         228  
886 96         329 $self->_tag_sums->{'bag-info.txt'} = $sum;
887             }
888              
889             {
890 96         176 my $sum = $self->_md5_sum($self->_manifest_as_string);
  96         147  
  96         249  
891 96         294 $self->_tag_sums->{'manifest-md5.txt'} = $sum;
892             }
893              
894 96 100       208 if ($self->list_fetch) {
895 8         23 my $sum = $self->_md5_sum($self->_fetch_as_string);
896 8         29 $self->_tag_sums->{'fetch.txt'} = $sum;
897              
898 8 100       22 unless (grep {/fetch.txt/} $self->list_tags) {
  28         93  
899 4         7 push @{$self->_tags} , 'fetch.txt';
  4         15  
900             }
901             }
902             else {
903 88         141 my (@new) = grep { $_ ne 'fetch.txt' } @{$self->_tags};
  188         439  
  88         208  
904 88         245 $self->_tags(\@new);
905 88         240 delete $self->_tag_sums->{'fetch.txt'};
906             }
907             }
908              
909             sub _read_fetch {
910 32     32   96 my ($self, $path) = @_;
911              
912 32         113 $self->_fetch([]);
913              
914 32 100       93 return 1 unless -f $self->_fetch_file($path);
915              
916 5         135 $self->log->debug("reading fetch.txt");
917              
918 5         71 foreach my $line (path($self->_fetch_file($path))->lines_utf8) {
919 5         1564 $line =~ s/\r\n$/\n/g;
920 5         16 chomp($line);
921              
922 5         47 my ($url,$size,$filename) = split(/\s+/,$line,3);
923              
924 5         32 $filename =~ s/^data\///;
925              
926 5         13 push @{ $self->_fetch } , Catmandu::BagIt::Fetch->new(url => $url , size => $size , filename => $filename);
  5         159  
927             }
928              
929 5         149 1;
930             }
931              
932             sub _read_tag_manifest {
933 32     32   84 my ($self, $path) = @_;
934              
935 32         139 $self->_tag_sums({});
936              
937 32 100       111 if (! -f $self->_tagmanifest_md5_file($path)) {
938 1         5 return 1;
939             }
940              
941 31         855 $self->log->debug("reading tagmanifest-md5.txt");
942              
943 31         404 foreach my $line (path($self->_tagmanifest_md5_file($path))->lines_utf8) {
944 96         9126 $line =~ s/\r\n$/\n/g;
945 96         164 chomp($line);
946 96         446 my ($sum,$file) = split(/\s+/,$line,2);
947 96         414 $self->_tag_sums->{$file} = $sum;
948             }
949              
950 31         102 1;
951             }
952              
953             sub _read_manifest {
954 32     32   416 my ($self, $path) = @_;
955              
956 32         774 $self->log->debug("reading manifest-md5.txt");
957              
958 32         442 $self->_sums({});
959              
960 32 100       113 if (! -f $self->_manifest_md5_file($path)) {
961 1         17 $self->_push_error("no manifest-md5.txt in $path");
962 1         3 return 0;
963             }
964              
965 31         164 foreach my $line (path($self->_manifest_md5_file($path))->lines_utf8) {
966 66         6111 $line =~ s/\r\n$/\n/g;
967 66         118 chomp($line);
968 66         298 my ($sum,$file) = split(/\s+/,$line,2);
969 66         262 $file =~ s/^data\///;
970 66         292 $self->_sums->{$file} = $sum;
971             }
972              
973 31         2763 1;
974             }
975              
976             sub _read_tags {
977 32     32   96 my ($self, $path) = @_;
978              
979 32         736 $self->log->debug("reading tag files");
980              
981 32         474 $self->_tags([]);
982              
983 32         246 my $rule = Path::Iterator::Rule->new;
984 32         374 $rule->max_depth(1);
985 32         1344 $rule->file;
986 32         793 my $iter = $rule->iter($path);
987              
988 32         3188 while(my $file = $iter->()) {
989 129         47565 $file =~ s/^$path.//;
990              
991 129 100       489 next if $file =~ /^tagmanifest-\w+.txt$/;
992              
993 98         143 push @{ $self->_tags } , $file;
  98         498  
994             }
995              
996 32         1270 1;
997             }
998              
999             sub _read_files {
1000 32     32   98 my ($self, $path) = @_;
1001              
1002 32         723 $self->log->debug("reading data files");
1003              
1004 32         507 $self->_files([]);
1005              
1006 32 100       576 if (! -d "$path/data" ) {
1007 3         72 $self->log->error("payload directory $path/data doesn't exist");
1008 3         55 $self->_push_error("payload directory $path/data doesn't exist");
1009 3         8 return 1;
1010             }
1011              
1012 29         271 my $rule = Path::Iterator::Rule->new;
1013 29         325 $rule->file;
1014 29         854 my $iter = $rule->iter("$path/data");
1015              
1016 29         2554 while(my $file = $iter->()) {
1017 32         13979 my $filename = $file;
1018 32         314 $filename =~ s/^$path\/data\///;
1019 32         815 my $payload = Catmandu::BagIt::Payload->new(filename => $filename, path => $file);
1020 32         2191 push @{ $self->_files } , $payload;
  32         174  
1021             }
1022              
1023 29         3247 1;
1024             }
1025              
1026             sub _read_info {
1027 32     32   85 my ($self, $path) = @_;
1028              
1029 32         776 $self->log->debug("reading the tag info file");
1030              
1031 32         469 $self->_info([]);
1032              
1033 32 100       126 my $info_file = -f $self->_bag_info_file($path) ?
1034             $self->_bag_info_file($path) :
1035             $self->_package_info_file($path);
1036              
1037 32 100       490 if (! -f $info_file) {
1038 1         25 $self->log->error("no package-info.txt or bag-info.txt in $path");
1039 1         25 $self->_push_error("no package-info.txt or bag-info.txt in $path");
1040 1         3 return 0;
1041             }
1042              
1043 31         155 foreach my $line (path($info_file)->lines_utf8) {
1044 127         9564 $line =~ s/\r\n$/\n/g;
1045 127         218 chomp($line);
1046              
1047 127 100       387 if ($line =~ /^\s+/) {
1048 12         44 $line =~ s/^\s*//;
1049 12         33 $self->_info->[-1]->[1] .= $line;
1050 12         19 next;
1051             }
1052              
1053 115         536 my ($n,$v) = split(/\s*:\s*/,$line,2);
1054              
1055 115         202 push @{ $self->_info } , [ $n , $v ];
  115         471  
1056             }
1057              
1058 31         112 1;
1059             }
1060              
1061             sub _read_version {
1062 32     32   105 my ($self, $path) = @_;
1063              
1064 32         772 $self->log->debug("reading the version file");
1065              
1066 32 100       430 if (! -f $self->_bagit_file($path) ) {
1067 1         26 $self->log->error("no bagit.txt in $path");
1068 1         18 $self->_push_error("no bagit.txt in $path");
1069 1         3 return 0;
1070             }
1071              
1072 31         159 foreach my $line (path($self->_bagit_file($path))->lines_utf8) {
1073 62         16963 $line =~ s/\r\n$/\n/g;
1074 62         143 chomp($line);
1075 62         479 my ($n,$v) = split(/\s*:\s*/,$line,2);
1076              
1077 62 100       370 if ($n eq 'BagIt-Version') {
    50          
1078 31         175 $self->_version($v);
1079             }
1080             elsif ($n eq 'Tag-File-Character-Encoding') {
1081 31         166 $self->_encoding($v);
1082             }
1083             }
1084              
1085 31         121 1;
1086             }
1087              
1088             sub _write_bagit {
1089 23     23   67 my ($self,$path) = @_;
1090              
1091 23 100       117 return 1 unless $self->dirty & FLAG_BAGIT;
1092              
1093 10         308 $self->log->info("writing the version file");
1094              
1095 10         136 path($self->_bagit_file($path))->spew_utf8($self->_bagit_as_string);
1096              
1097 10         6446 $self->_dirty($self->dirty ^ FLAG_BAGIT);
1098              
1099 10         31 1;
1100             }
1101              
1102             sub _bagit_as_string {
1103 106     106   479 my $self = shift;
1104              
1105 106         274 my $version = $self->version;
1106 106         241 my $encoding = $self->encoding;
1107              
1108 106         446 return <<EOF;
1109             BagIt-Version: $version
1110             Tag-File-Character-Encoding: $encoding
1111             EOF
1112             }
1113              
1114             sub _write_info {
1115 23     23   65 my ($self,$path) = @_;
1116              
1117 23 50       89 return 1 unless $self->dirty & FLAG_BAG_INFO;
1118              
1119 23         636 $self->log->info("writing the tag info file");
1120              
1121 23         341 path($self->_bag_info_file($path))->spew_utf8($self->_baginfo_as_string);
1122              
1123 23         9398 $self->_dirty($self->dirty ^ FLAG_BAG_INFO);
1124              
1125 23         62 1;
1126             }
1127              
1128             sub _baginfo_as_string {
1129 119     119   826 my $self = shift;
1130              
1131 119         183 my $str = '';
1132              
1133 119         292 foreach my $tag ($self->list_info_tags) {
1134 425         870 my @values = $self->get_info($tag);
1135 425         821 foreach my $val (@values) {
1136 427         1962 my @msg = split //, "$tag: $val";
1137              
1138 427         657 my $cnt = 0;
1139 427 100       1397 while (my (@chunk) = splice(@msg,0,$cnt == 0 ? 79 : 78)) {
1140 427 50       1462 $str .= ($cnt == 0 ? '' : ' ') . join('',@chunk) . "\n";
1141 427         1865 $cnt++;
1142             }
1143             }
1144             }
1145              
1146 119         395 $str;
1147             }
1148              
1149             # Write BagIt data payloads to disk
1150             sub _write_data {
1151 23     23   71 my ($self,$path) = @_;
1152              
1153             # Return immediately when no files need to be written
1154 23 100       96 return 1 unless $self->dirty & FLAG_DATA;
1155              
1156 22         591 $self->log->info("writing the data files");
1157              
1158             # Create a data/ directory for payloads
1159 22 100       715 unless (-d "$path/data") {
1160 10 50       548 unless (mkdir "$path/data") {
1161 0         0 $self->log->error("can't create payload directory $path/data: $!");
1162 0         0 $self->_push_error("can't create payload directory $path/data: $!");
1163 0         0 return;
1164             }
1165             }
1166              
1167             # Create a list of all files written to the payload directory
1168             # Compare this list later with files found in the payload directory
1169             # This difference are the files that can be deleted
1170 22         83 my @all_names_in_bag = ();
1171              
1172 22         77 foreach my $item ($self->list_files) {
1173 24         88 my $filename = 'data/' . $item->{filename};
1174 24         56 push @all_names_in_bag , $filename;
1175              
1176             # Only process files that are dirty
1177 24 100       93 next unless $item->flag & FLAG_DIRTY;
1178              
1179             # Check for deep directories that need to be stored
1180 14         33 my $dir = $filename; $dir =~ s/\/[^\/]+$//;
  14         111  
1181              
1182 14         315 $self->log->info("serializing $filename");
1183              
1184 14 50       391 path("$path/$dir")->mkpath unless -d "$path/$dir";
1185              
1186 14         67 my $old_path = $item->path;
1187 14         46 my $new_path = "$path/$filename";
1188              
1189 14 50       60 if ($item->is_new) {
1190 14         69 File::Copy::move($old_path,$new_path);
1191             }
1192             else {
1193 0         0 File::Copy::copy($old_path,$new_path);
1194             }
1195              
1196 14         1240 $item->flag($item->flag ^ FLAG_DIRTY);
1197             }
1198              
1199             # Check deleted files. Delete all files not in the @all_names_in_bag list
1200 22         183 my $rule = Path::Iterator::Rule->new;
1201 22         309 $rule->file;
1202 22         790 my $iter = $rule->iter("$path/data");
1203              
1204 22         2355 while(my $file = $iter->()) {
1205 28         12782 my $filename = $file;
1206 28         291 $filename =~ s/^$path\///;
1207              
1208 28 100       89 unless (grep {$filename eq $_} @all_names_in_bag) {
  51         189  
1209 4         92 $self->log->info("deleting $path/$filename");
1210 4         297 unlink "$path/$filename";
1211             }
1212             }
1213              
1214 22         2264 $self->_dirty($self->dirty ^ FLAG_DATA);
1215              
1216 22         404 1;
1217             }
1218              
1219             sub _write_fetch {
1220 23     23   61 my ($self,$path) = @_;
1221              
1222 23 50       80 return 1 unless $self->dirty & FLAG_FETCH;
1223              
1224 23         68 my $fetch_str = $self->_fetch_as_string;
1225              
1226 23 100 66     168 unless (defined($fetch_str) && length($fetch_str)) {
1227 20         480 $self->log->info("removing fetch.txt");
1228 20 100       254 unlink $self->_fetch_file($path) if -f $self->_fetch_file($path);
1229 20         93 return 1;
1230             }
1231              
1232 3         77 $self->log->info("writing the fetch file");
1233              
1234 3 50       45 if ($self->_fetch == 0) {
1235 0 0       0 unlink $self->_fetch_file($path) if -r $self->_fetch_file($path);
1236 0         0 $self->_dirty($self->dirty ^ FLAG_FETCH);
1237 0         0 return 1;
1238             }
1239              
1240 3         11 path($self->_fetch_file($path))->spew_utf8($fetch_str);
1241              
1242 3         1262 $self->_dirty($self->dirty ^ FLAG_FETCH);
1243              
1244 3         10 1;
1245             }
1246              
1247             sub _fetch_as_string {
1248 31     31   67 my $self = shift;
1249              
1250 31         47 my $str = '';
1251              
1252 31         156 foreach my $f ($self->list_fetch) {
1253 11         83 $str .= sprintf "%s %s data/%s\n" , $f->url, $f->size, $f->filename;
1254             }
1255              
1256 31         76 $str;
1257             }
1258              
1259             sub _write_manifest {
1260 23     23   63 my ($self,$path) = @_;
1261              
1262 23 50       92 return 1 unless $self->dirty & FLAG_MANIFEST;
1263              
1264 23         517 $self->log->info("writing the manifest file");
1265              
1266 23         292 path($self->_manifest_md5_file($path))->spew_utf8($self->_manifest_as_string);
1267              
1268 23         8992 $self->_dirty($self->dirty ^ FLAG_MANIFEST);
1269              
1270 23         64 1;
1271             }
1272              
1273             sub _manifest_as_string {
1274 119     119   832 my $self = shift;
1275 119         280 my $path = $self->path;
1276              
1277 119 100       328 return undef unless defined $path;
1278              
1279 67         107 my $str = '';
1280              
1281 67         165 foreach my $file ($self->list_checksum) {
1282 87 100       233 next unless -f $self->_payload_file($path,$file);
1283 75         343 my $md5 = $self->get_checksum($file);
1284 75         289 $str .= "$md5 data/$file\n";
1285             }
1286              
1287 67         260 $str;
1288             }
1289              
1290             sub _write_tag_manifest {
1291 23     23   64 my ($self,$path) = @_;
1292              
1293 23 50       110 return 1 unless $self->dirty & FLAG_TAG_MANIFEST;
1294              
1295             # The tag manifest can be dirty when writing new files
1296 23         91 $self->_update_tag_manifest;
1297              
1298 23         557 $self->log->info("writing the tagmanifest file");
1299              
1300 23         292 path($self->_tagmanifest_md5_file($path))->spew_utf8($self->_tag_manifest_as_string);
1301              
1302 23         9661 $self->_dirty($self->dirty ^ FLAG_MANIFEST);
1303              
1304 23         58 1;
1305             }
1306              
1307             sub _tag_manifest_as_string {
1308 23     23   676 my $self = shift;
1309              
1310 23         41 my $str = '';
1311              
1312 23         69 foreach my $file ($self->list_tagsum) {
1313 72         157 my $md5 = $self->get_tagsum($file);
1314 72         204 $str .= "$md5 $file\n";
1315             }
1316              
1317 23         115 $str;
1318             }
1319              
1320             sub _md5_sum {
1321 349     349   669 my ($self, $data) = @_;
1322              
1323 349         1581 my $ctx = Digest::MD5->new;
1324              
1325 349 100       1197 if (!defined $data) {
    100          
    50          
    50          
1326 52         346 return $ctx->add(Encode::encode_utf8(''))->hexdigest;
1327             }
1328             elsif (! ref $data) {
1329 244         2079 return $ctx->add(Encode::encode_utf8($data))->hexdigest;
1330             }
1331             elsif (ref($data) eq 'SCALAR') {
1332 0         0 return $ctx->add(Encode::encode_utf8($$data))->hexdigest;
1333             }
1334             elsif (ref($data) =~ /^IO/) {
1335 53         2046 return $ctx->addfile($data)->hexdigest;
1336             }
1337             else {
1338 0         0 die "unknown data type: `" . ref($data) . "`";
1339             }
1340             }
1341              
1342             sub _is_legal_file_name {
1343 26     26   97 my ($self, $filename) = @_;
1344              
1345 26 100       171 return 0 unless ($filename =~ /^[[:alnum:]._%-]+$/);
1346 24 50       131 return 0 if ($filename =~ m{(^\.|\/\.+\/)});
1347 24         79 return 1;
1348             }
1349              
1350             1;
1351              
1352             __END__
1353              
1354             =encoding utf-8
1355              
1356             =head1 NAME
1357              
1358             Catmandu::BagIt - Low level Catmandu interface to the BagIt packages.
1359              
1360             =begin markdown
1361              
1362             # STATUS
1363              
1364             [![Build Status](https://travis-ci.org/LibreCat/Catmandu-BagIt.svg?branch=master)](https://travis-ci.org/LibreCat/Catmandu-BagIt)
1365             [![Coverage Status](https://coveralls.io/repos/LibreCat/Catmandu-BagIt/badge.svg?branch=master&service=github)](https://coveralls.io/github/LibreCat/Catmandu-BagIt?branch=master)
1366              
1367             =end markdown
1368              
1369             =head1 SYNOPSIS
1370              
1371             use Catmandu::BagIt;
1372              
1373             # Assemble a new bag
1374             my $bagit = Catmandu::BagIt->new;
1375              
1376             # Read an existing
1377             my $bagit = Catmanu::BagIt->read($directory);
1378              
1379             $bag->read('t/bag');
1380              
1381             printf "path: %s\n", $bagit->path;
1382             printf "version: %s\n" , $bagit->version;
1383             printf "encoding: %s\n" , $bagit->encoding;
1384             printf "size: %s\n", $bagit->size;
1385             printf "payload-oxum: %s\n", $bagit->payload_oxum;
1386              
1387             printf "tags:\n";
1388             for my $tag ($bagit->list_info_tags) {
1389             my @values = $bagit->get_info($tag);
1390             printf " $tag: %s\n" , join(", ",@values);
1391             }
1392              
1393             printf "tag-sums:\n";
1394             for my $file ($bagit->list_tagsum) {
1395             my $sum = $bagit->get_tagsum($file);
1396             printf " $file: %s\n" , $sum;
1397             }
1398              
1399             # Read the file listing as found in the manifest file
1400             printf "file-sums:\n";
1401             for my $file ($bagit->list_checksum) {
1402             my $sum = $bagit->get_checksum($file);
1403             printf " $file: %s\n" , $sum;
1404             }
1405              
1406             # Read the real listing of files as found on the disk
1407             printf "files:\n";
1408             for my $file ($bagit->list_files) {
1409             my $stat = [$file->path];
1410             printf " name: %s\n", $file->filename;
1411             printf " size: %s\n", $stat->[7];
1412             printf " last-mod: %s\n", scalar(localtime($stat->[9]));
1413             }
1414              
1415             my $file = $bagit->get_file("mydata.txt");
1416             my $fh = $file->open;
1417              
1418             while (<$fh>) {
1419             ....
1420             }
1421              
1422             close($fh);
1423              
1424             print "dirty?\n" if $bagit->is_dirty;
1425              
1426             if ($bagit->complete) {
1427             print "bag is complete\n";
1428             }
1429             else {
1430             print "bag is not complete!\n";
1431             }
1432              
1433             if ($bagit->valid) {
1434             print "bag is valid\n";
1435             }
1436             else {
1437             print "bag is not valid!\n";
1438             }
1439              
1440             if ($bagit->is_holey) {
1441             print "bag is holey\n";
1442             }
1443             else {
1444             print "bag isn't holey\n";
1445             }
1446              
1447             if ($bagit->errors) {
1448             print join("\n",$bagit->errors);
1449             }
1450              
1451             # Write operations
1452             $bagit->add_info('My-Tag','fsdfsdfsdf');
1453             $bagit->add_info('My-Tag',['dfdsf','dfsfsf','dfdsf']);
1454             $bagit->remove_info('My-Tag');
1455              
1456             $bagit->add_file("test.txt","my text");
1457             $bagit->add_file("data.pdf", IO::File->new("/tmp/data.pdf"));
1458             $bagit->remove_file("test.txt");
1459              
1460             $bagit->add_fetch("http://www.gutenberg.org/cache/epub/1980/pg1980.txt","290000","shortstories.txt");
1461             $bagit->remove_fetch("shortstories.txt");
1462              
1463             unless ($bagit->locked) {
1464             $bagit->write("bags/demo04"); # fails when the bag already exists
1465             $bagit->write("bags/demo04", new => 1); # recreate the bag when it already existed
1466             $bagit->write("bags/demo04", overwrite => 1); # overwrites an exiting bag
1467             }
1468              
1469             =head1 CATMANDU MODULES
1470              
1471             =over
1472              
1473             =item * L<Catmandu::Importer::BagIt>
1474              
1475             =item * L<Catmandu::Exporter::BagIt>
1476              
1477             =item * L<Catmandu::Store::File::BagIt>
1478              
1479             =back
1480              
1481             =head1 LARGE FILE SUPPORT
1482              
1483             Streaming large files into a BagIt requires a large /tmp directory. The location
1484             of the temp directory can be set with the TMPDIR environmental variable.
1485              
1486             =head1 METHODS
1487              
1488             =head2 new()
1489              
1490             Create a new BagIt object
1491              
1492             =head2 read($directory)
1493              
1494             Open an exiting BagIt object and return an instance of BagIt or undef on failure.
1495             In array context the read method also returns all errors as an array:
1496              
1497             my $bagit = Catmandu::BagIt->read("/data/my-bag");
1498              
1499             my ($bagit,@errors) = Catmandu::BagIt->read("/data/my-bag");
1500              
1501             =head2 write($directory, [%options])
1502              
1503             Write a BagIt to disk. Options: new => 1 recreate the bag when it already existed, overwrite => 1 overwrite
1504             and existing bag (updating the changed tags/files);
1505              
1506             =head2 locked
1507              
1508             Check if a process has locked the BagIt. Or, a previous process didn't complete the write operations.
1509              
1510             =head2 path()
1511              
1512             Return the path to the BagIt.
1513              
1514             =head2 version()
1515              
1516             Return the version of the BagIt.
1517              
1518             =head2 encoding()
1519              
1520             Return the encoding of the BagIt.
1521              
1522             =head2 size()
1523              
1524             Return a human readble string of the expected size of the BagIt (adding the actual sizes found on disk plus
1525             the files that need to be fetched from the network).
1526              
1527             =head2 payload_oxum()
1528              
1529             Return the actual payload oxum of files found in the package.
1530              
1531             =head2 is_dirty()
1532              
1533             Return true when the BagIt contains changes not yet written to disk.
1534              
1535             =head2 is_holey()
1536              
1537             Return true when the BagIt contains a non emtpy fetch configuration.
1538              
1539             =head2 is_error()
1540              
1541             Return an ARRAY of errors when checking complete, valid and write.
1542              
1543             =head2 complete()
1544              
1545             Return true when the BagIt is complete (all files and manifest files are consistent).
1546              
1547             =head2 valid()
1548              
1549             Returns true when the BagIt is complete and all checkums match the files on disk.
1550              
1551             =head2 list_info_tags()
1552              
1553             Return an ARRAY of tag names found in bagit-info.txt.
1554              
1555             =head2 add_info($tag,$value)
1556              
1557             =head2 add_info($tag,[$values])
1558              
1559             Add an info $tag with a $value.
1560              
1561             =head2 remove_info($tag)
1562              
1563             Remove an info $tag.
1564              
1565             =head2 get_info($tag, [$delim])
1566              
1567             Return an ARRAY of values found for the $tag name. Or, in scalar context, return a string of
1568             all values optionally delimeted by $delim.
1569              
1570             =head2 list_tagsum()
1571              
1572             Return a ARRAY of all checkums of tag files.
1573              
1574             =head2 get_tagsum($filename)
1575              
1576             Return the checksum of the tag file $filename.
1577              
1578             =head2 list_checksum()
1579              
1580             Return an ARRAY of files found in the manifest file.
1581              
1582             =head2 get_checksum($filename)
1583              
1584             Return the checksum of the file $filname.
1585              
1586             =head2 list_files()
1587              
1588             Return an ARRAY of real payload files found on disk as Catmandu::BagIt::Payload.
1589              
1590             =head2 get_file($filename)
1591              
1592             Get a Catmandu::BagIt::Payload object for the file $filename.
1593              
1594             =head2 add_file($filename, $string, %opts)
1595              
1596             =head2 add_file($filename, IO::File->new(...), %opts)
1597              
1598             =head2 add_file($filaname, sub { my $io = shift; .... }, %opts)
1599              
1600             Add a new file to the BagIt. Possible options:
1601              
1602             overwrite => 1 - remove the old file
1603             md5 => "" - supply an MD5 (don't recalculate it)
1604              
1605             =head2 remove_file($filename)
1606              
1607             Remove a file from the BagIt.
1608              
1609             =head2 list_fetch()
1610              
1611             Return an ARRAY of fetch payloads as Catmandu::BagIt::Fetch.
1612              
1613             =head2 get_fetch($filename)
1614              
1615             Get a Catmandu::BagIt::Fetch object for the file $filename.
1616              
1617             =head2 add_fetch($url,$size,$filename)
1618              
1619             Add a fetch entry to the BagIt.
1620              
1621             =head2 remove_fetch($filename)
1622              
1623             Remove a fetch entry from the BagIt.
1624              
1625             =head2 mirror_fetch($fetch)
1626              
1627             Mirror a Catmandu::BagIt::Fetch object to local disk.
1628              
1629             =head1 SEE ALSO
1630              
1631             L<Catmandu::Importer::BagIt> , L<Catmandu::Exporter::BagIt> , L<Catmandu::Store::File::BagIt>
1632              
1633             =head1 AUTHOR
1634              
1635             Patrick Hochstenbach <Patrick.Hochstenbach@UGent.be>
1636              
1637             =head1 CONTRIBUTORS
1638              
1639             Nicolas Franck, C<< nicolas.franck at ugent.be >>
1640              
1641             =head1 COPYRIGHT AND LICENSE
1642              
1643             This software is copyright (c) 2015 by Patrick Hochstenbach.
1644              
1645             This is free software; you can redistribute it and/or modify it under
1646             the same terms as the Perl 5 programming language system itself.
1647              
1648             =cut