1 from twisted.internet import reactor, defer
2 from twisted.internet.protocol import Protocol, ClientCreator
3 from twisted.protocols.ftp import FTPClient, FTPFileListProtocol
5 from os import SEEK_END
7 # XXX: did I ever actually test supportPartial?
8 class FTPDownloader(Protocol):
9 """Download to a file from FTP."""
11 def __init__(self, host, port, path, fileOrName, username = 'anonymous', \
12 password = 'my@email.com', passive = True, supportPartial = False, \
19 self.resume = supportPartial
22 if isinstance(fileOrName, str):
23 self.filename = fileOrName
26 self.file = fileOrName
28 creator = ClientCreator(reactor, FTPClient, username, password, passive = passive)
30 creator.connectTCP(host, port, timeout).addCallback(self.controlConnectionMade).addErrback(self.connectionFailed)
32 self.deferred = defer.Deferred()
34 def controlConnectionMade(self, ftpclient):
35 # We need the client locally
36 self.ftpclient = ftpclient
38 # Try to fetch filesize
42 def sizeRcvd(self, msgs):
44 code, msg = msgs[0].split()
46 self.totallength = int(msg)
47 # We know the size, so start fetching
50 # Error while reading size, try to list it
53 def ftpFetchSize(self):
54 d = self.ftpclient.queueStringCommand('SIZE ' + self.path)
55 d.addCallback(self.sizeRcvd).addErrback(self.ftpFetchList)
58 def listRcvd(self, *args):
59 # Quit if file not found
60 if not len(self.filelist.files):
61 self.connectionFailed()
64 self.totallength = self.filelist.files[0]['size']
69 # We know the size, so start fetching
72 def ftpFetchList(self, *args, **kwargs):
73 self.filelist = FTPFileListProtocol()
74 d = self.ftpclient.list(self.path, self.filelist)
75 d.addCallback(self.listRcvd).addErrback(self.connectionFailed)
79 file = open(self.filename, 'ab')
81 file = open(self.filename, 'wb')
83 return (file, file.tell())
85 def ftpFetchFile(self):
91 self.file, offset = self.openFile()
93 self.connectionFailed()
96 offset = self.resume and offset or 0
98 d = self.ftpclient.retrieveFile(self.path, self, offset = offset)
99 d.addCallback(self.ftpFinish).addErrback(self.connectionFailed)
101 def dataReceived(self, data):
106 self.file.seek(0, SEEK_END)
108 self.file.write(data)
110 self.connectionFailed()
112 def ftpFinish(self, code = 0, message = None):
113 self.ftpclient.quit()
114 if self.file is not None:
116 self.deferred.callback(code)
118 def connectionFailed(self, reason = None):
119 if self.file is not None:
121 self.deferred.errback(reason)