diff --git a/Lib/ftplib.py b/Lib/ftplib.py --- a/Lib/ftplib.py +++ b/Lib/ftplib.py @@ -39,6 +39,7 @@ import os import sys import socket +import select from socket import _GLOBAL_DEFAULT_TIMEOUT __all__ = ["FTP","Netrc"] @@ -455,7 +456,8 @@ callback(line) return self.voidresp() - def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None): + def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None, + use_sendfile=False): """Store a file in binary mode. A new port is created for you. Args: @@ -472,11 +474,25 @@ """ self.voidcmd('TYPE I') with self.transfercmd(cmd, rest) as conn: - while 1: - buf = fp.read(blocksize) - if not buf: break - conn.sendall(buf) - if callback: callback(buf) + if use_sendfile: + if callback: + raise ValueError("can't use callback arg with sendfile") + offset = 0 + sockno = conn.fileno() + fileno = fp.fileno() + while 1: + # block until socket is writable + select.select([], [sockno], []) + sent = os.sendfile(sockno, fileno, offset, blocksize) + if sent == 0: + break + offset += sent + else: + while 1: + buf = fp.read(blocksize) + if not buf: break + conn.sendall(buf) + if callback: callback(buf) return self.voidresp() def storlines(self, cmd, fp, callback=None): diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -11,6 +11,7 @@ import errno import os import time +import unittest try: import ssl except ImportError: @@ -18,7 +19,7 @@ from unittest import TestCase from test import support -from test.support import HOST +from test.support import HOST, TESTFN threading = support.import_module('threading') # the dummy data returned by server over the data channel when @@ -577,6 +578,17 @@ self.client.storbinary('stor', f, rest=r) self.assertEqual(self.server.handler_instance.rest, str(r)) + @unittest.skipUnless(hasattr(os, 'sendfile'), 'os.sendfile() not available') + def test_storbinary_sendfile(self): + with open(TESTFN, 'wb+') as f: + test_data = 'abcde12345\r\n' * 100000 + f.write(test_data.encode('ascii')) + f = open(TESTFN, 'rb') + f.seek(0) + self.client.storbinary('stor', f, use_sendfile=1) + self.assertEqual(self.server.handler_instance.last_received_data, + test_data) + def test_storlines(self): f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) self.client.storlines('stor', f) @@ -802,6 +814,9 @@ self.client.auth() self.client.prot_p() + def test_storbinary_sendfile(self): + pass + class TestTLS_FTPClass(TestCase): """Specific TLS_FTP class tests.""" @@ -995,6 +1010,7 @@ support.run_unittest(*tests) finally: support.threading_cleanup(*thread_info) + support.unlink(TESTFN) if __name__ == '__main__':