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   7687 use strict;
  19         41  
  19         592  
20 19     19   108 use warnings;
  19         30  
  19         812  
21              
22             our $VERSION = '1.01';
23              
24 19     19   110 use Carp;
  19         40  
  19         1143  
25              
26 19     19   7287 use Dpkg::Compression;
  19         49  
  19         1471  
27 19     19   8670 use Dpkg::Compression::Process;
  19         45  
  19         566  
28 19     19   122 use Dpkg::Gettext;
  19         37  
  19         965  
29 19     19   108 use Dpkg::ErrorHandling;
  19         36  
  19         1173  
30              
31 19     19   2146 use parent qw(IO::File Tie::Handle);
  19         1230  
  19         134  
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 46412 my ($this, %args) = @_;
127 154   33     853 my $class = ref($this) || $this;
128 154         1222 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         8483 tie *$self, $class, $self; ## no critic (Miscellanea::ProhibitTies)
132 154         395 bless $self, $class;
133             # Initializations
134 154         629 *$self->{compression} = 'auto';
135 154         1208 *$self->{compressor} = Dpkg::Compression::Process->new();
136             *$self->{add_comp_ext} = $args{add_compression_extension} ||
137 154   50     1074 $args{add_comp_ext} || 0;
138 154         360 *$self->{allow_sigpipe} = 0;
139 154 100       429 if (exists $args{filename}) {
140 21         81 $self->set_filename($args{filename});
141             }
142 154 50       378 if (exists $args{compression}) {
143 0         0 $self->set_compression($args{compression});
144             }
145 154 50       371 if (exists $args{compression_level}) {
146 0         0 $self->set_compression_level($args{compression_level});
147             }
148 154         603 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 24533 my ($self, $mode, %opts) = @_;
162 14254 100       30114 if (exists *$self->{mode}) {
163 14228 50       38923 return if *$self->{mode} eq $mode;
164 0         0 croak "ensure_open requested incompatible mode: $mode";
165             } else {
166             # Sanitize options.
167 26         40 delete $opts{from_pipe};
168 26         43 delete $opts{from_file};
169 26         38 delete $opts{to_pipe};
170 26         47 delete $opts{to_file};
171              
172 26 50       79 if ($mode eq 'w') {
    50          
173 0         0 $self->_open_for_write(%opts);
174             } elsif ($mode eq 'r') {
175 26         76 $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   429 my ($class, $self) = @_;
187 154         524 return $self;
188             }
189              
190             sub WRITE {
191 4683     4683   55051 my ($self, $scalar, $length, $offset) = @_;
192 4683         11268 $self->ensure_open('w');
193 4683         12295 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   17724 my ($self) = shift;
204 9566         23481 $self->ensure_open('r');
205 9566 100       18343 return *$self->{file}->getlines() if wantarray;
206 9557         200005 return *$self->{file}->getline();
207             }
208              
209             sub OPEN {
210 133     133   1154 my ($self) = shift;
211 133 50       317 if (scalar(@_) == 2) {
212 133         300 my ($mode, $filename) = @_;
213 133         482 $self->set_filename($filename);
214 133 100       418 if ($mode eq '>') {
    50          
215 25         95 $self->_open_for_write();
216             } elsif ($mode eq '<') {
217 108         304 $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         840 return 1; # Always works (otherwise errors out)
227             }
228              
229             sub CLOSE {
230 129     129   1613811 my ($self) = shift;
231 129         288 my $ret = 1;
232 129 50       393 if (defined *$self->{file}) {
233 129 100       755 $ret = *$self->{file}->close(@_) if *$self->{file}->opened();
234             } else {
235 0         0 $ret = 0;
236             }
237 129         4878 $self->_cleanup();
238 129         627 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   23 my ($self, $param) = (shift, shift);
251 5 50       155 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   21 my ($self) = shift;
269 11 50       75 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 365 my ($self, $filename, $add_comp_ext) = @_;
317 154         386 *$self->{filename} = $filename;
318             # Automatically add compression extension to filename
319 154 50       433 if (defined($add_comp_ext)) {
320 0         0 *$self->{add_comp_ext} = $add_comp_ext;
321             }
322 154         401 my $comp_ext_regex = compression_get_file_extension_regex();
323 154 50 33     560 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 522 my $self = shift;
341 340         620 my $comp = *$self->{compression};
342 340 50       923 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         7245 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 270 my $self = shift;
368 159         322 my $comp = *$self->{compression};
369 159 50       579 if ($comp eq 'none') {
    50          
370 0         0 return 0;
371             } elsif ($comp eq 'auto') {
372 159         362 $comp = compression_guess_from_filename($self->get_filename());
373 159 100       400 *$self->{compressor}->set_compression($comp) if $comp;
374             }
375 159         487 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       49 return *$self->{file} if exists *$self->{file};
388             }
389              
390             ## INTERNAL METHODS
391              
392             sub _open_for_write {
393 25     25   52 my ($self, %opts) = @_;
394 25         33 my $filehandle;
395              
396             croak 'cannot reopen an already opened compressed file'
397 25 50       75 if exists *$self->{mode};
398              
399 25 100       61 if ($self->use_compression()) {
400 9         31 *$self->{compressor}->compress(from_pipe => \$filehandle,
401             to_file => $self->get_filename(), %opts);
402             } else {
403 16 50       98 CORE::open($filehandle, '>', $self->get_filename)
404             or syserr(g_('cannot write %s'), $self->get_filename());
405             }
406 23         307 *$self->{mode} = 'w';
407 23         277 *$self->{file} = $filehandle;
408             }
409              
410             sub _open_for_read {
411 134     134   560 my ($self, %opts) = @_;
412 134         363 my $filehandle;
413              
414             croak 'cannot reopen an already opened compressed file'
415 134 50       342 if exists *$self->{mode};
416              
417 134 100       489 if ($self->use_compression()) {
418 5         36 *$self->{compressor}->uncompress(to_pipe => \$filehandle,
419             from_file => $self->get_filename(), %opts);
420 3         32 *$self->{allow_sigpipe} = 1;
421             } else {
422 129 50       703 CORE::open($filehandle, '<', $self->get_filename)
423             or syserr(g_('cannot read %s'), $self->get_filename());
424             }
425 132         901 *$self->{mode} = 'r';
426 132         595 *$self->{file} = $filehandle;
427             }
428              
429             sub _cleanup {
430 129     129   249 my $self = shift;
431 129   100     750 my $cmdline = *$self->{compressor}{cmdline} // '';
432 129         715 *$self->{compressor}->wait_end_process(nocheck => *$self->{allow_sigpipe});
433 129 100       363 if (*$self->{allow_sigpipe}) {
434 3         60 require POSIX;
435 3 0 0     54 unless (($? == 0) || (POSIX::WIFSIGNALED($?) &&
      33        
436             (POSIX::WTERMSIG($?) == POSIX::SIGPIPE()))) {
437 0         0 subprocerr($cmdline);
438             }
439 3         19 *$self->{allow_sigpipe} = 0;
440             }
441 129         381 delete *$self->{mode};
442 129         640 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;