18
18
from posix.fcntl cimport O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY
19
19
20
20
from cpython.bytes cimport PyBytes_AS_STRING
21
+ from cpython.mem cimport PyMem_Free, PyMem_Malloc
21
22
22
23
from pylibsshext.errors cimport LibsshSFTPException
23
24
from pylibsshext.session cimport get_libssh_session
24
25
25
26
27
+ SFTP_MAX_CHUNK = 32 _768 # 32kB
28
+
29
+
26
30
MSG_MAP = {
27
31
sftp.SSH_FX_OK: " No error" ,
28
32
sftp.SSH_FX_EOF: " End-of-file encountered" ,
@@ -63,7 +67,7 @@ cdef class SFTP:
63
67
rf = sftp.sftp_open(self ._libssh_sftp_session, remote_file_b, O_WRONLY | O_CREAT | O_TRUNC, sftp.S_IRWXU)
64
68
if rf is NULL :
65
69
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 )
67
71
68
72
while buffer != b" " :
69
73
length = len (buffer )
@@ -76,38 +80,54 @@ cdef class SFTP:
76
80
self ._get_sftp_error_str(),
77
81
)
78
82
)
79
- buffer = f.read(1024 )
83
+ buffer = f.read(SFTP_MAX_CHUNK )
80
84
sftp.sftp_close(rf)
81
85
82
86
def get (self , remote_file , local_file ):
83
87
cdef sftp.sftp_file rf
84
- cdef char read_buffer[1024 ]
88
+ cdef char * read_buffer = NULL
89
+ cdef sftp.sftp_attributes attrs
85
90
86
91
remote_file_b = remote_file
87
92
if isinstance (remote_file_b, unicode ):
88
93
remote_file_b = remote_file.encode(" utf-8" )
89
94
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
+
90
101
rf = sftp.sftp_open(self ._libssh_sftp_session, remote_file_b, O_RDONLY, sftp.S_IRWXU)
91
102
if rf is NULL :
92
103
raise LibsshSFTPException(" Opening remote file [%s ] for read failed with error [%s ]" % (remote_file, self ._get_sftp_error_str()))
93
104
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)
111
131
sftp.sftp_close(rf)
112
132
113
133
def close (self ):
0 commit comments