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   8770 use strict;
  19         61  
  19         617  
20 19     19   107 use warnings;
  19         37  
  19         835  
21              
22             our $VERSION = '1.01';
23              
24 19     19   111 use Carp;
  19         38  
  19         1301  
25              
26 19     19   6957 use Dpkg::Compression;
  19         62  
  19         1587  
27 19     19   9221 use Dpkg::Compression::Process;
  19         55  
  19         607  
28 19     19   147 use Dpkg::Gettext;
  19         38  
  19         1066  
29 19     19   129 use Dpkg::ErrorHandling;
  19         53  
  19         1434  
30              
31 19     19   2649 use parent qw(IO::File Tie::Handle);
  19         1505  
  19         135  
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 50749 my ($this, %args) = @_;
127 154   33     954 my $class = ref($this) || $this;
128 154         1384 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         8839 tie *$self, $class, $self; ## no critic (Miscellanea::ProhibitTies)
132 154         412 bless $self, $class;
133             # Initializations
134 154         682 *$self->{compression} = 'auto';
135 154         1218 *$self->{compressor} = Dpkg::Compression::Process->new();
136             *$self->{add_comp_ext} = $args{add_compression_extension} ||
137 154   50     1355 $args{add_comp_ext} || 0;
138 154         379 *$self->{allow_sigpipe} = 0;
139 154 100       411 if (exists $args{filename}) {
140 21         76 $self->set_filename($args{filename});
141             }
142 154 50       409 if (exists $args{compression}) {
143 0         0 $self->set_compression($args{compression});
144             }
145 154 50       412 if (exists $args{compression_level}) {
146 0         0 $self->set_compression_level($args{compression_level});
147             }
148 154         662 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 26682 my ($self, $mode, %opts) = @_;
162 14254 100       31702 if (exists *$self->{mode}) {
163 14228 50       40033 return if *$self->{mode} eq $mode;
164 0         0 croak "ensure_open requested incompatible mode: $mode";
165             } else {
166             # Sanitize options.
167 26         52 delete $opts{from_pipe};
168 26         41 delete $opts{from_file};
169 26         30 delete $opts{to_pipe};
170 26         41 delete $opts{to_file};
171              
172 26 50       76 if ($mode eq 'w') {
    50          
173 0         0 $self->_open_for_write(%opts);
174             } elsif ($mode eq 'r') {
175 26         72 $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   570 my ($class, $self) = @_;
187 154         683 return $self;
188             }
189              
190             sub WRITE {
191 4683     4683   60011 my ($self, $scalar, $length, $offset) = @_;
192 4683         13052 $self->ensure_open('w');
193 4683         13600 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   17716 my ($self) = shift;
204 9566         23684 $self->ensure_open('r');
205 9566 100       20577 return *$self->{file}->getlines() if wantarray;
206 9557         198245 return *$self->{file}->getline();
207             }
208              
209             sub OPEN {
210 133     133   1324 my ($self) = shift;
211 133 50       370 if (scalar(@_) == 2) {
212 133         345 my ($mode, $filename) = @_;
213 133         535 $self->set_filename($filename);
214 133 100       519 if ($mode eq '>') {
    50          
215 25         100 $self->_open_for_write();
216             } elsif ($mode eq '<') {
217 108         346 $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         1076 return 1; # Always works (otherwise errors out)
227             }
228              
229             sub CLOSE {
230 129     129   1978905 my ($self) = shift;
231 129         265 my $ret = 1;
232 129 50       502 if (defined *$self->{file}) {
233 129 100       825 $ret = *$self->{file}->close(@_) if *$self->{file}->opened();
234             } else {
235 0         0 $ret = 0;
236             }
237 129         5206 $self->_cleanup();
238 129         679 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   18 my ($self, $param) = (shift, shift);
251 5 50       57 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   22 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 378 my ($self, $filename, $add_comp_ext) = @_;
317 154         426 *$self->{filename} = $filename;
318             # Automatically add compression extension to filename
319 154 50       426 if (defined($add_comp_ext)) {
320 0         0 *$self->{add_comp_ext} = $add_comp_ext;
321             }
322 154         436 my $comp_ext_regex = compression_get_file_extension_regex();
323 154 50 33     652 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 610 my $self = shift;
341 340         657 my $comp = *$self->{compression};
342 340 50       892 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         7428 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 307 my $self = shift;
368 159         331 my $comp = *$self->{compression};
369 159 50       562 if ($comp eq 'none') {
    50          
370 0         0 return 0;
371             } elsif ($comp eq 'auto') {
372 159         477 $comp = compression_guess_from_filename($self->get_filename());
373 159 100       470 *$self->{compressor}->set_compression($comp) if $comp;
374             }
375 159         489 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 15 my $self = shift;
387 5 50       57 return *$self->{file} if exists *$self->{file};
388             }
389              
390             ## INTERNAL METHODS
391              
392             sub _open_for_write {
393 25     25   60 my ($self, %opts) = @_;
394 25         36 my $filehandle;
395              
396             croak 'cannot reopen an already opened compressed file'
397 25 50       74 if exists *$self->{mode};
398              
399 25 100       83 if ($self->use_compression()) {
400 9         32 *$self->{compressor}->compress(from_pipe => \$filehandle,
401             to_file => $self->get_filename(), %opts);
402             } else {
403 16 50       90 CORE::open($filehandle, '>', $self->get_filename)
404             or syserr(g_('cannot write %s'), $self->get_filename());
405             }
406 23         349 *$self->{mode} = 'w';
407 23         251 *$self->{file} = $filehandle;
408             }
409              
410             sub _open_for_read {
411 134     134   579 my ($self, %opts) = @_;
412 134         357 my $filehandle;
413              
414             croak 'cannot reopen an already opened compressed file'
415 134 50       389 if exists *$self->{mode};
416              
417 134 100       424 if ($self->use_compression()) {
418 5         26 *$self->{compressor}->uncompress(to_pipe => \$filehandle,
419             from_file => $self->get_filename(), %opts);
420 3         31 *$self->{allow_sigpipe} = 1;
421             } else {
422 129 50       803 CORE::open($filehandle, '<', $self->get_filename)
423             or syserr(g_('cannot read %s'), $self->get_filename());
424             }
425 132         827 *$self->{mode} = 'r';
426 132         656 *$self->{file} = $filehandle;
427             }
428              
429             sub _cleanup {
430 129     129   282 my $self = shift;
431 129   100     882 my $cmdline = *$self->{compressor}{cmdline} // '';
432 129         765 *$self->{compressor}->wait_end_process(nocheck => *$self->{allow_sigpipe});
433 129 100       404 if (*$self->{allow_sigpipe}) {
434 3         86 require POSIX;
435 3 0 0     79 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         398 delete *$self->{mode};
442 129         679 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;