~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/sftp.py

  • Committer: John Arbash Meinel
  • Date: 2005-11-05 08:37:01 UTC
  • mto: (1185.50.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1518.
  • Revision ID: john@arbash-meinel.com-20051105083701-efc57f6197d8b137
Added a form of locking to sftp branches. Refactored _sftp_open_exclusive to take a relative path

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
 
29
29
from bzrlib.errors import (FileExists, 
30
30
                           TransportNotPossible, NoSuchFile, NonRelativePath,
31
 
                           TransportError)
 
31
                           TransportError,
 
32
                           LockError)
32
33
from bzrlib.config import config_dir
33
34
from bzrlib.trace import mutter, warning, error
34
35
from bzrlib.transport import Transport, register_transport
89
90
class SFTPTransportError (TransportError):
90
91
    pass
91
92
 
 
93
class SFTPLock(object):
 
94
    """This fakes a lock in a remote location."""
 
95
    __slots__ = ['path', 'lock_path', 'lock_file', 'transport']
 
96
    def __init__(self, path, transport):
 
97
        assert isinstance(transport, SFTPTransport)
 
98
 
 
99
        self.lock_file = None
 
100
        self.path = path
 
101
        self.lock_path = path + '.write-lock'
 
102
        self.transport = transport
 
103
        try:
 
104
            self.lock_file = transport._sftp_open_exclusive(self.lock_path)
 
105
        except FileExists:
 
106
            raise LockError('File %r already locked' % (self.path,))
 
107
 
 
108
    def __del__(self):
 
109
        """Should this warn, or actually try to cleanup?"""
 
110
        if self.lock_file:
 
111
            warn("SFTPLock %r not explicitly unlocked" % (self.path,))
 
112
            self.unlock()
 
113
 
 
114
    def unlock(self):
 
115
        if not self.lock_file:
 
116
            return
 
117
        self.lock_file.close()
 
118
        self.lock_file = None
 
119
        try:
 
120
            self.transport.delete(self.lock_path)
 
121
        except (NoSuchFile,):
 
122
            # What specific errors should we catch here?
 
123
            pass
92
124
 
93
125
class SFTPTransport (Transport):
94
126
    """
214
246
        :param relpath: Location to put the contents, relative to base.
215
247
        :param f:       File-like or string object.
216
248
        """
217
 
        finalpath = self._abspath(relpath)
218
 
        tmp_path = '%s.tmp.%.9f.%d.%d' % (finalpath, time.time(),
219
 
                   os.getpid(), random.randint(0,0x7FFFFFFF))
220
 
        fout = self._sftp_open_exclusive(tmp_path, relpath)
 
249
        final_path = self._abspath(relpath)
 
250
        tmp_relpath = '%s.tmp.%.9f.%d.%d' % (relpath, time.time(),
 
251
                        os.getpid(), random.randint(0,0x7FFFFFFF))
 
252
        tmp_abspath = self._abspath(tmp_relpath)
 
253
        fout = self._sftp_open_exclusive(tmp_relpath)
221
254
 
222
255
        try:
223
256
            try:
232
265
            # but don't let another exception mess things up
233
266
            try:
234
267
                fout.close()
235
 
                self._sftp.remove(tmp_path)
 
268
                self._sftp.remove(tmp_abspath)
236
269
            except:
237
270
                pass
238
271
            raise e
239
272
        else:
240
273
            try:
241
 
                self._sftp.rename(tmp_path, finalpath)
 
274
                self._sftp.rename(tmp_abspath, final_path)
242
275
            except IOError, e:
243
276
                self._translate_io_exception(e, relpath)
244
277
            except paramiko.SSHException, x:
353
386
    def lock_read(self, relpath):
354
387
        """
355
388
        Lock the given file for shared (read) access.
356
 
        :return: A lock object, which should be passed to Transport.unlock()
 
389
        :return: A lock object, which has an unlock() member function
357
390
        """
358
391
        # FIXME: there should be something clever i can do here...
359
392
        class BogusLock(object):
368
401
        Lock the given file for exclusive (write) access.
369
402
        WARNING: many transports do not support this, so trying avoid using it
370
403
 
371
 
        :return: A lock object, which should be passed to Transport.unlock()
 
404
        :return: A lock object, which has an unlock() member function
372
405
        """
373
406
        # This is a little bit bogus, but basically, we create a file
374
407
        # which should not already exist, and if it does, we assume
375
408
        # that there is a lock, and if it doesn't, the we assume
376
409
        # that we have taken the lock.
377
 
        class BogusLock(object):
378
 
            def __init__(self, path):
379
 
                self.path = path
380
 
            def unlock(self):
381
 
                pass
382
 
        return BogusLock(relpath)
 
410
        return SFTPLock(relpath, self)
383
411
 
384
412
 
385
413
    def _unparse_url(self, path=None):
503
531
            pass
504
532
        return False
505
533
 
506
 
    def _sftp_open_exclusive(self, path, relpath=None):
 
534
    def _sftp_open_exclusive(self, relpath):
507
535
        """Open a remote path exclusively.
508
536
 
509
537
        SFTP supports O_EXCL (SFTP_FLAG_EXCL), which fails if
514
542
        WARNING: This breaks the SFTPClient abstraction, so it
515
543
        could easily break against an updated version of paramiko.
516
544
 
517
 
        :param path: This should be an absolute remote path.
518
 
        :param relpath: Just a parameter so we can throw better exceptions.
 
545
        :param relpath: The relative path, where the file should be opened
519
546
        """
520
 
        if relpath is None:
521
 
            relpath = path
 
547
        path = self._abspath(relpath)
522
548
        attr = SFTPAttributes()
523
549
        mode = (SFTP_FLAG_WRITE | SFTP_FLAG_CREATE 
524
550
                | SFTP_FLAG_TRUNC | SFTP_FLAG_EXCL)