The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Net::SFTP::Foreign::Compat;

our $VERSION = '1.70_05';

use warnings;
use strict;
use Carp;

require Net::SFTP::Foreign;
require Net::SFTP::Foreign::Constants;
require Net::SFTP::Foreign::Attributes::Compat;

our @ISA = qw(Net::SFTP::Foreign);

my $supplant;

sub import {
    for my $arg (@_[1..$#_]) {
	if ($arg eq ':supplant') {
            # print STDERR "suplanting Net::SFTP...\n";
	    if (!$supplant) {
		$supplant = 1;

		@Net::SFTP::ISA = qw(Net::SFTP::Foreign::Compat);
		@Net::SFTP::Attributes::ISA = qw(Net::SFTP::Foreign::Attributes::Compat);
		@Net::SFTP::Constant::ISA = qw(Net::SFTP::Foreign::Constants);

		$INC{q(Net/SFTP.pm)} = $INC{q(Net/SFTP/Foreign/Compat.pm)};
		$INC{q(Net/SFTP/Attributes.pm)} = $INC{q(Net/SFTP/Foreign/Compat.pm)};
		$INC{q(Net/SFTP/Constants.pm)} = $INC{q(Net/SFTP/Foreign/Compat.pm)};

	    }
	}
	else {
	    croak "invalid import tag '$arg'"
	}
    }
}

our %DEFAULTS = ( put => [best_effort => 1],
                  get => [best_effort => 1],
                  ls  => [],
                  new => [] );

BEGIN {
    my @forbidden = qw( setcwd cwd open opendir sftpread sftpwrite
                        seek tell eof write flush read getc lstat stat
                        fstat remove rmdir mkdir setstat fsetstat
                        close closedir readdir realpath readlink
                        rename symlink abort get_content join glob
                        rremove rget rput error die_on_error );

    for my $method (@forbidden) {
        my $super = "SUPER::$method";
        no strict 'refs';
        *{$method} = sub {
            unless (index((caller)[0], "Net::SFTP::Foreign") == 0) {
                croak "Method '$method' is not available from " . __PACKAGE__
                    . ", use the real Net::SFTP::Foreign if you want it!";
            }
            shift->$super(@_);
        };
    }
}

sub new {
    my ($class, $host, %opts) = @_;

    my $warn;
    if (exists $opts{warn}) {
	$warn = delete($opts{warn}) || sub {};
    }
    else {
	$warn = sub { warn(CORE::join '', @_, "\n") };
    }

    my $sftp = $class->SUPER::new($host, @{$DEFAULTS{new}}, %opts);

    $sftp->{_compat_warn} = $warn;

    return $sftp;

}

sub _warn {
    my $sftp = shift;
    if (my $w = $sftp->{_compat_warn}) {
	$w->(@_);
    }
}

sub _warn_error {
    my $sftp = shift;
    if (my $e = $sftp->SUPER::error) {
	$sftp->_warn($e);
    }
}

sub status {
    my $status = shift->SUPER::status;
    return wantarray ? ($status + 0, "$status") : $status + 0;
}

sub get {
    croak '$Usage: $sftp->get($local, $remote, $cb)' if @_ < 2 or @_ > 4;
    my ($sftp, $remote, $local, $cb) = @_;

    my $save = defined(wantarray);
    my @content;
    my @cb;
    if (defined $cb or $save) {
        @cb = ( callback => sub {
                    my ($sftp, $data, $off, $size) = @_;
                    $cb->($sftp, $data, $off, $size) if $cb;
                    push @content, $data if $save
                });
    }

    $sftp->SUPER::get($remote, $local,
                      @{$DEFAULTS{get}},
                      dont_save => !defined($local),
                      @cb)
        or return undef;

    if ($save) {
	return CORE::join('', @content);
    }
}

sub put {
    croak '$Usage: $sftp->put($local, $remote, $cb)' if @_ < 3 or @_ > 4;
    my ($sftp, $local, $remote, $cb) = @_;

    $sftp->SUPER::put($local, $remote,
                      @{$DEFAULTS{put}},
		      callback => $cb);
    $sftp->_warn_error;
    !$sftp->SUPER::error;
}

sub ls {
    croak '$Usage: $sftp->ls($path, $cb)' if @_ < 2 or @_ > 3;
    my ($sftp, $path, $cb) = @_;
    if ($cb) {
	$sftp->SUPER::ls($path,
                         @{$DEFAULTS{ls}},
			 wanted => sub { _rebless_attrs($_[1]->{a});
					 $cb->($_[1]);
					 0 } );
	return ();
    }
    else {
	if (my $ls = $sftp->SUPER::ls($path, @{$DEFAULTS{ls}})) {
	    _rebless_attrs($_->{a}) for @$ls;
	    return @$ls;
	}
	return ()
    }
}

sub do_open { shift->SUPER::open(@_) }

sub do_opendir { shift->SUPER::opendir(@_) }

sub do_realpath { shift->SUPER::realpath(@_) }

sub do_read {
    my $sftp = shift;
    my $read = $sftp->SUPER::sftpread(@_);
    $sftp->_warn_error;
    if (wantarray) {
	return ($read, $sftp->status);
    }
    else {
	return $read
    }
}

sub _gen_do_and_status {
    my $method = "SUPER::" . shift;
    return sub {
	my $sftp = shift;
	$sftp->$method(@_);
	$sftp->_warn_error;
	$sftp->status;
    }
}

*do_write = _gen_do_and_status('sftpwrite');
*do_close = _gen_do_and_status('close');
*do_setstat = _gen_do_and_status('setstat');
*do_fsetstat = _gen_do_and_status('setstat');
*do_remove = _gen_do_and_status('remove');
*do_rename = _gen_do_and_status('rename');
*do_mkdir = _gen_do_and_status('mkdir');
*do_rmdir = _gen_do_and_status('rmdir');

sub _rebless_attrs {
    my $a = shift;
    if ($a) {
	bless $a,  ( $supplant
		     ? "Net::SFTP::Attributes"
		     : "Net::SFTP::Foreign::Attributes::Compat" );
    }
    $a;
}

sub _gen_do_stat {
    my $name = shift;
    my $method = "SUPER::$name";
    return sub {
        croak '$Usage: $sftp->'.$name.'($local, $remote, $cb)' if @_ != 2;
	my $sftp = shift;
	if (my $a = $sftp->$method(@_)) {
	    return _rebless_attrs($a);
	}
	else {
	    $sftp->_warn_error;
	    return undef;
	}
    }
}

*do_lstat = _gen_do_stat('lstat');
*do_fstat = _gen_do_stat('fstat');
*do_stat = _gen_do_stat('stat');


1;

__END__

=head1 NAME

Net::SFTP::Foreign::Compat - Adapter for Net::SFTP compatibility

=head1 SYNOPSIS

    use Net::SFTP::Foreign::Compat;
    my $sftp = Net::SFTP::Foreign::Compat->new($host);
    $sftp->get("foo", "bar");
    $sftp->put("bar", "baz");

    use Net::SFTP::Foreign::Compat ':supplant';
    my $sftp = Net::SFTP->new($host);

=head1 DESCRIPTION

This package is a wrapper around L<Net::SFTP::Foreign> that provides
an API (mostly) compatible with that of L<Net::SFTP>.

Methods on this package are identical to those in L<Net::SFTP> except
that L<Net::SFTP::Foreign::Attributes::Compat> objects have to be used
instead of L<Net::SFTP::Attributes>.

If the C<:supplant> tag is used, this module installs also wrappers on
the C<Net::SFTP> and L<Net::SFTP::Attributes> packages so no other
parts of the program have to modified in order to move from Net::SFTP
to Net::SFTP::Foreign.

=head2 Setting defaults

The hash C<%Net::SFTP::Foreign::DEFAULTS> can be used to set default
values for L<Net::SFTP::Foreign> methods called under the hood and
otherwise not accessible through the Net::SFTP API.

The entries currently supported are:

=over

=item new => \@opts

extra options passed to Net::SFTP::Foreign constructor.

=item get => \@opts

extra options passed to Net::SFTP::Foreign::get method.

=item put => \@opts

extra options passed to Net::SFTP::Foreign::put method.

=item ls  => \@opts

extra options passed to Net::SFTP::Foreign::ls method.

=back

=head1 COPYRIGHT

Copyright (c) 2006-2008, 2011 Salvador FandiE<ntilde>o

All rights reserved.  This program is free software; you can
redistribute it and/or modify it under the same terms as Perl itself.

=cut