228 lines
5.4 KiB
Python
228 lines
5.4 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Cross-platform (posix/nt) API for flock-style file locking.
|
|
|
|
Synopsis::
|
|
|
|
import portalocker
|
|
file = open(\"somefile\", \"r+\")
|
|
portalocker.lock(file, portalocker.LOCK_EX)
|
|
file.seek(12)
|
|
file.write(\"foo\")
|
|
file.close()
|
|
|
|
If you know what you're doing, you may choose to::
|
|
|
|
portalocker.unlock(file)
|
|
|
|
before closing the file, but why?
|
|
|
|
Methods::
|
|
|
|
lock( file, flags )
|
|
unlock( file )
|
|
|
|
Constants::
|
|
|
|
LOCK_EX - exclusive lock
|
|
LOCK_SH - shared lock
|
|
LOCK_NB - don't lock when locking
|
|
|
|
Original
|
|
---------
|
|
http://code.activestate.com/recipes/65203-portalocker-cross-platform-posixnt-api-for-flock-s/
|
|
|
|
I learned the win32 technique for locking files from sample code
|
|
provided by John Nielsen <nielsenjf@my-deja.com> in the documentation
|
|
that accompanies the win32 modules.
|
|
|
|
Author: Jonathan Feinberg <jdf@pobox.com>
|
|
|
|
|
|
Roundup Changes
|
|
---------------
|
|
2012-11-28 (anatoly techtonik)
|
|
- Ported to ctypes
|
|
- Dropped support for Win95, Win98 and WinME
|
|
- Added return result
|
|
|
|
Web2py Changes
|
|
--------------
|
|
2016-07-28 (niphlod)
|
|
- integrated original recipe, web2py's GAE warnings and roundup in a unique
|
|
solution
|
|
|
|
"""
|
|
import sys
|
|
import logging
|
|
|
|
PY2 = sys.version_info[0] == 2
|
|
|
|
logger = logging.getLogger("pydal")
|
|
|
|
|
|
os_locking = None
|
|
try:
|
|
import google.appengine
|
|
os_locking = 'gae'
|
|
except:
|
|
try:
|
|
import fcntl
|
|
os_locking = 'posix'
|
|
except:
|
|
try:
|
|
import msvcrt
|
|
import ctypes
|
|
from ctypes.wintypes import BOOL, DWORD, HANDLE
|
|
from ctypes import windll
|
|
os_locking = 'windows'
|
|
except:
|
|
pass
|
|
|
|
if os_locking == 'windows':
|
|
LOCK_SH = 0 # the default
|
|
LOCK_NB = 0x1 # LOCKFILE_FAIL_IMMEDIATELY
|
|
LOCK_EX = 0x2 # LOCKFILE_EXCLUSIVE_LOCK
|
|
|
|
# --- the code is taken from pyserial project ---
|
|
#
|
|
# detect size of ULONG_PTR
|
|
def is_64bit():
|
|
return ctypes.sizeof(ctypes.c_ulong) != ctypes.sizeof(ctypes.c_void_p)
|
|
|
|
if is_64bit():
|
|
ULONG_PTR = ctypes.c_int64
|
|
else:
|
|
ULONG_PTR = ctypes.c_ulong
|
|
PVOID = ctypes.c_void_p
|
|
|
|
# --- Union inside Structure by stackoverflow:3480240 ---
|
|
class _OFFSET(ctypes.Structure):
|
|
_fields_ = [
|
|
('Offset', DWORD),
|
|
('OffsetHigh', DWORD)]
|
|
|
|
class _OFFSET_UNION(ctypes.Union):
|
|
_anonymous_ = ['_offset']
|
|
_fields_ = [
|
|
('_offset', _OFFSET),
|
|
('Pointer', PVOID)]
|
|
|
|
class OVERLAPPED(ctypes.Structure):
|
|
_anonymous_ = ['_offset_union']
|
|
_fields_ = [
|
|
('Internal', ULONG_PTR),
|
|
('InternalHigh', ULONG_PTR),
|
|
('_offset_union', _OFFSET_UNION),
|
|
('hEvent', HANDLE)]
|
|
|
|
LPOVERLAPPED = ctypes.POINTER(OVERLAPPED)
|
|
|
|
# --- Define function prototypes for extra safety ---
|
|
LockFileEx = windll.kernel32.LockFileEx
|
|
LockFileEx.restype = BOOL
|
|
LockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
|
UnlockFileEx = windll.kernel32.UnlockFileEx
|
|
UnlockFileEx.restype = BOOL
|
|
UnlockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED]
|
|
|
|
def lock(file, flags):
|
|
hfile = msvcrt.get_osfhandle(file.fileno())
|
|
overlapped = OVERLAPPED()
|
|
LockFileEx(hfile, flags, 0, 0, 0xFFFF0000, ctypes.byref(overlapped))
|
|
|
|
def unlock(file):
|
|
hfile = msvcrt.get_osfhandle(file.fileno())
|
|
overlapped = OVERLAPPED()
|
|
UnlockFileEx(hfile, 0, 0, 0xFFFF0000, ctypes.byref(overlapped))
|
|
|
|
elif os_locking == 'posix':
|
|
LOCK_EX = fcntl.LOCK_EX
|
|
LOCK_SH = fcntl.LOCK_SH
|
|
LOCK_NB = fcntl.LOCK_NB
|
|
|
|
def lock(file, flags):
|
|
fcntl.flock(file.fileno(), flags)
|
|
|
|
def unlock(file):
|
|
fcntl.flock(file.fileno(), fcntl.LOCK_UN)
|
|
|
|
|
|
else:
|
|
if os_locking != 'gae':
|
|
logger.debug('no file locking, this will cause problems')
|
|
|
|
LOCK_EX = None
|
|
LOCK_SH = None
|
|
LOCK_NB = None
|
|
|
|
def lock(file, flags):
|
|
pass
|
|
|
|
def unlock(file):
|
|
pass
|
|
|
|
|
|
def open_file(filename, mode):
|
|
if PY2 or 'b' in mode:
|
|
f = open(filename, mode)
|
|
else:
|
|
f = open(filename, mode, encoding='utf8')
|
|
return f
|
|
|
|
|
|
class LockedFile(object):
|
|
def __init__(self, filename, mode='rb'):
|
|
self.filename = filename
|
|
self.mode = mode
|
|
self.file = None
|
|
if 'r' in mode:
|
|
self.file = open_file(filename, mode)
|
|
lock(self.file, LOCK_SH)
|
|
elif 'w' in mode or 'a' in mode:
|
|
self.file = open_file(filename, mode.replace('w', 'a'))
|
|
lock(self.file, LOCK_EX)
|
|
if 'a' not in mode:
|
|
self.file.seek(0)
|
|
self.file.truncate(0)
|
|
else:
|
|
raise RuntimeError("invalid LockedFile(...,mode)")
|
|
|
|
def read(self, size=None):
|
|
return self.file.read() if size is None else self.file.read(size)
|
|
|
|
def readline(self):
|
|
return self.file.readline()
|
|
|
|
def readlines(self):
|
|
return self.file.readlines()
|
|
|
|
def write(self, data):
|
|
self.file.write(data)
|
|
self.file.flush()
|
|
|
|
def close(self):
|
|
if self.file is not None:
|
|
unlock(self.file)
|
|
self.file.close()
|
|
self.file = None
|
|
|
|
def __del__(self):
|
|
if self.file is not None:
|
|
self.close()
|
|
|
|
|
|
def read_locked(filename):
|
|
fp = LockedFile(filename, 'rb')
|
|
data = fp.read()
|
|
fp.close()
|
|
return data
|
|
|
|
|
|
def write_locked(filename, data):
|
|
fp = LockedFile(filename, 'wb')
|
|
data = fp.write(data)
|
|
fp.close()
|