diff --git a/oauth2client/locked_file.py b/oauth2client/locked_file.py index c78410c..8a7aff5 100644 --- a/oauth2client/locked_file.py +++ b/oauth2client/locked_file.py @@ -143,6 +143,7 @@ class _PosixOpener(_Opener): try: import fcntl + class _FcntlOpener(_Opener): """Open, lock, and unlock a file using fcntl.lockf.""" @@ -192,7 +193,6 @@ try: return time.sleep(delay) - def unlock_and_close(self): """Close and unlock the file using the fcntl.lockf primitive.""" if self._locked: @@ -204,25 +204,114 @@ except ImportError: _FcntlOpener = None +try: + import pywintypes + import win32con + import win32file + + class _Win32Opener(_Opener): + """Open, lock, and unlock a file using windows primitives.""" + + # Error #33: + # 'The process cannot access the file because another process' + FILE_IN_USE_ERROR = 33 + + # Error #158: + # 'The segment is already unlocked.' + FILE_ALREADY_UNLOCKED_ERROR = 158 + + def open_and_lock(self, timeout, delay): + """Open the file and lock it. + + Args: + timeout: float, How long to try to lock for. + delay: float, How long to wait between retries + + Raises: + AlreadyLockedException: if the lock is already acquired. + IOError: if the open fails. + """ + if self._locked: + raise AlreadyLockedException('File %s is already locked' % + self._filename) + start_time = time.time() + + try: + self._fh = open(self._filename, self._mode) + except IOError, e: + # If we can't access with _mode, try _fallback_mode and don't lock. + if e.errno == errno.EACCES: + self._fh = open(self._filename, self._fallback_mode) + return + + # We opened in _mode, try to lock the file. + while True: + try: + hfile = win32file._get_osfhandle(self._fh.fileno()) + win32file.LockFileEx( + hfile, + (win32con.LOCKFILE_FAIL_IMMEDIATELY| + win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000, + pywintypes.OVERLAPPED()) + self._locked = True + return + except pywintypes.error, e: + if timeout == 0: + raise e + + # If the error is not that the file is already in use, raise. + if e[0] != _Win32Opener.FILE_IN_USE_ERROR: + raise + + # We could not acquire the lock. Try again. + if (time.time() - start_time) >= timeout: + logger.warn('Could not lock %s in %s seconds' % ( + self._filename, timeout)) + if self._fh: + self._fh.close() + self._fh = open(self._filename, self._fallback_mode) + return + time.sleep(delay) + + def unlock_and_close(self): + """Close and unlock the file using the win32 primitive.""" + if self._locked: + try: + hfile = win32file._get_osfhandle(self._fh.fileno()) + win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED()) + except pywintypes.error, e: + if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR: + raise + self._locked = False + if self._fh: + self._fh.close() +except ImportError: + _Win32Opener = None + + class LockedFile(object): """Represent a file that has exclusive access.""" - def __init__(self, filename, mode, fallback_mode, use_fcntl=True): + def __init__(self, filename, mode, fallback_mode, use_native_locking=True): """Construct a LockedFile. Args: filename: string, The path of the file to open. mode: string, The mode to try to open the file with. fallback_mode: string, The mode to use if locking fails. - use_fcntl: string, Whether or not fcntl-based locking should be used. + use_native_locking: bool, Whether or not fcntl/win32 locking is used. """ - if not use_fcntl: - self._opener = _PosixOpener(filename, mode, fallback_mode) - else: + opener = None + if not opener and use_native_locking: + if _Win32Opener: + opener = _Win32Opener(filename, mode, fallback_mode) if _FcntlOpener: - self._opener = _FcntlOpener(filename, mode, fallback_mode) - else: - self._opener = _PosixOpener(filename, mode, fallback_mode) + opener = _FcntlOpener(filename, mode, fallback_mode) + + if not opener: + opener = _PosixOpener(filename, mode, fallback_mode) + + self._opener = opener def filename(self): """Return the filename we were constructed with."""