| 1 | package Padre::File::FTP; |
|---|
| 2 | |
|---|
| 3 | use 5.008; |
|---|
| 4 | use strict; |
|---|
| 5 | use warnings; |
|---|
| 6 | |
|---|
| 7 | use Padre::File; |
|---|
| 8 | use File::Temp; |
|---|
| 9 | |
|---|
| 10 | our $VERSION = '0.50'; |
|---|
| 11 | our @ISA = 'Padre::File'; |
|---|
| 12 | |
|---|
| 13 | sub new { |
|---|
| 14 | my $class = shift; |
|---|
| 15 | |
|---|
| 16 | my $url = shift; |
|---|
| 17 | |
|---|
| 18 | # Create myself |
|---|
| 19 | my $self = bless { filename => $url }, $class; |
|---|
| 20 | |
|---|
| 21 | # Using the config is optional, tests and other usages should run without |
|---|
| 22 | my $config = eval { return Padre->ide->config; }; |
|---|
| 23 | if (defined($config)) { |
|---|
| 24 | $self->{_timeout} = $config->file_ftp_timeout; |
|---|
| 25 | $self->{_passive} = $config->file_ftp_passive; |
|---|
| 26 | } else { |
|---|
| 27 | # Use defaults if we have no config |
|---|
| 28 | $self->{_timeout} = 60; |
|---|
| 29 | $self->{_passive} = 1; |
|---|
| 30 | } |
|---|
| 31 | |
|---|
| 32 | # Don't add a new overall-dependency to Padre: |
|---|
| 33 | eval { require Net::FTP; }; |
|---|
| 34 | if ($@) { |
|---|
| 35 | |
|---|
| 36 | $self->{error} = 'Net::FTP is not installed, Padre::File::FTP currently depends on it.'; |
|---|
| 37 | return $self; |
|---|
| 38 | } |
|---|
| 39 | |
|---|
| 40 | ##### START URL parsing ##### |
|---|
| 41 | |
|---|
| 42 | ##### NO REGEX's below this line (except the parser)! ##### |
|---|
| 43 | |
|---|
| 44 | # TO DO: Improve URL parsing |
|---|
| 45 | if ( $url !~ /ftp\:\/?\/?((.+?)(\:(.+?))?\@)?([a-z0-9\-\.]+)(\:(\d+))?(\/.+)$/i ) { |
|---|
| 46 | |
|---|
| 47 | # URL parsing failed |
|---|
| 48 | # TO DO: Warning should go to a user popup not to the text console |
|---|
| 49 | $self->{error} = 'Unable to parse ' . $url; |
|---|
| 50 | return $self; |
|---|
| 51 | } |
|---|
| 52 | |
|---|
| 53 | |
|---|
| 54 | # Login data |
|---|
| 55 | if ( defined($2) ) { |
|---|
| 56 | $self->{_user} = $2; |
|---|
| 57 | $self->{_pass} = $4 if defined($4); |
|---|
| 58 | } else { |
|---|
| 59 | $self->{_user} = 'ftp'; |
|---|
| 60 | $self->{_pass} = 'padre_user@devnull.perlide.org'; |
|---|
| 61 | } |
|---|
| 62 | |
|---|
| 63 | # Host & port |
|---|
| 64 | $self->{_host} = $5; |
|---|
| 65 | $self->{_port} = $7 || 21; |
|---|
| 66 | |
|---|
| 67 | # Path & filename |
|---|
| 68 | $self->{_file} = $8; |
|---|
| 69 | |
|---|
| 70 | ##### END URL parsing, regex is allowed again ##### |
|---|
| 71 | |
|---|
| 72 | if ( !defined( $self->{_pass} ) ) { |
|---|
| 73 | |
|---|
| 74 | # TO DO: Ask the user for a password |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | # TO DO: Handle aborted/timed out connections |
|---|
| 78 | |
|---|
| 79 | # Create FTP object and connection |
|---|
| 80 | $self->{_ftp} = Net::FTP->new( |
|---|
| 81 | Host => $self->{_host}, |
|---|
| 82 | Port => $self->{_port}, |
|---|
| 83 | Timeout => $self->{_timeout}, |
|---|
| 84 | Passive => $self->{_passive}, |
|---|
| 85 | |
|---|
| 86 | # Debug => 3, # Enable for FTP-debugging to STDERR |
|---|
| 87 | ); |
|---|
| 88 | |
|---|
| 89 | if ( !defined( $self->{_ftp} ) ) { |
|---|
| 90 | |
|---|
| 91 | $self->{error} = 'Error connecting to ' . $self->{_host} . ':' . $self->{_port} . ': ' . $@; |
|---|
| 92 | return $self; |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | if ( !$self->{_ftp}->login( $self->{_user}, $self->{_pass} ) ) { |
|---|
| 96 | |
|---|
| 97 | $self->{error} = 'Error logging in on ' . $self->{_host} . ':' . $self->{_port} . ': ' . $@; |
|---|
| 98 | return $self; |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | $self->{_ftp}->binary; |
|---|
| 102 | |
|---|
| 103 | $self->{protocol} = 'ftp'; # Should not be overridden |
|---|
| 104 | |
|---|
| 105 | $self->{_file_temp} = File::Temp->new( UNLINK => 1 ); |
|---|
| 106 | $self->{_tmpfile} = $self->{_file_temp}->filename; |
|---|
| 107 | |
|---|
| 108 | return $self; |
|---|
| 109 | } |
|---|
| 110 | |
|---|
| 111 | sub can_run { |
|---|
| 112 | return 0; |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | sub size { |
|---|
| 116 | my $self = shift; |
|---|
| 117 | return if !defined( $self->{_ftp} ); |
|---|
| 118 | return $self->{_ftp}->size( $self->{_file} ); |
|---|
| 119 | } |
|---|
| 120 | |
|---|
| 121 | sub _todo_mode { |
|---|
| 122 | my $self = shift; |
|---|
| 123 | return 33024; # Currently fixed: read-only textfile |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | sub _todo_mtime { |
|---|
| 127 | my $self = shift; |
|---|
| 128 | |
|---|
| 129 | # The file-changed-on-disk - function requests this frequently: |
|---|
| 130 | if ( defined( $self->{_cached_mtime_time} ) and ( $self->{_cached_mtime_time} > ( time - 60 ) ) ) { |
|---|
| 131 | return $self->{_cached_mtime_value}; |
|---|
| 132 | } |
|---|
| 133 | |
|---|
| 134 | require HTTP::Date; # Part of LWP which is required for this module but not for Padre |
|---|
| 135 | my ( $Content, $Result ) = $self->_request('HEAD'); |
|---|
| 136 | |
|---|
| 137 | $self->{_cached_mtime_value} = HTTP::Date::str2time( $Result->header('Last-Modified') ); |
|---|
| 138 | $self->{_cached_mtime_time} = time; |
|---|
| 139 | |
|---|
| 140 | return $self->{_cached_mtime_value}; |
|---|
| 141 | } |
|---|
| 142 | |
|---|
| 143 | sub exists { |
|---|
| 144 | my $self = shift; |
|---|
| 145 | return if !defined( $self->{_ftp} ); |
|---|
| 146 | |
|---|
| 147 | # Cache basename value |
|---|
| 148 | my $basename = $self->basename; |
|---|
| 149 | |
|---|
| 150 | for ($self->{_ftp}->ls($self->{_file})) { |
|---|
| 151 | return 1 if $_ eq $self->{_file}; |
|---|
| 152 | return 1 if $_ eq $basename; |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | # Fallback if ->ls didn't help. A file heaving a size should exist. |
|---|
| 156 | return 1 if $self->size; |
|---|
| 157 | |
|---|
| 158 | return 0; |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | sub basename { |
|---|
| 162 | my $self = shift; |
|---|
| 163 | |
|---|
| 164 | my $name = $self->{_file}; |
|---|
| 165 | $name =~ s/^.*\///; |
|---|
| 166 | |
|---|
| 167 | return $name; |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | # This method should return the dirname to be used inside Padre, not the one |
|---|
| 171 | # used on the FTP-server. |
|---|
| 172 | sub dirname { |
|---|
| 173 | my $self = shift; |
|---|
| 174 | |
|---|
| 175 | my $dir = $self->{filename}; |
|---|
| 176 | $dir =~ s/\/[^\/]*$//; |
|---|
| 177 | |
|---|
| 178 | return $dir; |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | sub read { |
|---|
| 182 | my $self = shift; |
|---|
| 183 | |
|---|
| 184 | return if !defined( $self->{_ftp} ); |
|---|
| 185 | |
|---|
| 186 | # TO DO: Better error handling |
|---|
| 187 | $self->{_ftp}->get( $self->{_file}, $self->{_tmpfile} ) or $self->{error} = $@; |
|---|
| 188 | open my $tmpfh, $self->{_tmpfile}; |
|---|
| 189 | return join( '', <$tmpfh> ); |
|---|
| 190 | } |
|---|
| 191 | |
|---|
| 192 | sub readonly { |
|---|
| 193 | |
|---|
| 194 | # TO DO: Check file access |
|---|
| 195 | return 0; |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | sub write { |
|---|
| 199 | my $self = shift; |
|---|
| 200 | my $content = shift; |
|---|
| 201 | my $encode = shift || ''; # undef encode = default, but undef will trigger a warning |
|---|
| 202 | |
|---|
| 203 | return if !defined( $self->{_ftp} ); |
|---|
| 204 | |
|---|
| 205 | my $fh; |
|---|
| 206 | if ( !open $fh, ">$encode", $self->{_tmpfile} ) { |
|---|
| 207 | $self->{error} = $!; |
|---|
| 208 | return 0; |
|---|
| 209 | } |
|---|
| 210 | print {$fh} $content; |
|---|
| 211 | close $fh; |
|---|
| 212 | |
|---|
| 213 | # TO DO: Better error handling |
|---|
| 214 | $self->{_ftp}->put( $self->{_tmpfile}, $self->{_file} ) or warn $@; |
|---|
| 215 | |
|---|
| 216 | return 1; |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | ############################################################################### |
|---|
| 220 | ### Internal FTP helper functions |
|---|
| 221 | |
|---|
| 222 | sub _ftp_dirname { |
|---|
| 223 | my $self = shift; |
|---|
| 224 | |
|---|
| 225 | my $dir = $self->{_file}; |
|---|
| 226 | $dir =~ s/\/[^\/]*$//; |
|---|
| 227 | |
|---|
| 228 | return $dir; |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | |
|---|
| 232 | 1; |
|---|
| 233 | |
|---|
| 234 | # Copyright 2008-2009 The Padre development team as listed in Padre.pm. |
|---|
| 235 | # LICENSE |
|---|
| 236 | # This program is free software; you can redistribute it and/or |
|---|
| 237 | # modify it under the same terms as Perl 5 itself. |
|---|