package Convert::yEnc; use strict; use IO::File; use Convert::yEnc::Decoder; use Convert::yEnc::RC; use warnings; our $VERSION = '1.05'; sub new { my($package, %params) = @_; my $rcFile = $params{RC } || "$ENV{HOME}/.yencrc"; my $outDir = $params{out} || '.'; my $tmpDir = $params{tmp} || $outDir; my $RC = new Convert::yEnc::RC $rcFile; my $decoder = new Convert::yEnc::Decoder; my $yEnc = { rcFile => $rcFile, RC => $RC, out => $outDir, tmp => $tmpDir, decoder => $decoder }; bless $yEnc, $package } sub out_dir { my($yEnc, $dir) = @_; $yEnc->{out} = $dir; } sub tmp_dir { my($yEnc, $dir) = @_; $yEnc->{tmp} = $dir; } sub decode { my($yEnc, $in) = @_; my $tmpDir = $yEnc->{tmp}; my $decoder = $yEnc->{decoder}; $decoder->out_dir($tmpDir); eval { $decoder->decode($in); my $rc = $yEnc->{RC}; for my $tag (qw(ybegin ypart yend)) { my $line = $decoder->$tag; $line or next; $rc->update($line) or die ref $yEnc, ": bad =$tag line: $line\n"; } my $name = $decoder->name; $rc->complete($name) and $yEnc->_complete($name); }; my $err = $@; my $ok = $err ? 0 : 1; wantarray ? ($ok, $err) : $ok; } sub _complete { my($yEnc, $name) = @_; $yEnc->{RC}->drop($name); my $tmpDir = $yEnc->{tmp}; my $outDir = $yEnc->{out}; my $tmpFile = "$tmpDir/$name"; my $outFile = $yEnc->mkpath($outDir, $name); if (defined $outFile and $outFile eq $tmpFile) { # all done } elsif (defined $outFile) { rename $tmpFile, $outFile or die ref $yEnc, ": Can't rename $tmpFile -> $outFile: $!\n"; } else { unlink $tmpFile; } } sub mkpath { my($yEnc, $dir, $name) = @_; "$dir/$name" } sub decoder { shift->{decoder} } sub RC { shift->{RC } } sub DESTROY { my $yEnc = shift; my $RC = $yEnc->{RC}; defined $RC and $RC->save; } 1 __END__ =head1 NAME Convert::yEnc - yEnc decoder =head1 SYNOPSIS use Convert::yEnc; $yEnc = new Convert::yEnc RC => $rcFile, out => $outDir, tmp => $tmpDir; $yEnc->out_dir($dir); $yEnc->tmp_dir($dir); $ok = $yEnc->decode(\*FILE); $ok = $yEnc->decode( $file); $decoder = $yEnc->decoder; $rc = $yEnc->RC; undef $yEnc; # saves the Convert::yEnc::RC database to disk package My::Decoder; use base qw(Convert::yEnc); sub mkpath { my($yEnc, $dir, $name) = @_; "$dir/$name" } =head1 ABSTRACT yEnc decoder, with database of file parts =head1 DESCRIPTION C decodes yEncoded files and writes them to disk. File parts are saved to I<$tmpDir>; when all parts of a file have been received, the completed file is moved to I<$outDir>. C maintains a database of partially received files, called the RC database. The RC database is loaded from disk when a C object is created, and saved to disk when the object is C'd. =head2 Exports Nothing. =head2 Methods =over 4 =item I<$yEnc> = C C C => I<$rcFile>, C => I<$outDir>, C => I<$tmpDir> Creates and returns a new C object. I<$rcFile> contains the RC database. I<$outDir> is the output directory, and I<$tmpDir> is the temporary directory, If the C parameter is omitted, it defaults to F<$ENV{HOME}/.yencrc>. If the C parameter is omitted, it defaults to the current working directory. If the C parameter is omitted, it defaults to the C parameter. =item I<$yEnc>->C(I<$dir>) Sets the output directory to I<$dir> =item I<$yEnc>->C(I<$dir>) Sets the temporary directory to I<$dir> =item I<$ok> = I<$yEnc>->C(I<$file>) =item I<$ok> = I<$yEnc>->C(I<\*FILE>) Decodes a yEncoded file and writes it to the C directory. If the file is complete, moves it to the C directory and drops the entry for the file from the RC database. The first form reads the file named I<$file>. The second form reads the file handle I. In scalar context, returns true on success. In list context, returns ($ok, $err) where I<$ok> is true on success, and I<$err> is an error message. =item I<$rc> = I<$yEnc>->C Returns the C object that holds the RC database for I<$yEnc>. Applications can use the returned value to query or manipulate the RC database directly. =item C C has a destructor. The destructor writes the RC database back to the file from which it was loaded. =back =head2 Overrides =over 4 =item C C calls C to construct the path to which a completed file is moved. The default implementation of C is shown in the L. Applications can subclass from C and override this method if they want the completed file to appear somewhere else. If C returns C, the completed file is discarded. =back =head1 NOTES =head2 Destructors don't work reliably at global destruct time C provides a C method as a convenience: you can create a C object, use it, forget about it my $yEnc = new Convert::yEnc; $yEnc->decode(...); and the RC file will automatically be written when the object ref count goes to zero. Unless the ref count never goes to zero, because, for example, a named closure is holding a reference on the object sub A { $yEnc } In this case, the object won't be destructed until global destruct time. Unfortunately, the order in which objects are destructed during global destruction isn't controlled, and if the embedded C<< $yEnc->RC >> object is destructed before C<$yEnc> itself, then C<< $yEnc->DESTROY >> won't be able to write the RC file. To avoid creating closures, pass C objects as parameters my $yEnc = new Convert::yEnc; A($yEnc); sub A { my $yEnc = shift } rather than referencing them as globals. To pass a C object to a C I routine, use an anonymous closure File::Find::find(sub { A($yEnc) }, $dir) It isn't always obvious when a closure is created; if you're feeling paranoid, write $yEnc->RC->save to save the RC file. This problem is reported as bug 7853 at L. =head1 SEE ALSO =over 4 =item * L =item * L =item * L =item * L =back =head1 AUTHOR Steven W McDougall, =head1 COPYRIGHT AND LICENSE Copyright (c) 2002-2008 by Steven McDougall. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.