1818from posix.fcntl cimport O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY
1919
2020from cpython.bytes cimport PyBytes_AS_STRING
21+ from cpython.mem cimport PyMem_Free, PyMem_Malloc
2122
2223from pylibsshext.errors cimport LibsshSFTPException
2324from pylibsshext.session cimport get_libssh_session
2425
2526
27+ SFTP_MAX_CHUNK = 32 _768 # 32kB
28+
29+
2630MSG_MAP = {
2731 sftp.SSH_FX_OK: " No error" ,
2832 sftp.SSH_FX_EOF: " End-of-file encountered" ,
@@ -63,7 +67,7 @@ cdef class SFTP:
6367 rf = sftp.sftp_open(self ._libssh_sftp_session, remote_file_b, O_WRONLY | O_CREAT | O_TRUNC, sftp.S_IRWXU)
6468 if rf is NULL :
6569 raise LibsshSFTPException(" Opening remote file [%s ] for write failed with error [%s ]" % (remote_file, self ._get_sftp_error_str()))
66- buffer = f.read(1024 )
70+ buffer = f.read(SFTP_MAX_CHUNK )
6771
6872 while buffer != b" " :
6973 length = len (buffer )
@@ -76,38 +80,54 @@ cdef class SFTP:
7680 self ._get_sftp_error_str(),
7781 )
7882 )
79- buffer = f.read(1024 )
83+ buffer = f.read(SFTP_MAX_CHUNK )
8084 sftp.sftp_close(rf)
8185
8286 def get (self , remote_file , local_file ):
8387 cdef sftp.sftp_file rf
84- cdef char read_buffer[1024 ]
88+ cdef char * read_buffer = NULL
89+ cdef sftp.sftp_attributes attrs
8590
8691 remote_file_b = remote_file
8792 if isinstance (remote_file_b, unicode ):
8893 remote_file_b = remote_file.encode(" utf-8" )
8994
95+ attrs = sftp.sftp_stat(self ._libssh_sftp_session, remote_file_b)
96+ if attrs is NULL :
97+ raise LibsshSFTPException(" Failed to stat the remote file [%s ]. Error: [%s ]"
98+ % (remote_file, self ._get_sftp_error_str()))
99+ file_size = attrs.size
100+
90101 rf = sftp.sftp_open(self ._libssh_sftp_session, remote_file_b, O_RDONLY, sftp.S_IRWXU)
91102 if rf is NULL :
92103 raise LibsshSFTPException(" Opening remote file [%s ] for read failed with error [%s ]" % (remote_file, self ._get_sftp_error_str()))
93104
94- with open (local_file, ' wb' ) as f:
95- while True :
96- file_data = sftp.sftp_read(rf, < void * > read_buffer, sizeof(char ) * 1024 )
97- if file_data == 0 :
98- break
99- elif file_data < 0 :
100- sftp.sftp_close(rf)
101- raise LibsshSFTPException(" Reading data from remote file [%s ] failed with error [%s ]"
102- % (remote_file, self ._get_sftp_error_str()))
103-
104- bytes_written = f.write(read_buffer[:file_data])
105- if bytes_written and file_data != bytes_written:
106- sftp.sftp_close(rf)
107- raise LibsshSFTPException(" Number of bytes [%s ] read from remote file [%s ]"
108- " does not match number of bytes [%s ] written to local file [%s ]"
109- " due to error [%s ]"
110- % (file_data, remote_file, bytes_written, local_file, self ._get_sftp_error_str()))
105+ try :
106+ with open (local_file, ' wb' ) as f:
107+ buffer_size = min (SFTP_MAX_CHUNK, file_size)
108+ read_buffer = < char * > PyMem_Malloc(buffer_size)
109+ if read_buffer is NULL :
110+ raise LibsshSFTPException(" Memory allocation error" )
111+
112+ while True :
113+ file_data = sftp.sftp_read(rf, < void * > read_buffer, sizeof(char ) * buffer_size)
114+ if file_data == 0 :
115+ break
116+ elif file_data < 0 :
117+ sftp.sftp_close(rf)
118+ raise LibsshSFTPException(" Reading data from remote file [%s ] failed with error [%s ]"
119+ % (remote_file, self ._get_sftp_error_str()))
120+
121+ bytes_written = f.write(read_buffer[:file_data])
122+ if bytes_written and file_data != bytes_written:
123+ sftp.sftp_close(rf)
124+ raise LibsshSFTPException(" Number of bytes [%s ] read from remote file [%s ]"
125+ " does not match number of bytes [%s ] written to local file [%s ]"
126+ " due to error [%s ]"
127+ % (file_data, remote_file, bytes_written, local_file, self ._get_sftp_error_str()))
128+ finally :
129+ if read_buffer is not NULL :
130+ PyMem_Free(read_buffer)
111131 sftp.sftp_close(rf)
112132
113133 def close (self ):
0 commit comments