"""
FtpCube
Copyright (C) Michael Gilfix
This file is part of FtpCube.
You should have received a file COPYING containing license terms
along with this program; if not, write to Michael Gilfix
(mgilfix@eecs.tufts.edu) for a copy.
This version of FtpCube is open source; you can redistribute it and/or
modify it under the terms listed in the file COPYING.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
"""
import libftpcube.dispatcher
import libftpcube.protocol
from libftpcube.logger import Logger
import os
import socket
import select
import errno
import threading
import ftplib
import time
import exceptions
CRLF = '\r\n'
class FtpException(libftpcube.protocol.ProtocolException):
"""Base exception for FTP communications."""
def __init__(self, args=None):
libftpcube.protocol.ProtocolException.__init__(self, args)
class FTPDispatcher(libftpcube.dispatcher.Dispatcher):
"""FTP command dispatcher.
This class extends the base FTP dispatcher to allow for dispatching of FTP
command objects. Unless otherwise noted, the extension methods should be
executed within the dispatcher thread. The FTP dispatcher provides the low
level handling of socket communication to the FTP command session. The
extension methods allow commands to wait on the dispatcher for received
messages from the FTP session. It is recommended that command objects have
access to the dispatcher in order to send/receive data while executing from
within the dispatcher thread. This allows the command classes to make use
of the dispatcher capabilities without worrying about thread safety issues.
"""
def __init__(self, **kwargs):
"""Creates an FTP dispatcher instance.
This initializes private variables for use within the dispatcher command
execution thread. These variables are either set once during
initialization or serve as variables for the thread stack. Although
command objects may have reference to the dispatcher object, instance
variables should never be accessed directly -only through accessor
functions.
This method defines the following keywords:
'host' : The host to connect to - default is localhost.
'port' : The FTP port for the control session - default is 21.
"""
libftpcube.dispatcher.Dispatcher.__init__(self, **kwargs)
# Initialize instance variables for use in the thread context
self.sock = None
self.file = None
self.multiline_code = None
self.incoming = [ ]
try:
self.host = kwargs['host']
except KeyError:
self.host = 'localhost'
try:
self.port = kwargs['port']
except KeyError:
self.port = 21
def connect(self):
"""Opens an FTP connection.
This method should be executed either as initialization or within the thread
context of the dispatcher. Otherwise, this method should not be considered
thread safe. If an error occurs establishing a connection, then this method
will raise an FtpException.
The establishment of the connection is performed asynchronously. This allows
for destruction of the dispatcher object (i.e., tearing down the connection)
while a connection is being established."""
proto = socket.getprotobyname('tcp')
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto)
except socket.error, strerror:
self.closeSocket()
raise FtpException(_("Error opening ftp socket: %(err)s") %{ 'err' : strerror })
self.log(Logger.LOCAL, _("Connecting to %(h)s on port %(p)d.")
%{ 'h' : self.host, 'p' : self.port })
self.sock.setblocking(0)
try:
err = self.sock.connect_ex((self.host, self.port))
except socket.error, strerror:
self.closeSocket()
raise FtpException(_("Error connecting to host %(h)s: %(err)s")
%{ 'h' : self.host, 'err' : strerror })
self.log(Logger.LOCAL, _("Connected to %(h)s.") %{ 'h' : self.host })
while (err == errno.EINPROGRESS or err == errno.EWOULDBLOCK) and not self.die.isSet():
time.sleep(0.25)
err = self.sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err != 0 or self.die.isSet():
self.closeSocket()
raise FtpException(_("Error connecting to host %(h)s: %(err)s")
%{ 'h' : self.host, 'err' : os.strerror(err) })
else:
self.sock.setblocking(1)
self.file = self.sock.makefile('rb')
def getHost(self):
"""Returns the host to which this FTP session is connected."""
return self.host
def getPort(self):
"""Returns the port to which this FTP session is connected."""
return self.port
def closeSocket(self):
"""Closes the FTP session socket."""
if self.file is not None:
self.file.close()
self.file = None
if self.sock is not None:
self.sock.close()
self.sock = None
def removeEndLine(self, line):
"""Removes the line ending characters from FTP strings."""
if line[-2:] == CRLF:
line = line[:-2]
elif line[-1:] in CRLF:
line = line[:-1]
return line
def waitFtpResponse(self):
"""Waits for an FTP response.
Calling this within an FTP command will pause execution of the command
until a response is retrieved. This method will return the data retrieved
or None if no data could be retrieved."""
while 1:
line = self.file.readline()
if not line:
self.log(Logger.LOCAL, _("Connection to %(h)s terminated...")
%{ 'h' : self.host })
self.closeSocket()
return
line = self.removeEndLine(line)
data = self.processData(line)
if data is not None:
return data
def waitFtpResponseWithTimeout(self, timeout):
"""Waits for an FTP response within a timeout period."""
read, write, ex = select.select([ self.sock ], [ ], [ ], timeout)
if self.sock in read:
self.waitFtpResponse()
def processData(self, line):
"""Processes FTP response data.
This method handles the receipt of multi-line FTP data, appending incoming
data to the incoming queue. This method returns None as long as multi-line
data is being received, otherwise it returns a list of the aggregated
data."""
self.log(Logger.REMOTE, line)
self.incoming.append(line)
if line[3:4] == '-':
if self.multiline_code is None:
self.multiline_code = line[:3]
return None
elif line[:3] == self.multiline_code and line[3:4] != '-':
self.multiline_code = None
else:
return None
data = self.incoming
self.incoming = [ ]
return data
def send(self, message, alternate=None):
"""Sends a data string through the FTP session."""
if alternate is not None:
self.log(Logger.LOCAL, alternate)
else:
self.log(Logger.LOCAL, message)
return self.sock.sendall(message + CRLF)
def sendOOB(self, message):
"""Sends an out-of-band messages.
This is useful for sending FTP ABORT commands immediately."""
sent = self.sock.sendall(message + CRLF, socket.MSG_OOB)
# Log after the send in case we throw an exception
self.log(Logger.LOCAL, message)
return sent
def run(self):
"""Overrides the dispatcher execute method to clean up sockets upon
termination."""
try:
libftpcube.dispatcher.Dispatcher.run(self)
finally:
# Try to quit the session gracefully
try:
self.quit()
except Exception, strerror:
pass
self.closeSocket()
def quit(self):
"""Quits the current control session."""
self.dispatcher.sendOOB('QUIT')
self.dispatcher.waitFtpResponseWithTimeout(1.0)
class FTPAbstractCommand(libftpcube.dispatcher.DispatcherCommand):
"""Abstract base class for all FTP commands."""
pass
class FTPConnect(FTPAbstractCommand):
"""FTP connection command.
This performs an asynchronous opening of an FTP connection.
The connection can be aborted by destroying the dispatcher
instance while a connection is outstanding."""
def execute(self, dispatcher):
dispatcher.connect()
class FTPAbortCommand(FTPAbstractCommand):
"""FTP abort command.
This command can be used to cancel outstanding commands. The
procedure for cancelling an outstanding command is to empty the
dispatcher queue, and then schedule an abort command. When the
abort command is next scheduled, then an abort will be sent
out of band."""
def execute(self, dispatcher):
dispatcher.sendOOB('ABOR')
self.response = dispatcher.waitFtpResponse()
if self.response[-1][:3] not in ('426', '226'):
raise FtpException(self.response[-1])
return self.response
class FTPGetWelcomeMessage(FTPAbstractCommand):
"""FTP Get Welcome Message.
Receives a welcome message. This should be called immediately
upon opening the session to receive the welcome message, before
commands are sent."""
def execute(self, dispatcher):
self.response = dispatcher.waitFtpResponse()
if self.response[-1][:1] not in '123':
raise FtpException(response[-1])
class FTPCommand(FTPAbstractCommand):
"""Base class for FTP session commands.
This base class provides a common execution for sending a
command and receiving a synchronize response. The response code
is examined to determine whether the command is successful. If
an error is received, then an FtpException is raised."""
def __init__(self, cmd):
FTPAbstractCommand.__init__(self)
self.cmd = cmd
def execute(self, dispatcher):
dispatcher.send(self.cmd)
self.response = dispatcher.waitFtpResponse()
if self.response[-1][0] != '2':
raise FtpException(self.response[-1])
return self.response
class FTPResponseCommand(FTPCommand):
"""Base class for FTP commands that must access the response.
The response is made available for post retrieval within other
commands through the getResponse() method."""
def __init__(self, cmd):
FTPCommand.__init__(self, cmd)
self.response = None
def getResponse(self):
# Set in FTPCommand.execute()
return self.response
class FTP257Command(FTPResponseCommand):
"""Sends a command that has a 257 FTP success response.
These commands return response data regarding directories that
needs to be processed by the FTP client. This command accepts a
reference to a status object, whose optional data is set to the
received data."""
def __init__(self, cmd, status=None):
FTPResponseCommand.__init__(self, cmd)
if status and not isinstance(status, libftpcube.dispatcher.DispatchStatus):
raise FtpException(_("Invalid status object"))
self.status = status
def execute(self, dispatcher):
dispatcher.send(self.cmd)
self.response = dispatcher.waitFtpResponse()
self.response = self.response[-1]
if self.response[:3] != '257':
raise FtpException(_("Invalid 257 response: %(res)s") %{ 'res' : self.response })
if self.status:
dir = ftplib.parse257(self.response)
self.status.setOptionalData(dir)
self.status.finished()
class FTPPendingCommand(FTPAbstractCommand):
"""Sends an FTP session command that can receive a pending response.
An FTPException is raised if the expected pending response code is
not received."""
def __init__(self, cmd):
FTPAbstractCommand.__init__(self)
self.cmd = cmd
def execute(self, dispatcher):
dispatcher.send(self.cmd)
self.response = dispatcher.waitFtpResponse()
if self.response[-1][0] != '3':
raise FtpException(self.response[-1])
class FTPLogin(FTPAbstractCommand):
"""Performs an FTP log-in.
The username, password, and accounting information must be provided
for the login. Anonymous users would simply use the anonymous user
and pssword convention. If an error occurs during log-in, then an
FtpException will be raised."""
def __init__(self, user, pw, acct):
FTPAbstractCommand.__init__(self)
self.user = user
self.pw = pw
self.acct = acct
def execute(self, dispatcher):
user_string = 'USER ' + self.user
dispatcher.send(user_string)
response = dispatcher.waitFtpResponse()
if response[-1][0] == '3':
pw_string = 'PASS ' + self.pw
# So we don't print the password to screen
alternate = 'PASS ' + '*' * len(self.pw)
dispatcher.send(pw_string, alternate)
response = dispatcher.waitFtpResponse()
if response[-1][0] == '3':
# For now, assume ACCT to be ''
acct_string = 'ACCT '
dispatcher.send(acct_string)
response = dispatcher.waitFtpResponse()
if response[-1][0] != '2':
raise FtpException(_("Error logging in to %(host)s: %(resp)s")
%{ 'host' : dispatcher.getHost(), 'resp' : response[-1] })
class FTPTransferConnection:
"""FTP Data Transfer Connection base class.
This class establishes a data connection for sending/receiving file
data. Sub-classes should implement the socket lock according to the
direction of the transfer and type of data being transferred. Each
transfer connection has a unique host and port used for the data
session."""
def __init__(self, host, port, dispatcher=None, passive=True):
"""Creates a new transfer connection.
There are two types of transfer connections: active and passive connections. Passive
connections means that the FTP client will connect to a socket opened by the server.
Non-passive connections require the FTP client to act as a server for the data
session and the remote FTP server to connect back to the client."""
self.abort_flag = threading.Event()
self.host = host
self.port = port
self.dispatcher = dispatcher
if passive:
proto = socket.getprotobyname('tcp')
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto)
except socket.error, strerror:
raise FtpException(_("Error creating socket for transfer: %(err)s")
%{ 'err' : strerror })
self.sock.setblocking(0)
try:
err = self.sock.connect_ex((host, port))
except socket.error, strerror:
raise FtpException(_("Error creating socket for transfer: %(err)s")
%{ 'err' : strerror })
while (err == errno.EINPROGRESS or err == errno.EWOULDBLOCK) and \
not self.abort_flag.isSet():
time.sleep(0.10)
err = self.sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err != 0 or self.abort_flag.isSet():
raise FtpException(_("Error creating socket for transfer: %(err)s")
%{ 'err' : os.strerror(err) })
else:
self.sock.setblocking(1)
else:
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind(('', 0))
self.sock.listen(1)
port = self.sock.getsockname()[1]
host = socket.gethostbyname(dispatcher.getHost())
except socket.error, strerror:
if self.sock:
self.sock.close()
raise FtpException(_("Error creating socket for transfer: %(err)s")
%{ 'err' : strerror })
self.file = self.sock.makefile('rb')
def sendPort(self, host, port):
"""Sends port information to the remote FTP session."""
hbytes = host.split('.')
pbytes = [ str(port / 256), str(port % 256) ]
bytes = hbytes + pbytes
class Handler(libftpcube.dispatcher.ErrorHandler):
def __init__(self, transfer):
self.transfer = transfer
def handle(self, error, dispatcher):
dispatcher.log(Logger.ERROR, error)
self.transfer.abort()
cmd = FTPResponseCommand('PORT ' + ','.join(bytes))
err_handler = cmd.getErrorHandler()
cmd.setErrorHandler(Handler(self))
cmd.execute(self.dispatcher)
cmd.setErrorHandler(err_handler)
def finalizeNonPassiveTransfer(self):
"""Finishes setting up a non-passive transfer by blocking waiting for a connection
from the FTP server.
The old socket is closed and the new accepted socket is used for data transfer."""
new_sock, sockaddr = self.sock.accept()
self.close()
self.sock = new_sock
self.file = self.sock.makefile('rb')
def getSock(self):
"""Gets the socket object for the transfer connection."""
return self.sock
def getFile(self):
"""Gets the file object for the transfer connection."""
return self.file
def close(self):
"""Closes the current open file and socket."""
if self.file:
self.file.close()
if self.sock:
self.sock.close()
def abort(self):
"""Aborts the current transfer connection action.
This method is thread-safe."""
self.abort_flag.set()
class FTPTransferCommand(FTPAbstractCommand):
"""FTP control session initiate data transfer command.
This command allows for the sending of FTP control session commands
that initiate data transfer connections. Execution of this command
also provides size information about the file to be transfered when
the transfer is a download. This information is made available via
the getSize() method post execution."""
def __init__(self, cmd):
FTPAbstractCommand.__init__(self)
self.cmd = cmd
self.size = None
def execute(self, dispatcher):
dispatcher.send(self.cmd)
response = dispatcher.waitFtpResponse()
if response[-1][0] != '1':
raise FtpException(_("Error initiating transfer: %(resp)s")
%{ 'resp' : response[-1] })
if response[-1][:3] == '150':
self.size = ftplib.parse150(response[-1])
def getSize(self):
"""Returns the size of a file transfer download.
If no size information is available, then the None value is returned."""
return self.size
class FTPTransfer(FTPAbstractCommand):
"""Initiates an FTP transfer.
This class contains all the sub-commands needed to initiate an FTP transfer.
This command supports both passive and active transfers, and makes use of the
FTPTransferCommand class under the covers. It also initiates an
FTPTransferConnection for the data session. The transfer can be aborted via
the abort() method."""
def __init__(self, cmd, rest=None, passive=True):
FTPAbstractCommand.__init__(self)
self.cmd = cmd
self.rest = rest
self.passive = passive
self.transfer = None
self.lock = threading.Lock()
def execute(self, dispatcher):
if self.passive:
cmd = FTPResponseCommand('PASV')
cmd.execute(dispatcher)
host, port = ftplib.parse227(cmd.getResponse()[-1])
transfer_args = (host, port)
else:
transfer_args = ('localhost', 0, dispatcher, False)
self.lock.acquire()
try:
self.transfer = apply(FTPTransferConnection, transfer_args)
finally:
self.lock.release()
if self.rest is not None:
cmd = FTPPendingCommand("REST %s" %self.rest)
cmd.execute(dispatcher)
cmd = FTPTransferCommand(self.cmd)
cmd.execute(dispatcher)
self.size = cmd.getSize()
if not self.passive:
self.transfer.finalizeNonPassiveTransfer()
def abort(self):
self.lock.acquire()
try:
if self.transfer:
self.transfer.abort()
finally:
self.lock.release()
class FTPList(FTPTransfer):
"""Gets an FTP listing.
This retrieves an FTP listing within a data session. This command encompasses
both the control and data aspects to the retrieval of the listing. This command
takes a dispatcher status object, which is updated with the latest status as the
listing is retrieved."""
def __init__(self, cmd, status=None, passive=True):
FTPTransfer.__init__(self, cmd, passive=passive)
if status is not None and not isinstance(status, libftpcube.dispatcher.DispatchStatus):
raise FtpException(_("Invalid status object"))
self.status = status
self.abort_flag = threading.Event()
self.data = [ ] # Local copy of data
def execute(self, dispatcher):
# Set the appropriate transfer type
type = FTPResponseCommand('TYPE A')
type.execute(dispatcher)
# Now prepare for the transfer
FTPTransfer.execute(self, dispatcher)
self.sock = self.transfer.getSock()
self.file = self.transfer.getFile()
if self.status:
self.status.start()
while not self.abort_flag.isSet():
line = self.file.readline()
if not line:
break
if line[-2:] == CRLF:
line = line[:-2]
elif line[-1:] in CRLF:
line = line[:-1]
if self.status:
self.status.update(len(line))
self.data.append(line)
self.transfer.close()
response = dispatcher.waitFtpResponse()
if self.status:
self.status.setOptionalData(self.data)
self.status.finished()
def getData(self):
return self.data
def abort(self):
FTPTransfer.abort(self)
self.abort_flag.set()
class FTPAsciiTransfer(FTPTransfer):
"""Performs an ASCII file transfer.
This class performs an ASCII file transfer -both upload and download. This command
encompasses both the control and data aspects to the retrieval of the listing.
This command takes a dispatcher status object, which is updated with the latest
status as the file is transfered."""
def __init__(self, cmd, local_file, download=True, status=None, passive=True):
FTPTransfer.__init__(self, cmd, passive=passive)
self.local_file = local_file
if not isinstance(status, libftpcube.dispatcher.DispatchStatus):
raise FtpException(_("Invalid status object"))
self.status = status
self.download = download
self.abort_flag = threading.Event()
def execute(self, dispatcher):
# Set the transfer type
type = FTPResponseCommand('TYPE A')
type.execute(dispatcher)
FTPTransfer.execute(self, dispatcher)
self.sock = self.transfer.getSock()
self.file = self.transfer.getFile()
if self.status:
self.status.start()
if self.download:
local_file = open(self.local_file, 'w')
while not self.abort_flag.isSet():
line = self.file.readline()
if not line:
break
if self.status:
self.status.update(len(line))
local_file.write(line)
else:
local_file = open(self.local_file, 'r')
while not self.abort_flag.isSet():
line = local_file.readline()
if not line:
break
self.sock.sendall(line)
if self.status:
self.status.update(len(line))
self.transfer.close()
dispatcher.waitFtpResponse()
if self.status:
self.status.finished()
def abort(self):
FTPTransfer.abort(self)
self.abort_flag.set()
class FTPBinaryTransfer(FTPTransfer):
"""Performs a binary file transfer.
This class performs a binary file transfer -both upload and download. This command
encompasses both the control and data aspects to the retrieval of the listing.
This command takes a dispatcher status object, which is updated with the latest
status as the file is transfered."""
def __init__(self, cmd, local_file, download=True, status=None, blocksize=8192,
rest=None, passive=True):
FTPTransfer.__init__(self, cmd, rest=rest, passive=passive)
if not isinstance(status, libftpcube.dispatcher.DispatchStatus):
raise FtpException(_("Invalid status object"))
self.status = status
self.local_file = local_file
self.blocksize = blocksize
self.download = download
self.abort_flag = threading.Event()
def execute(self, dispatcher):
# Set the transfer type
type = FTPResponseCommand('TYPE I')
type.execute(dispatcher)
FTPTransfer.execute(self, dispatcher)
self.sock = self.transfer.getSock()
if self.status:
self.status.start()
if self.download:
if self.rest is not None:
local_file = open(self.local_file, 'ab')
else:
local_file = open(self.local_file, 'wb')
while not self.abort_flag.isSet():
data = self.sock.recv(self.blocksize)
if not data:
break
if self.status:
self.status.update(len(data))
local_file.write(data)
else:
local_file = open(self.local_file, 'rb')
if self.rest is not None:
local_file.seek(self.rest)
while not self.abort_flag.isSet():
data = local_file.read(self.blocksize)
if not data:
break
self.sock.sendall(data)
if self.status:
self.status.update(len(data))
local_file.close()
self.transfer.close()
dispatcher.waitFtpResponse()
if self.status:
self.status.finished()
def abort(self):
FTPTransfer.abort(self)
self.abort_flag.set()
class FTP(libftpcube.protocol.ProtocolInterface):
"""Main interface for the FTP protocol implementation.
This class provides file transfer functionality over the FTP protocol. Classes that
are external to the transports package should only use this class as the interface
into FTP interactions. Each instance of this object represents an FTP session,
including both command and data connections."""
def __init__(self, **kwargs):
"""Initializes the FTP interface.
This creates a new dispatcher thread and dispatch queue for outbound FTP
commands. The default transfer mode for FTP is passive transfers.
This method defines the following keywords:
'host' : The host to connect to - default is localhost.
'port' : The FTP port for the control session - default is 21.
'logger' : The console/file logger to use. May be omitted.
"""
# Process all critical keyword arguments
try:
self.host = kwargs['host']
except KeyError:
raise FtpException(_("Error opening FTP connection: no host given"))
try:
self.port = kwargs['port']
except KeyError:
raise FtpException(_("Error opening FTP connection: no port given"))
try:
self.logger = kwargs['logger']
except KeyError:
self.logger = None
# Initialize instance variables
self.passive = True
# Create the dispatcher
self.dispatcher = FTPDispatcher(**kwargs)
self.dispatcher.start()
def setPassive(self, passive):
"""Sets a boolean indicating whether to use passive mode for transfers."""
self.passive = passive
def getPassive(self):
"""Returns a boolean indicating whether to use passive mode for transfers."""
return self.passive
def getDispatcher(self):
"""Returns the dispatcher for this FTP session."""
return self.dispatcher
def destroy(self):
"""Destroys the FTP session."""
self.dispatcher.destroy()
def busy(self):
"""Returns True if the dispatcher is currently busy executing a command."""
return self.dispatcher.busy()
def addOutgoing(self, cmd, err_handler=None):
"""Adds an outgoing FTP command to the dispatch queue.
If a handler is specified, then it is set as the error handler on the command
object. Otherwise, a default error logger that prints to the console is used."""
if err_handler is None:
if self.logger is not None:
handler = libftpcube.dispatcher.ErrorLogger(self.logger)
cmd.setErrorHandler(handler)
else:
cmd.setErrorHandler(err_handler)
self.dispatcher.dispatch(cmd)
def initiateConnect(self, err_handler=None):
"""Initiates an outbound FTP connection."""
connect = FTPConnect()
self.addOutgoing(connect, err_handler)
def getWelcomeMessage(self, err_handler=None):
"""Receives a welcome message from a newly connected FTP control session."""
welcome = FTPGetWelcomeMessage()
self.addOutgoing(welcome, err_handler)
def login(self, username, password, err_handler=None):
"""Performs a login to an FTP control session.
The specified username and password are used for the login. If the session is
to be anonymous, then the anonymous username/password convention should be
specified."""
login = FTPLogin(username, password, '')
self.addOutgoing(login, err_handler)
def noop(self, err_handler=None):
"""Sends a NOOP command to the control session."""
ftpcmd = FTPCommand('NOOP')
self.addOutgoing(ftpcmd, err_handler)
def cwd(self, dir, err_handler=None):
"""Changes the current working directory to the specified directory.
This method provides special handling for the '.' and '..' entries. This allows
those entries to be used like regular path entries. A '..' corresponds to a
CDUP command and a '.' corresponds to a no-op."""
if not dir or dir == '.':
return
elif dir == '..':
cmd = 'CDUP'
else:
cmd = 'CWD ' + dir
ftpcmd = FTPCommand(cmd)
self.addOutgoing(ftpcmd, err_handler)
def pwd(self, status=None, err_handler=None):
"""Gets the current working directory.
The specified status object is updated with the data received for the current
working directory."""
cmd = 'PWD'
ftpcmd = FTP257Command(cmd, status=status)
self.addOutgoing(ftpcmd, err_handler)
def delete(self, file, err_handler=None):
"""Deletes a file with the specified name in the current directory."""
cmd = 'DELE ' + file
ftpcmd = FTPCommand(cmd)
self.addOutgoing(ftpcmd, err_handler)
def rename(self, name, new_name, err_handler=None):
"""Performs a file rename for a file in the current directory."""
cmd = 'RNFR ' + name
ftpcmd = FTPPendingCommand(cmd)
self.addOutgoing(ftpcmd, err_handler)
cmd = 'RNTO ' + new_name
ftpcmd = FTPCommand(cmd)
self.addOutgoing(ftpcmd, err_handler)
def mkdir(self, dir, status=None, err_handler=None):
"""Creates the specified directory.
The specified status object is updated with the new directory path."""
cmd = 'MKD ' + dir
ftpcmd = FTP257Command(cmd, status=status)
self.addOutgoing(ftpcmd, err_handler)
def rmdir(self, dir, err_handler=None):
"""Removes the specified directory."""
cmd = 'RMD ' + dir
ftpcmd = FTPCommand(cmd)
self.addOutgoing(ftpcmd, err_handler)
def chmod(self, mode, file, err_handler=None):
"""Changes the permissions to the specified mode on the specified file.
Permissions are in octal form, as is used on unix."""
cmd = "SITE CHMOD %s %s" %(mode, file)
ftpcmd = FTPCommand(cmd)
self.addOutgoing(ftpcmd, err_handler)
def abort(self, err_handler=None):
"""Performs an abort of the current running command.
An abort is only performed on an abortable command. Abortable commands are
FTPTransfer and FTPConnect objects. Aborting a connection object destroys
the connection."""
executing = self.dispatcher.getExecutingCommand()
if isinstance(executing, FTPTransfer):
self.dispatcher.resetQueue()
executing.abort()
elif isinstance(executing, FTPConnect):
self.dispatcher.destroy()
def list(self, args=None, status=None, passive=True, err_handler=None):
"""Gets a listing of the current directory.
The supplied list of arguments are supplied to the list command, separated
by spaces. The status object is updated with the list transfer events,
including the amount of listing data transfered. The listing data is then
made available within the optional data field, to be checked when the
finished event is called. The passive argument controls whether the transfer
is done in active or passive mode."""
cmd = 'LIST'
if args is not None:
for list_arg in args:
if list_arg:
cmd = cmd + ' ' + list_arg
listcmd = FTPList(cmd, status=status, passive=passive)
self.addOutgoing(listcmd, err_handler)
def retrieveAscii(self, file, local_dir, status=None, passive=True, err_handler=None):
"""Performs the retrieval of an ASCII file.
The retrieved file is stored in the specified local directory, under the supplied
file name. The status object is updated with the file transfer events, including
the amount of data transfered (for each line transfered). The passive
argument controls whether the transfer is done in active or passive mode."""
cmd = 'RETR ' + file
local_file = os.path.join(local_dir, file)
asciicmd = FTPAsciiTransfer(cmd, local_file, status=status, passive=passive)
self.addOutgoing(asciicmd, err_handler)
def uploadAscii(self, file, local_dir, status=None, passive=True, err_handler=None):
"""Performs an upload of a local ASCII file.
The specified file in the specified local directory is uploaded in ASCII format
to the FTP server. The status object is updated with the file transfer events,
including the amount of data transfered (for each line transfered). The passive
argument controls whether the transfer is done in active or passive mode."""
cmd = 'STOR ' + file
local_file = os.path.join(local_dir, file)
asciicmd = FTPAsciiTransfer(cmd, local_file, download=False, status=status,
passive=passive)
self.addOutgoing(cmd, err_handler)
def retrieveBinary(self, file, local_dir, status=None, blocksize=8192, rest=None,
passive=True, err_handler=None):
"""Performs a download of a binary file.
The retrieved file is stored in the specified local directory, under the supplied
file name. The status object is updated with the file transfer events, including
the amount of data transfered (for each block transfered). The rest argument
is the byte offset of the file used to resume transfers. A rest of None indicates
to download the whole file (overwriting the local copy). The passive argument
controls whether the transfer is done in active or passive mode."""
cmd = 'RETR ' + file
local_file = os.path.join(local_dir, file)
binarycmd = FTPBinaryTransfer(cmd, local_file, status=status, blocksize=blocksize,
rest=rest, passive=passive)
self.addOutgoing(binarycmd, err_handler)
def uploadBinary(self, file, local_dir, status=None, blocksize=8192, rest=None,
passive=True, err_handler=None):
"""Performs an upload of a binary file.
The specified file in the specified local directory is uploaded in binary format
to the FTP server. The status object is updated with the file transfer events,
including the amount of data transfered (for each block transfered). The rest
argument is the byte offset of the file used to resume transfers. A rest of None
indicates that the whole file should be uploaded (overwriting the remote copy).
The passive argument controls whether the transfer is done in active or passive
mode."""
cmd = 'STOR ' + file
local_file = os.path.join(local_dir, file)
binarycmd = FTPBinaryTransfer(cmd, local_file, download=False, status=status,
blocksize=blocksize, rest=rest, passive=passive)
self.addOutgoing(binarycmd, err_handler)
|