34
36
from warnings import warn
37
39
from bzrlib.transport import Transport
38
40
from bzrlib.errors import (TransportNotPossible, TransportError,
39
41
NoSuchFile, FileExists)
40
from bzrlib.trace import mutter
42
from bzrlib.trace import mutter, warning
46
def _find_FTP(hostname, username, password, is_active):
47
"""Find an ftplib.FTP instance attached to this triplet."""
48
key = "%s|%s|%s|%s" % (hostname, username, password, is_active)
49
if key not in _FTP_cache:
50
mutter("Constructing FTP instance against %r" % key)
51
_FTP_cache[key] = ftplib.FTP(hostname, username, password)
52
_FTP_cache[key].set_pasv(not is_active)
53
return _FTP_cache[key]
56
class FtpTransportError(TransportError):
43
60
class FtpStatResult(object):
179
199
except ftplib.error_perm, e:
180
raise NoSuchFile(self.abspath(relpath), extra=extra)
200
raise NoSuchFile(self.abspath(relpath), extra=str(e))
201
except ftplib.error_temp, e:
202
if retries > _number_of_retries:
203
raise TransportError(msg="FTP temporary error during GET %s. Aborting."
204
% self.abspath(relpath),
207
warning("FTP temporary error: %s. Retrying." % str(e))
208
self._FTP_instance = None
209
return self.get(relpath, decode, retries+1)
211
if retries > _number_of_retries:
212
raise TransportError("FTP control connection closed during GET %s."
213
% self.abspath(relpath),
216
warning("FTP control connection closed. Trying to reopen.")
217
time.sleep(_sleep_between_retries)
218
self._FTP_instance = None
219
return self.get(relpath, decode, retries+1)
182
def put(self, relpath, fp, mode=None):
221
def put(self, relpath, fp, mode=None, retries=0):
183
222
"""Copy the file-like or string object into the location.
185
224
:param relpath: Location to put the contents, relative to base.
186
:param f: File-like or string object.
187
TODO: jam 20051215 This should be an atomic put, not overwritting files in place
225
:param fp: File-like or string object.
226
:param retries: Number of retries after temporary failures so far
188
229
TODO: jam 20051215 ftp as a protocol seems to support chmod, but ftplib does not
231
tmp_abspath = '%s.tmp.%.9f.%d.%d' % (self._abspath(relpath), time.time(),
232
os.getpid(), random.randint(0,0x7FFFFFFF))
190
233
if not hasattr(fp, 'read'):
191
234
fp = StringIO(fp)
193
236
mutter("FTP put: %s" % self._abspath(relpath))
194
237
f = self._get_FTP()
195
f.storbinary('STOR '+self._abspath(relpath), fp, 8192)
239
f.storbinary('STOR '+tmp_abspath, fp)
240
f.rename(tmp_abspath, self._abspath(relpath))
241
except (ftplib.error_temp,EOFError), e:
242
warning("Failure during ftp PUT. Deleting temporary file.")
244
f.delete(tmp_abspath)
246
warning("Failed to delete temporary file on the server.\nFile: %s"
196
250
except ftplib.error_perm, e:
197
raise TransportError(orig_error=e)
251
if "no such file" in str(e).lower():
252
raise NoSuchFile("Error storing %s: %s"
253
% (self.abspath(relpath), str(e)), extra=e)
255
raise FtpTransportError(orig_error=e)
256
except ftplib.error_temp, e:
257
if retries > _number_of_retries:
258
raise TransportError("FTP temporary error during PUT %s. Aborting."
259
% self.abspath(relpath), orig_error=e)
261
warning("FTP temporary error: %s. Retrying." % str(e))
262
self._FTP_instance = None
263
self.put(relpath, fp, mode, retries+1)
265
if retries > _number_of_retries:
266
raise TransportError("FTP control connection closed during PUT %s."
267
% self.abspath(relpath), orig_error=e)
269
warning("FTP control connection closed. Trying to reopen.")
270
time.sleep(_sleep_between_retries)
271
self._FTP_instance = None
272
self.put(relpath, fp, mode, retries+1)
199
275
def mkdir(self, relpath, mode=None):
200
276
"""Create a directory at the given path."""