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