Index: /lang/perl/Net-FTP-Stepstone/t/04_stat.t
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/04_stat.t (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/04_stat.t (revision 7825)
@@ -0,0 +1,82 @@
+use strict;
+use warnings;
+use Test::More qw(no_plan);
+use File::stat;
+use Time::Piece;
+use POE qw(Component::Server::FTP);
+use FindBin;
+
+BEGIN { use_ok 'Net::FTP::Stepstone' }
+
+use Filesys::Virtual;
+
+my $pid = fork;
+if ($pid == 0) {
+    POE::Session->create(
+        inline_states => {
+            _start => sub {
+                my ($kernel, $heap) = @_[KERNEL, HEAP];
+                POE::Component::Server::FTP->spawn(
+                    ListenPort => 2112,
+                    Domain => 'localhost',
+                    AnonymousLogin => 'allow',
+                    FilesystemClass => 'Filesys::Virtual::Plain',
+                    FilesystemArgs => {
+                        'root_path' => "$FindBin::Bin/data",
+                        'cwd' => '/',
+                    },
+                    LogLevel => 4,
+                );
+                $kernel->post(ftpd => 'register');
+            },
+            ftpd_accept => sub {
+                return 1;
+            }
+        }
+    );
+
+    $poe_kernel->run();
+
+    exit;
+}
+my $ftp = Net::FTP::Stepstone->new({
+        stepstone_addr => 'localhost',
+        stepstone_user => $ENV{USER},
+        ftp_addr => 'localhost',
+        ftp_port => 2112,
+        ftp_user => 'anonymous',
+        ftp_pass => 'test',
+    });
+$ftp->lcd("$FindBin::Bin/dest");
+$ftp->cd("/d");
+{
+    my $file = "$FindBin::Bin/data/d/e";
+    my $stat = $ftp->stat('e');
+    is $stat->size, stat($file)->size;
+    is localtime($stat->mtime)->ymd, '2008-03-08';
+    is $stat->mode, stat($file)->mode;
+}
+{
+    my $file = "$FindBin::Bin/data/d/f";
+    my $stat = $ftp->stat('f');
+    is $stat->size, stat($file)->size;
+    is localtime($stat->mtime)->ymd, '2008-03-07';
+    is $stat->mode, stat($file)->mode;
+}
+$ftp->cd("/");
+{
+    my $file = "$FindBin::Bin/data/d/e";
+    my $stat = $ftp->stat('/d/e');
+    is $stat->size, stat($file)->size;
+    is localtime($stat->mtime)->ymd, '2008-03-08';
+    is $stat->mode, stat($file)->mode;
+}
+{
+    my $file = "$FindBin::Bin/data/d/f";
+    my $stat = $ftp->stat('/d/f');
+    is $stat->size, stat($file)->size;
+    is $stat->mtime, 1204846200;
+    is $stat->mode, stat($file)->mode;
+}
+
+kill 9, $pid;
Index: /lang/perl/Net-FTP-Stepstone/t/03_ls.t
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/03_ls.t (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/03_ls.t (revision 7825)
@@ -0,0 +1,66 @@
+use strict;
+use warnings;
+use Test::More qw(no_plan);
+use POE qw(Component::Server::FTP);
+use FindBin;
+
+BEGIN { use_ok 'Net::FTP::Stepstone' }
+
+use Filesys::Virtual;
+
+my $pid = fork;
+if ($pid == 0) {
+    POE::Session->create(
+        inline_states => {
+            _start => sub {
+                my ($kernel, $heap) = @_[KERNEL, HEAP];
+                POE::Component::Server::FTP->spawn(
+                    ListenPort => 2112,
+                    Domain => 'localhost',
+                    AnonymousLogin => 'allow',
+                    FilesystemClass => 'Filesys::Virtual::Plain',
+                    FilesystemArgs => {
+                        'root_path' => "$FindBin::Bin/data",
+                        'cwd' => '/',
+                    },
+                    LogLevel => 4,
+                );
+                $kernel->post(ftpd => 'register');
+            },
+            ftpd_accept => sub {
+                return 1;
+            }
+        }
+    );
+
+    $poe_kernel->run();
+
+    exit;
+}
+my $ftp = Net::FTP::Stepstone->new({
+        stepstone_addr => 'localhost',
+        stepstone_user => $ENV{USER},
+        ftp_addr => 'localhost',
+        ftp_port => 2112,
+        ftp_user => 'anonymous',
+        ftp_pass => 'test',
+    });
+$ftp->lcd("$FindBin::Bin/dest");
+{
+    is $ftp->pwd, '/';
+    my @ls = $ftp->ls;
+    is_deeply \@ls, [qw(a b c d)];
+}
+{
+    $ftp->cd("/d");
+    is $ftp->pwd, '/d';
+    my @ls = $ftp->ls;
+    is_deeply \@ls, [qw(e f g)];
+}
+{
+    $ftp->cd("/");
+    is $ftp->pwd, '/';
+    my @ls = $ftp->ls('d');
+    is_deeply \@ls, [qw(e f g)];
+}
+kill 9, $pid;
Index: /lang/perl/Net-FTP-Stepstone/t/05_get.t
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/05_get.t (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/05_get.t (revision 7825)
@@ -0,0 +1,68 @@
+use strict;
+use warnings;
+use Test::More qw(no_plan);
+use File::stat;
+use POE qw(Component::Server::FTP);
+use FindBin;
+
+BEGIN { use_ok 'Net::FTP::Stepstone' }
+
+use Filesys::Virtual;
+
+my $pid = fork;
+if ($pid == 0) {
+    POE::Session->create(
+        inline_states => {
+            _start => sub {
+                my ($kernel, $heap) = @_[KERNEL, HEAP];
+                POE::Component::Server::FTP->spawn(
+                    ListenPort => 2112,
+                    Domain => 'localhost',
+                    AnonymousLogin => 'allow',
+                    FilesystemClass => 'Filesys::Virtual::Plain',
+                    FilesystemArgs => {
+                        'root_path' => "$FindBin::Bin/data",
+                        'cwd' => '/',
+                    },
+                    LogLevel => 4,
+                );
+                $kernel->post(ftpd => 'register');
+            },
+            ftpd_accept => sub {
+                return 1;
+            }
+        }
+    );
+
+    $poe_kernel->run();
+
+    exit;
+}
+my $ftp = Net::FTP::Stepstone->new({
+        stepstone_addr => 'localhost',
+        stepstone_user => $ENV{USER},
+        ftp_addr => 'localhost',
+        ftp_port => 2112,
+        ftp_user => 'anonymous',
+        ftp_pass => 'test',
+    });
+$ftp->lcd("$FindBin::Bin/dest");
+{
+    $ftp->cd("/d");
+    my $file = "$FindBin::Bin/data/d/f";
+    my $dest = "$FindBin::Bin/dest/f";
+    my $error = $ftp->get('f');
+    is $error, undef;
+    is stat($file)->size, stat($dest)->size;
+    unlink $dest;
+}
+{
+    $ftp->cd("/");
+    my $file = "$FindBin::Bin/data/a";
+    my $dest = "$FindBin::Bin/dest/a";
+    my $error = $ftp->get('a');
+    is $error, undef;
+    is stat($file)->size, stat($dest)->size;
+    unlink $dest;
+}
+kill 9, $pid;
Index: /lang/perl/Net-FTP-Stepstone/t/01_instance.t
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/01_instance.t (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/01_instance.t (revision 7825)
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+use Test::More qw(no_plan);
+
+BEGIN { use_ok 'Net::FTP::Stepstone' }
+
+my $ftp = Net::FTP::Stepstone->new({
+        stepstone_addr => 'localhost',
+        stepstone_user => $ENV{USER},
+        ftp_addr => 'localhost',
+        ftp_port => 2112,
+        ftp_user => 'anonymous',
+        ftp_pass => 'test',
+    });
+isa_ok $ftp, 'Net::FTP::Stepstone';
+is $ftp->stepstone_addr, 'localhost';
+is $ftp->stepstone_user, $ENV{USER};
+is $ftp->ftp_addr, 'localhost';
+is $ftp->ftp_port, 2112;
+is $ftp->ftp_user, 'anonymous';
+is $ftp->ftp_pass, 'test';
Index: /lang/perl/Net-FTP-Stepstone/t/00_compile.t
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/00_compile.t (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/00_compile.t (revision 7825)
@@ -0,0 +1,4 @@
+use strict;
+use Test::More tests => 1;
+
+BEGIN { use_ok 'Net::FTP::Stepstone' }
Index: /lang/perl/Net-FTP-Stepstone/t/data/a
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/data/a (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/data/a (revision 7825)
@@ -0,0 +1,1 @@
+abcdefg
Index: /lang/perl/Net-FTP-Stepstone/t/data/d/e/f
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/data/d/e/f (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/data/d/e/f (revision 7825)
@@ -0,0 +1,1 @@
+12345678
Index: /lang/perl/Net-FTP-Stepstone/t/data/d/f
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/data/d/f (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/data/d/f (revision 7825)
@@ -0,0 +1,1 @@
+this is test
Index: /lang/perl/Net-FTP-Stepstone/t/02_pwd.t
===================================================================
--- /lang/perl/Net-FTP-Stepstone/t/02_pwd.t (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/t/02_pwd.t (revision 7825)
@@ -0,0 +1,51 @@
+use strict;
+use warnings;
+use Test::More qw(no_plan);
+use POE qw(Component::Server::FTP);
+use FindBin;
+
+BEGIN { use_ok 'Net::FTP::Stepstone' }
+
+use Filesys::Virtual;
+
+my $pid = fork;
+if ($pid == 0) {
+    POE::Session->create(
+        inline_states => {
+            _start => sub {
+                my ($kernel, $heap) = @_[KERNEL, HEAP];
+                POE::Component::Server::FTP->spawn(
+                    ListenPort => 2112,
+                    Domain => 'localhost',
+                    AnonymousLogin => 'allow',
+                    FilesystemClass => 'Filesys::Virtual::Plain',
+                    FilesystemArgs => {
+                        'root_path' => "$FindBin::Bin/data",
+                        'cwd' => '/',
+                    },
+                    LogLevel => 4,
+                );
+                $kernel->post(ftpd => 'register');
+            },
+            ftpd_accept => sub {
+                return 1;
+            }
+        }
+    );
+
+    $poe_kernel->run();
+
+    exit;
+}
+my $ftp = Net::FTP::Stepstone->new({
+        stepstone_addr => 'localhost',
+        stepstone_user => $ENV{USER},
+        ftp_addr => 'localhost',
+        ftp_port => 2112,
+        ftp_user => 'anonymous',
+        ftp_pass => 'test',
+    });
+$ftp->lcd("$FindBin::Bin/dest");
+is $ftp->lcwd, "$FindBin::Bin/dest";
+is $ftp->pwd, '/';
+kill 9, $pid;
Index: /lang/perl/Net-FTP-Stepstone/MANIFEST
===================================================================
--- /lang/perl/Net-FTP-Stepstone/MANIFEST (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/MANIFEST (revision 7825)
@@ -0,0 +1,16 @@
+Changes
+lib/Net/FTP/Stepstone.pm
+Makefile.PL
+MANIFEST			This list of files
+t/00_compile.t
+t/01_instance.t
+t/02_pwd.t
+t/03_ls.t
+t/04_stat.t
+t/05_get.t
+t/data/a
+t/data/b
+t/data/c
+t/data/d/e/f
+t/data/d/f
+t/data/d/g
Index: /lang/perl/Net-FTP-Stepstone/lib/Net/FTP/Stepstone.pm
===================================================================
--- /lang/perl/Net-FTP-Stepstone/lib/Net/FTP/Stepstone.pm (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/lib/Net/FTP/Stepstone.pm (revision 7825)
@@ -0,0 +1,227 @@
+package Net::FTP::Stepstone;
+
+use strict;
+use strict;
+use base qw(Class::Accessor::Fast);
+use File::stat ();
+use File::Basename;
+use Time::Piece;
+
+our $VERSION = '0.01';
+__PACKAGE__->mk_accessors(qw(
+    cwd
+    lcwd
+    stepstone_addr
+    stepstone_user
+    ftp_addr
+    ftp_user
+    ftp_pass
+    ftp_port
+));
+
+sub new {
+    my $class = shift;
+    my $self = $class->SUPER::new(@_);
+    $self->cwd('.') unless $self->cwd;
+    $self->ftp_port(21) unless $self->ftp_port;
+    return $self;
+}
+
+sub ls {
+    my $self = shift;
+    my $arg = shift || '';
+    my @lines = $self->_ftp_command("ls $arg");
+    my @items = ();
+    for (@lines) {
+        my $name = (split(' ', $_))[8];
+        $name =~ m{^\.{1,2}$} and next;
+        push @items, $name;
+    }
+    return @items;
+}
+
+sub stat {
+    my $self = shift;
+    my $arg = shift or return;
+    $arg =~ s{/$}{};
+    my $dir = dirname($arg);
+    my $base = basename($arg);
+    my @lines = $self->_ftp_command("ls $dir");
+    my $stob;
+    for (@lines) {
+        (split(' ', $_))[8] eq $base or next;
+        $stob = $self->_stat($_);
+    }
+    return $stob;
+}
+
+sub pwd {
+    my $self = shift;
+    if ($self->cwd eq '.') {
+        $self->_ftp_command('');
+    }
+    return $self->cwd;
+}
+
+sub lcd {
+    my $self = shift;
+    my $arg = shift;
+    $self->_ftp_command("lcd $arg");
+    $self->lcwd($arg);
+}
+
+for my $command (qw(cd get mget)) {
+    no strict 'refs';
+    *$command = sub {
+        my $self = shift;
+        my $arg = join ' ', @_;
+        return $self->_ftp_command("$command $arg");
+    };
+}
+
+sub _ftp_command {
+    my $self = shift;
+    my $command = shift || '';
+    $command = "lcd $self->{lcwd}\n".$command if $self->lcwd;
+    my $ftp_command = <<EOF;
+open $self->{ftp_addr} $self->{ftp_port}
+user $self->{ftp_user} $self->{ftp_pass}
+prompt
+type binary
+cd $self->{cwd}
+$command
+pwd
+close
+quit
+EOF
+    my @all = ` echo "$ftp_command" | ssh -l $self->{stepstone_user} $self->{stepstone_addr} ftp -n 2>/dev/null`;
+    my @lines = grep {$_ !~ m/AUTH not understood|Interactive mode off.|Local directory now|File may not have transferred correctly./} @all;
+    if (my $last_line = $lines[-1]) {
+        my ($code, $cwd) = split '"', $last_line;
+        if ($code =~ /257/) {
+            $self->cwd($cwd);
+        } else {
+            return $last_line;
+        }
+    }
+    if (scalar @lines > 1) {
+        return @lines[0..($#lines -1)];
+    }
+    return undef;
+}
+
+sub _stat {
+    my $self = shift;
+    my $line = shift;
+    my ($mode, $nlink, $user, $group, $size, $month, $date, $time_or_year, $name) = split (" ", $line);
+    my ($time, $year);
+    if ($time_or_year =~ /:/) {
+        $time = $time_or_year;
+        $year = Time::Piece->strptime($month, "%b")->mon > localtime->mon ? 
+            localtime->year - 1 : localtime->year;
+    } else {
+        $time = '00:00';
+        $year = $time_or_year;
+    }
+    my $mtime = Time::Piece->strptime(join(' ', $year, $month, $date, $time),"%Y %b %d %H:%M")->epoch;
+    my $uid = $self->_getent_passwd($user);
+    my $gid = $self->_getent_group($group);
+    my $stob = File::stat::populate(
+        0,                      # dev
+        0,                      # ino
+        $self->_mode($mode),    # mode
+        $nlink + 0,             # nlink
+        $uid + 0,               # uid
+        $gid + 0,               # gid
+        0,                      # rdev
+        $size + 0,              # size
+        0,                      # atime
+        $mtime + 0,             # mtime
+        0,                      # ctime
+        1,                      # blksize
+        $size + 0,              # blocks
+    );
+    return $stob;
+}
+
+for my $db (qw(group passwd)) {
+    no strict 'refs';
+    my $command = "_getent_$db";
+    *$command = sub {
+        my $self = shift;
+        my $name = shift;
+        my $val = `getent $db $name | cut -d: -f3`;
+        chomp $val;
+        $val = 99 unless length $val;
+        return $val;
+    }
+}
+
+sub _mode {
+    my $self = shift;
+    my $mode = shift;
+    my $val = 0;
+    my ($t,$u,$g,$o) = ($mode =~ /^(.)(...)(...)(...)$/);
+
+    for ($t){
+        $val |= 0010000 if /p/;
+        $val |= 0020000 if /c/;
+        $val |= 0040000 if /d/;
+        $val |= 0060000 if /b/;
+        $val |= 0100000 if /-/;
+        $val |= 0120000 if /l/;
+        $val |= 0140000 if /s/;
+    }
+
+    for ($u){
+        $val |= 0400 if /r/;
+        $val |= 0200 if /w/;
+        $val |= 0100 if /[xs]/;
+        $val |= 04000 if /[sS]/;
+    }
+
+    for ($g){
+        $val |= 0040 if /r/;
+        $val |= 0020 if /w/;
+        $val |= 0010 if /[xs]/;
+        $val |= 02000 if /[sS]/;
+    }
+
+    for ($o){
+        $val |= 0004 if /r/;
+        $val |= 0002 if /w/;
+        $val |= 0001 if /[xt]/;
+        $val |= 01000 if /[Tt]/;
+    }
+
+    return $val;
+}
+
+
+1;
+__END__
+
+=head1 NAME
+
+Net::FTP::Stepstone -
+
+=head1 SYNOPSIS
+
+  use Net::FTP::Stepstone;
+
+=head1 DESCRIPTION
+
+Net::FTP::Stepstone is
+
+=head1 AUTHOR
+
+Author E<lt>author@galaxyE<gt>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+L<>
+
+=cut
Index: /lang/perl/Net-FTP-Stepstone/Makefile.PL
===================================================================
--- /lang/perl/Net-FTP-Stepstone/Makefile.PL (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/Makefile.PL (revision 7825)
@@ -0,0 +1,16 @@
+use ExtUtils::MakeMaker;
+WriteMakefile(
+    'NAME'      => 'Net::FTP::Stepstone',
+    'VERSION_FROM' => 'lib/Net/FTP/Stepstone.pm', # finds $VERSION
+    'PREREQ_PM' => {
+        Test::More => 0.32,
+        Class::Accessor::Fast => 0,
+        Time::Piece => 0,
+        File::stat => 0,
+        File::Basename => 0,
+        POE::Component::Server::FTP => 0,
+        Filesys::Virtual => 0,
+        Filesys::Virtual::Plain => 0,
+        FindBin => 0,
+    },
+);
Index: /lang/perl/Net-FTP-Stepstone/Changes
===================================================================
--- /lang/perl/Net-FTP-Stepstone/Changes (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/Changes (revision 7825)
@@ -0,0 +1,4 @@
+Revision history for Perl extension Net::FTP::Stepstone
+
+0.01  Wed Mar  5 21:14:44 2008
+	- original version
Index: /lang/perl/Net-FTP-Stepstone/MANIFEST.SKIP
===================================================================
--- /lang/perl/Net-FTP-Stepstone/MANIFEST.SKIP (revision 7825)
+++ /lang/perl/Net-FTP-Stepstone/MANIFEST.SKIP (revision 7825)
@@ -0,0 +1,11 @@
+\bRCS\b
+\bCVS\b
+^MANIFEST\.
+^Makefile$
+~$
+\.old$
+^blib/
+^pm_to_blib
+^MakeMaker-\d
+\.gz$
+\.cvsignore
