File Coverage

blib/lib/Dpkg/Compression/FileHandle.pm
Criterion Covered Total %
statement 117 153 76.4
branch 38 72 52.7
condition 6 19 31.5
subroutine 24 30 80.0
pod 8 8 100.0
total 193 282 68.4


line stmt bran cond sub pod time code
1             # Copyright © 2008-2010 Raphaël Hertzog
2             # Copyright © 2012-2014 Guillem Jover
3             #
4             # This program is free software; you can redistribute it and/or modify
5             # it under the terms of the GNU General Public License as published by
6             # the Free Software Foundation; either version 2 of the License, or
7             # (at your option) any later version.
8             #
9             # This program is distributed in the hope that it will be useful,
10             # but WITHOUT ANY WARRANTY; without even the implied warranty of
11             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12             # GNU General Public License for more details.
13             #
14             # You should have received a copy of the GNU General Public License
15             # along with this program. If not, see .
16              
17             package Dpkg::Compression::FileHandle;
18              
19 19     19   9323 use strict;
  19         46  
  19         614  
20 19     19   107 use warnings;
  19         37  
  19         807  
21              
22             our $VERSION = '1.01';
23              
24 19     19   111 use Carp;
  19         42  
  19         1258  
25              
26 19     19   7096 use Dpkg::Compression;
  19         44  
  19         2566  
27 19     19   9465 use Dpkg::Compression::Process;
  19         54  
  19         663  
28 19     19   130 use Dpkg::Gettext;
  19         39  
  19         1094  
29 19     19   124 use Dpkg::ErrorHandling;
  19         38  
  19         1293  
30              
31 19     19   2930 use parent qw(IO::File Tie::Handle);
  19         1590  
  19         139  
32              
33             # Useful reference to understand some kludges required to
34             # have the class behave like a filehandle
35             # http://blog.woobling.org/2009/10/are-filehandles-objects.html
36              
37             =encoding utf8
38              
39             =head1 NAME
40              
41             Dpkg::Compression::FileHandle - class dealing transparently with file compression
42              
43             =head1 SYNOPSIS
44              
45             use Dpkg::Compression::FileHandle;
46              
47             my ($fh, @lines);
48              
49             $fh = Dpkg::Compression::FileHandle->new(filename => 'sample.gz');
50             print $fh "Something\n";
51             close $fh;
52              
53             $fh = Dpkg::Compression::FileHandle->new();
54             open($fh, '>', 'sample.bz2');
55             print $fh "Something\n";
56             close $fh;
57              
58             $fh = Dpkg::Compression::FileHandle->new();
59             $fh->open('sample.xz', 'w');
60             $fh->print("Something\n");
61             $fh->close();
62              
63             $fh = Dpkg::Compression::FileHandle->new(filename => 'sample.gz');
64             @lines = <$fh>;
65             close $fh;
66              
67             $fh = Dpkg::Compression::FileHandle->new();
68             open($fh, '<', 'sample.bz2');
69             @lines = <$fh>;
70             close $fh;
71              
72             $fh = Dpkg::Compression::FileHandle->new();
73             $fh->open('sample.xz', 'r');
74             @lines = $fh->getlines();
75             $fh->close();
76              
77             =head1 DESCRIPTION
78              
79             Dpkg::Compression::FileHandle is a class that can be used
80             like any filehandle and that deals transparently with compressed
81             files. By default, the compression scheme is guessed from the filename
82             but you can override this behaviour with the method C.
83              
84             If you don't open the file explicitly, it will be auto-opened on the
85             first read or write operation based on the filename set at creation time
86             (or later with the C method).
87              
88             Once a file has been opened, the filehandle must be closed before being
89             able to open another file.
90              
91             =head1 STANDARD FUNCTIONS
92              
93             The standard functions acting on filehandles should accept a
94             Dpkg::Compression::FileHandle object transparently including
95             C (only when using the variant with 3 parameters), C,
96             C, C, C, C, C, C, C,
97             C, C, C, C, C, C, C.
98              
99             Note however that C and C will only work on uncompressed
100             files as compressed files are really pipes to the compressor programs
101             and you can't seek on a pipe.
102              
103             =head1 FileHandle METHODS
104              
105             The class inherits from IO::File so all methods that work on this
106             class should work for Dpkg::Compression::FileHandle too. There
107             may be exceptions though.
108              
109             =head1 PUBLIC METHODS
110              
111             =over 4
112              
113             =item $fh = Dpkg::Compression::FileHandle->new(%opts)
114              
115             Creates a new filehandle supporting on-the-fly compression/decompression.
116             Supported options are "filename", "compression", "compression_level" (see
117             respective set_* functions) and "add_comp_ext". If "add_comp_ext"
118             evaluates to true, then the extension corresponding to the selected
119             compression scheme is automatically added to the recorded filename. It's
120             obviously incompatible with automatic detection of the compression method.
121              
122             =cut
123              
124             # Class methods
125             sub new {
126 154     154 1 54317 my ($this, %args) = @_;
127 154   33     915 my $class = ref($this) || $this;
128 154         1231 my $self = IO::File->new();
129             # Tying is required to overload the open functions and to auto-open
130             # the file on first read/write operation
131 154         8708 tie *$self, $class, $self; ## no critic (Miscellanea::ProhibitTies)
132 154         363 bless $self, $class;
133             # Initializations
134 154         693 *$self->{compression} = 'auto';
135 154         1121 *$self->{compressor} = Dpkg::Compression::Process->new();
136             *$self->{add_comp_ext} = $args{add_compression_extension} ||
137 154   50     1236 $args{add_comp_ext} || 0;
138 154         368 *$self->{allow_sigpipe} = 0;
139 154 100       454 if (exists $args{filename}) {
140 21         86 $self->set_filename($args{filename});
141             }
142 154 50       417 if (exists $args{compression}) {
143 0         0 $self->set_compression($args{compression});
144             }
145 154 50       375 if (exists $args{compression_level}) {
146 0         0 $self->set_compression_level($args{compression_level});
147             }
148 154         688 return $self;
149             }
150              
151             =item $fh->ensure_open($mode, %opts)
152              
153             Ensure the file is opened in the requested mode ("r" for read and "w" for
154             write). The options are passed down to the compressor's spawn() call, if one
155             is used. Opens the file with the recorded filename if needed. If the file
156             is already open but not in the requested mode, then it errors out.
157              
158             =cut
159              
160             sub ensure_open {
161 14254     14254 1 24410 my ($self, $mode, %opts) = @_;
162 14254 100       29152 if (exists *$self->{mode}) {
163 14228 50       36275 return if *$self->{mode} eq $mode;
164 0         0 croak "ensure_open requested incompatible mode: $mode";
165             } else {
166             # Sanitize options.
167 26         44 delete $opts{from_pipe};
168 26         36 delete $opts{from_file};
169 26         47 delete $opts{to_pipe};
170 26         34 delete $opts{to_file};
171              
172 26 50       86 if ($mode eq 'w') {
    50          
173 0         0 $self->_open_for_write(%opts);
174             } elsif ($mode eq 'r') {
175 26         71 $self->_open_for_read(%opts);
176             } else {
177 0         0 croak "invalid mode in ensure_open: $mode";
178             }
179             }
180             }
181              
182             ##
183             ## METHODS FOR TIED HANDLE
184             ##
185             sub TIEHANDLE {
186 154     154   490 my ($class, $self) = @_;
187 154         530 return $self;
188             }
189              
190             sub WRITE {
191 4683     4683   52356 my ($self, $scalar, $length, $offset) = @_;
192 4683         10862 $self->ensure_open('w');
193 4683         12066 return *$self->{file}->write($scalar, $length, $offset);
194             }
195              
196             sub READ {
197 0     0   0 my ($self, $scalar, $length, $offset) = @_;
198 0         0 $self->ensure_open('r');
199 0         0 return *$self->{file}->read($scalar, $length, $offset);
200             }
201              
202             sub READLINE {
203 9566     9566   16895 my ($self) = shift;
204 9566         22700 $self->ensure_open('r');
205 9566 100       18818 return *$self->{file}->getlines() if wantarray;
206 9557         194351 return *$self->{file}->getline();
207             }
208              
209             sub OPEN {
210 133     133   1438 my ($self) = shift;
211 133 50       360 if (scalar(@_) == 2) {
212 133         378 my ($mode, $filename) = @_;
213 133         551 $self->set_filename($filename);
214 133 100       589 if ($mode eq '>') {
    50          
215 25         91 $self->_open_for_write();
216             } elsif ($mode eq '<') {
217 108         366 $self->_open_for_read();
218             } else {
219 0         0 croak 'Dpkg::Compression::FileHandle does not support ' .
220             "open() mode $mode";
221             }
222             } else {
223 0         0 croak 'Dpkg::Compression::FileHandle only supports open() ' .
224             'with 3 parameters';
225             }
226 129         980 return 1; # Always works (otherwise errors out)
227             }
228              
229             sub CLOSE {
230 129     129   1975014 my ($self) = shift;
231 129         262 my $ret = 1;
232 129 50       450 if (defined *$self->{file}) {
233 129 100       858 $ret = *$self->{file}->close(@_) if *$self->{file}->opened();
234             } else {
235 0         0 $ret = 0;
236             }
237 129         5434 $self->_cleanup();
238 129         715 return $ret;
239             }
240              
241             sub FILENO {
242 0     0   0 my ($self) = shift;
243 0 0       0 return *$self->{file}->fileno(@_) if defined *$self->{file};
244 0         0 return;
245             }
246              
247             sub EOF {
248             # Since perl 5.12, an integer parameter is passed describing how the
249             # function got called, just ignore it.
250 5     5   20 my ($self, $param) = (shift, shift);
251 5 50       67 return *$self->{file}->eof(@_) if defined *$self->{file};
252 0         0 return 1;
253             }
254              
255             sub SEEK {
256 0     0   0 my ($self) = shift;
257 0 0       0 return *$self->{file}->seek(@_) if defined *$self->{file};
258 0         0 return 0;
259             }
260              
261             sub TELL {
262 0     0   0 my ($self) = shift;
263 0 0       0 return *$self->{file}->tell(@_) if defined *$self->{file};
264 0         0 return -1;
265             }
266              
267             sub BINMODE {
268 11     11   23 my ($self) = shift;
269 11 50       70 return *$self->{file}->binmode(@_) if defined *$self->{file};
270 0         0 return;
271             }
272              
273             ##
274             ## NORMAL METHODS
275             ##
276              
277             =item $fh->set_compression($comp)
278              
279             Defines the compression method used. $comp should one of the methods supported by
280             B or "none" or "auto". "none" indicates that the file is
281             uncompressed and "auto" indicates that the method must be guessed based
282             on the filename extension used.
283              
284             =cut
285              
286             sub set_compression {
287 0     0 1 0 my ($self, $method) = @_;
288 0 0 0     0 if ($method ne 'none' and $method ne 'auto') {
289 0         0 *$self->{compressor}->set_compression($method);
290             }
291 0         0 *$self->{compression} = $method;
292             }
293              
294             =item $fh->set_compression_level($level)
295              
296             Indicate the desired compression level. It should be a value accepted
297             by the function C of B.
298              
299             =cut
300              
301             sub set_compression_level {
302 0     0 1 0 my ($self, $level) = @_;
303 0         0 *$self->{compressor}->set_compression_level($level);
304             }
305              
306             =item $fh->set_filename($name, [$add_comp_ext])
307              
308             Use $name as filename when the file must be opened/created. If
309             $add_comp_ext is passed, it indicates whether the default extension
310             of the compression method must be automatically added to the filename
311             (or not).
312              
313             =cut
314              
315             sub set_filename {
316 154     154 1 346 my ($self, $filename, $add_comp_ext) = @_;
317 154         421 *$self->{filename} = $filename;
318             # Automatically add compression extension to filename
319 154 50       446 if (defined($add_comp_ext)) {
320 0         0 *$self->{add_comp_ext} = $add_comp_ext;
321             }
322 154         420 my $comp_ext_regex = compression_get_file_extension_regex();
323 154 50 33     632 if (*$self->{add_comp_ext} and $filename =~ /\.$comp_ext_regex$/) {
324 0         0 warning('filename %s already has an extension of a compressed file ' .
325             'and add_comp_ext is active', $filename);
326             }
327             }
328              
329             =item $file = $fh->get_filename()
330              
331             Returns the filename that would be used when the filehandle must
332             be opened (both in read and write mode). This function errors out
333             if "add_comp_ext" is enabled while the compression method is set
334             to "auto". The returned filename includes the extension of the compression
335             method if "add_comp_ext" is enabled.
336              
337             =cut
338              
339             sub get_filename {
340 340     340 1 568 my $self = shift;
341 340         603 my $comp = *$self->{compression};
342 340 50       905 if (*$self->{add_comp_ext}) {
343 0 0       0 if ($comp eq 'auto') {
    0          
344 0         0 croak 'automatic detection of compression is ' .
345             'incompatible with add_comp_ext';
346             } elsif ($comp eq 'none') {
347 0         0 return *$self->{filename};
348             } else {
349 0         0 return *$self->{filename} . '.' .
350             compression_get_property($comp, 'file_ext');
351             }
352             } else {
353 340         7974 return *$self->{filename};
354             }
355             }
356              
357             =item $ret = $fh->use_compression()
358              
359             Returns "0" if no compression is used and the compression method used
360             otherwise. If the compression is set to "auto", the value returned
361             depends on the extension of the filename obtained with the B
362             method.
363              
364             =cut
365              
366             sub use_compression {
367 159     159 1 277 my $self = shift;
368 159         312 my $comp = *$self->{compression};
369 159 50       544 if ($comp eq 'none') {
    50          
370 0         0 return 0;
371             } elsif ($comp eq 'auto') {
372 159         393 $comp = compression_guess_from_filename($self->get_filename());
373 159 100       481 *$self->{compressor}->set_compression($comp) if $comp;
374             }
375 159         441 return $comp;
376             }
377              
378             =item $real_fh = $fh->get_filehandle()
379              
380             Returns the real underlying filehandle. Useful if you want to pass it
381             along in a derived class.
382              
383             =cut
384              
385             sub get_filehandle {
386 5     5 1 13 my $self = shift;
387 5 50       53 return *$self->{file} if exists *$self->{file};
388             }
389              
390             ## INTERNAL METHODS
391              
392             sub _open_for_write {
393 25     25   66 my ($self, %opts) = @_;
394 25         38 my $filehandle;
395              
396             croak 'cannot reopen an already opened compressed file'
397 25 50       67 if exists *$self->{mode};
398              
399 25 100       80 if ($self->use_compression()) {
400 9         43 *$self->{compressor}->compress(from_pipe => \$filehandle,
401             to_file => $self->get_filename(), %opts);
402             } else {
403 16 50       130 CORE::open($filehandle, '>', $self->get_filename)
404             or syserr(g_('cannot write %s'), $self->get_filename());
405             }
406 23         350 *$self->{mode} = 'w';
407 23         332 *$self->{file} = $filehandle;
408             }
409              
410             sub _open_for_read {
411 134     134   591 my ($self, %opts) = @_;
412 134         380 my $filehandle;
413              
414             croak 'cannot reopen an already opened compressed file'
415 134 50       365 if exists *$self->{mode};
416              
417 134 100       418 if ($self->use_compression()) {
418 5         23 *$self->{compressor}->uncompress(to_pipe => \$filehandle,
419             from_file => $self->get_filename(), %opts);
420 3         46 *$self->{allow_sigpipe} = 1;
421             } else {
422 129 50       764 CORE::open($filehandle, '<', $self->get_filename)
423             or syserr(g_('cannot read %s'), $self->get_filename());
424             }
425 132         795 *$self->{mode} = 'r';
426 132         679 *$self->{file} = $filehandle;
427             }
428              
429             sub _cleanup {
430 129     129   371 my $self = shift;
431 129   100     797 my $cmdline = *$self->{compressor}{cmdline} // '';
432 129         893 *$self->{compressor}->wait_end_process(nocheck => *$self->{allow_sigpipe});
433 129 100       373 if (*$self->{allow_sigpipe}) {
434 3         94 require POSIX;
435 3 0 0     73 unless (($? == 0) || (POSIX::WIFSIGNALED($?) &&
      33        
436             (POSIX::WTERMSIG($?) == POSIX::SIGPIPE()))) {
437 0         0 subprocerr($cmdline);
438             }
439 3         23 *$self->{allow_sigpipe} = 0;
440             }
441 129         427 delete *$self->{mode};
442 129         596 delete *$self->{file};
443             }
444              
445             =back
446              
447             =head1 DERIVED CLASSES
448              
449             If you want to create a class that inherits from
450             Dpkg::Compression::FileHandle you must be aware that
451             the object is a reference to a GLOB that is returned by Symbol::gensym()
452             and as such it's not a HASH.
453              
454             You can store internal data in a hash but you have to use
455             C<*$self->{...}> to access the associated hash like in the example below:
456              
457             sub set_option {
458             my ($self, $value) = @_;
459             *$self->{option} = $value;
460             }
461              
462             =head1 CHANGES
463              
464             =head2 Version 1.01 (dpkg 1.17.11)
465              
466             New argument: $fh->ensure_open() accepts an %opts argument.
467              
468             =head2 Version 1.00 (dpkg 1.15.6)
469              
470             Mark the module as public.
471              
472             =cut
473             1;