{-# LANGUAGE CPP #-}
--cross-platform file locking utilizing POSIX file locking on Unix/Linux and Windows file locking
--hackage's System.FileLock doesn't support POSIX advisory locks nor locking file based on file descriptors, hence this needless rewrite
module ProjectM36.FileLock where
import System.IO


#if defined(mingw32_HOST_OS)
import ProjectM36.Win32Handle
import System.Win32.Types
import Foreign.Marshal.Alloc
import System.Win32.File
import System.Win32.Mem
import Data.Bits

#if defined(i386_HOST_ARCH)
# define WINDOWS_CCONV stdcall
#elif defined(x86_64_HOST_ARCH)
# define WINDOWS_CCONV ccall
#else
# error Unknown mingw32 arch
#endif
foreign import WINDOWS_CCONV "LockFileEx" c_lockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> DWORD -> LPOVERLAPPED -> IO BOOL

foreign import WINDOWS_CCONV "UnlockFileEx" c_unlockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> LPOVERLAPPED -> IO BOOL

--swiped from System.FileLock package
lockFile :: Handle -> LockType -> IO ()
lockFile handle lock = withHandleToHANDLE handle $ \winHandle -> do
  let exFlag = case lock of
                 WriteLock -> 2
                 ReadLock -> 0
      blockFlag = 0 --always block
      sizeof_OVERLAPPED = 32

  allocaBytes sizeof_OVERLAPPED $ \op -> do
    zeroMemory op $ fromIntegral sizeof_OVERLAPPED
    res <- c_lockFileEx winHandle (exFlag .|. blockFlag) 0 1 0 op
    if res then
       pure ()
    else
       error "failed to wait for database lock"

unlockFile :: Handle -> IO ()
unlockFile handle = withHandleToHANDLE handle $ \winHandle -> do
  let sizeof_OVERLAPPED = 32
  allocaBytes sizeof_OVERLAPPED $ \op -> do
    zeroMemory op $ fromIntegral sizeof_OVERLAPPED
    res <- c_unlockFileEx winHandle 0 1 0 op
    if res then
      pure ()
    else
      error ("failed to unlock database lock: " ++ show res)

#else
import qualified System.Posix.IO as P

lockStruct :: P.LockRequest -> P.FileLock
lockStruct req = (req, AbsoluteSeek, 0, 0)

--blocks on lock, if necessary
lockFile :: Handle -> LockType -> IO ()    
lockFile file lock = do
  fd <- P.handleToFd file
  let lockt = case lock of
        WriteLock -> P.WriteLock
        ReadLock -> P.ReadLock
  P.waitToSetLock fd (lockStruct lockt)
  
unlockFile :: Handle -> IO ()  
unlockFile file = do 
  fd <- P.handleToFd file
  P.waitToSetLock fd (lockStruct P.Unlock)
#endif

data LockType = ReadLock | WriteLock

{-
lockFileSTM :: Handle -> LockType -> STM ()
lockFileSTM file lock = unsafeIOToSTM $ onException (lockFile file lock) (unlockFile file)

unlockFileSTM :: Handle -> STM ()
unlockFileSTM file = unsafeIOToSTM $ unlockFile file
-}