| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
|
|---|
| 25 |
|
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 |
|
|---|
| 31 |
|
|---|
| 32 |
""" |
|---|
| 33 |
ftp_file.py - support for file-like objects on FTP servers |
|---|
| 34 |
""" |
|---|
| 35 |
|
|---|
| 36 |
|
|---|
| 37 |
|
|---|
| 38 |
import ftp_error |
|---|
| 39 |
|
|---|
| 40 |
|
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
|
|---|
| 46 |
|
|---|
| 47 |
|
|---|
| 48 |
|
|---|
| 49 |
|
|---|
| 50 |
_crlf_to_python_linesep = lambda text: text.replace('\r', '') |
|---|
| 51 |
|
|---|
| 52 |
|
|---|
| 53 |
_python_to_crlf_linesep = lambda text: text.replace('\n', '\r\n') |
|---|
| 54 |
|
|---|
| 55 |
|
|---|
| 56 |
|
|---|
| 57 |
|
|---|
| 58 |
class _XReadlines(object): |
|---|
| 59 |
"""Represents `xreadline` objects for ASCII transfers.""" |
|---|
| 60 |
def __init__(self, ftp_file): |
|---|
| 61 |
self._ftp_file = ftp_file |
|---|
| 62 |
self._next_index = 0 |
|---|
| 63 |
|
|---|
| 64 |
def __getitem__(self, index): |
|---|
| 65 |
"""Return next line with specified index.""" |
|---|
| 66 |
if index != self._next_index: |
|---|
| 67 |
raise RuntimeError( "_XReadline access index " |
|---|
| 68 |
"out of order (expected %s but got %s)" % |
|---|
| 69 |
(self._next_index, index) ) |
|---|
| 70 |
line = self._ftp_file.readline() |
|---|
| 71 |
if not line: |
|---|
| 72 |
raise IndexError("_XReadline object out of data") |
|---|
| 73 |
self._next_index += 1 |
|---|
| 74 |
return line |
|---|
| 75 |
|
|---|
| 76 |
|
|---|
| 77 |
class _FTPFile(object): |
|---|
| 78 |
""" |
|---|
| 79 |
Represents a file-like object connected to an FTP host. File and |
|---|
| 80 |
socket are closed appropriately if the `close` operation is |
|---|
| 81 |
requested. |
|---|
| 82 |
""" |
|---|
| 83 |
def __init__(self, host): |
|---|
| 84 |
"""Construct the file(-like) object.""" |
|---|
| 85 |
self._host = host |
|---|
| 86 |
self._session = host._session |
|---|
| 87 |
|
|---|
| 88 |
self.closed = True |
|---|
| 89 |
|
|---|
| 90 |
def _open(self, path, mode): |
|---|
| 91 |
"""Open the remote file with given path name and mode.""" |
|---|
| 92 |
|
|---|
| 93 |
if 'a' in mode: |
|---|
| 94 |
raise ftp_error.FTPIOError("append mode not supported") |
|---|
| 95 |
if mode not in ('r', 'rb', 'w', 'wb'): |
|---|
| 96 |
raise ftp_error.FTPIOError("invalid mode '%s'" % mode) |
|---|
| 97 |
|
|---|
| 98 |
self._bin_mode = 'b' in mode |
|---|
| 99 |
self._read_mode = 'r' in mode |
|---|
| 100 |
|
|---|
| 101 |
transfer_type = ('A', 'I')[self._bin_mode] |
|---|
| 102 |
command = 'TYPE %s' % transfer_type |
|---|
| 103 |
ftp_error._try_with_ioerror(self._session.voidcmd, command) |
|---|
| 104 |
|
|---|
| 105 |
command_type = ('STOR', 'RETR')[self._read_mode] |
|---|
| 106 |
command = '%s %s' % (command_type, path) |
|---|
| 107 |
|
|---|
| 108 |
|
|---|
| 109 |
if not 'b' in mode: |
|---|
| 110 |
mode = mode + 'b' |
|---|
| 111 |
|
|---|
| 112 |
self._conn = ftp_error._try_with_ioerror( |
|---|
| 113 |
self._session.transfercmd, command) |
|---|
| 114 |
self._fo = self._conn.makefile(mode) |
|---|
| 115 |
|
|---|
| 116 |
|
|---|
| 117 |
|
|---|
| 118 |
self.closed = False |
|---|
| 119 |
|
|---|
| 120 |
|
|---|
| 121 |
|
|---|
| 122 |
|
|---|
| 123 |
|
|---|
| 124 |
|
|---|
| 125 |
|
|---|
| 126 |
|
|---|
| 127 |
def read(self, *args): |
|---|
| 128 |
"""Return read bytes, normalized if in text transfer mode.""" |
|---|
| 129 |
data = self._fo.read(*args) |
|---|
| 130 |
if self._bin_mode: |
|---|
| 131 |
return data |
|---|
| 132 |
data = _crlf_to_python_linesep(data) |
|---|
| 133 |
if args == (): |
|---|
| 134 |
return data |
|---|
| 135 |
|
|---|
| 136 |
|
|---|
| 137 |
|
|---|
| 138 |
|
|---|
| 139 |
|
|---|
| 140 |
|
|---|
| 141 |
|
|---|
| 142 |
|
|---|
| 143 |
|
|---|
| 144 |
|
|---|
| 145 |
wanted_size = args[0] |
|---|
| 146 |
chunks = [data] |
|---|
| 147 |
current_size = len(data) |
|---|
| 148 |
while current_size < wanted_size: |
|---|
| 149 |
|
|---|
| 150 |
|
|---|
| 151 |
more_data = self._fo.read(wanted_size - current_size) |
|---|
| 152 |
if not more_data: |
|---|
| 153 |
break |
|---|
| 154 |
more_data = _crlf_to_python_linesep(more_data) |
|---|
| 155 |
|
|---|
| 156 |
chunks.append(more_data) |
|---|
| 157 |
current_size += len(more_data) |
|---|
| 158 |
return ''.join(chunks) |
|---|
| 159 |
|
|---|
| 160 |
def readline(self, *args): |
|---|
| 161 |
"""Return one read line, normalized if in text transfer mode.""" |
|---|
| 162 |
data = self._fo.readline(*args) |
|---|
| 163 |
if self._bin_mode: |
|---|
| 164 |
return data |
|---|
| 165 |
|
|---|
| 166 |
if data.endswith('\r'): |
|---|
| 167 |
data = data + self.read(1) |
|---|
| 168 |
return _crlf_to_python_linesep(data) |
|---|
| 169 |
|
|---|
| 170 |
def readlines(self, *args): |
|---|
| 171 |
"""Return read lines, normalized if in text transfer mode.""" |
|---|
| 172 |
lines = self._fo.readlines(*args) |
|---|
| 173 |
if self._bin_mode: |
|---|
| 174 |
return lines |
|---|
| 175 |
|
|---|
| 176 |
for index, line in enumerate(lines): |
|---|
| 177 |
lines[index] = _crlf_to_python_linesep(line) |
|---|
| 178 |
return lines |
|---|
| 179 |
|
|---|
| 180 |
def xreadlines(self): |
|---|
| 181 |
""" |
|---|
| 182 |
Return an appropriate `xreadlines` object with built-in line |
|---|
| 183 |
separator conversion support. |
|---|
| 184 |
""" |
|---|
| 185 |
if self._bin_mode: |
|---|
| 186 |
return self._fo.xreadlines() |
|---|
| 187 |
return _XReadlines(self) |
|---|
| 188 |
|
|---|
| 189 |
def __iter__(self): |
|---|
| 190 |
"""Return a file iterator.""" |
|---|
| 191 |
return self |
|---|
| 192 |
|
|---|
| 193 |
def next(self): |
|---|
| 194 |
""" |
|---|
| 195 |
Return the next line or raise `StopIteration`, if there are |
|---|
| 196 |
no more. |
|---|
| 197 |
""" |
|---|
| 198 |
|
|---|
| 199 |
line = self.readline() |
|---|
| 200 |
if line: |
|---|
| 201 |
return line |
|---|
| 202 |
else: |
|---|
| 203 |
raise StopIteration |
|---|
| 204 |
|
|---|
| 205 |
def write(self, data): |
|---|
| 206 |
"""Write data to file. Do linesep conversion for text mode.""" |
|---|
| 207 |
if not self._bin_mode: |
|---|
| 208 |
data = _python_to_crlf_linesep(data) |
|---|
| 209 |
self._fo.write(data) |
|---|
| 210 |
|
|---|
| 211 |
def writelines(self, lines): |
|---|
| 212 |
"""Write lines to file. Do linesep conversion for text mode.""" |
|---|
| 213 |
if self._bin_mode: |
|---|
| 214 |
self._fo.writelines(lines) |
|---|
| 215 |
return |
|---|
| 216 |
|
|---|
| 217 |
|
|---|
| 218 |
|
|---|
| 219 |
for line in lines: |
|---|
| 220 |
self._fo.write(_python_to_crlf_linesep(line)) |
|---|
| 221 |
|
|---|
| 222 |
|
|---|
| 223 |
|
|---|
| 224 |
|
|---|
| 225 |
def __getattr__(self, attr_name): |
|---|
| 226 |
""" |
|---|
| 227 |
Handle requests for attributes unknown to `_FTPFile` objects: |
|---|
| 228 |
delegate the requests to the contained file object. |
|---|
| 229 |
""" |
|---|
| 230 |
if attr_name in ('flush isatty fileno seek tell ' |
|---|
| 231 |
'truncate name softspace'.split()): |
|---|
| 232 |
return getattr(self._fo, attr_name) |
|---|
| 233 |
raise AttributeError( |
|---|
| 234 |
"'FTPFile' object has no attribute '%s'" % attr_name) |
|---|
| 235 |
|
|---|
| 236 |
def close(self): |
|---|
| 237 |
"""Close the `FTPFile`.""" |
|---|
| 238 |
if not self.closed: |
|---|
| 239 |
self._fo.close() |
|---|
| 240 |
ftp_error._try_with_ioerror(self._conn.close) |
|---|
| 241 |
try: |
|---|
| 242 |
ftp_error._try_with_ioerror(self._session.voidresp) |
|---|
| 243 |
except ftp_error.FTPIOError, exception: |
|---|
| 244 |
|
|---|
| 245 |
|
|---|
| 246 |
error_code = str(exception).split()[0] |
|---|
| 247 |
if error_code not in ("426", "450"): |
|---|
| 248 |
raise |
|---|
| 249 |
self.closed = True |
|---|
| 250 |
|
|---|