packages feed

Win32-extras-0.1.0.0: System/Win32/Error/MultiByte.hsc

{- |
   Module      :  System.Win32.Error.MutiByte
   Copyright   :  2012 shelarcy
   License     :  BSD-style

   Maintainer  :  shelarcy@gmail.com
   Stability   :  Provisional
   Portability :  Non-portable (Win32 API)

   MutiByte support version of error handling for foreign calls to the Win32 API.
-}
module System.Win32.Error.MultiByte where
import Control.Concurrent        ( threadDelay)
import Control.Exception         ( throwIO )
import Control.Monad             ( void )
import Data.Char                 ( isSpace )
import Foreign.C.Error           ( getErrno, errnoToIOError )
import Foreign.Ptr               ( Ptr )
import qualified Numeric         ( showHex )
import System.IO.Error           ( ioeSetErrorString )
import System.Win32.Types hiding ( failIf, failIf_, failIfNull
                                 , failIfZero, failIfFalse_
                                 , failUnlessSuccess, failUnlessSuccessOr
                                 , errorWin, failWith)
import System.Win32.Encoding

#include <windows.h>

----------------------------------------------------------------
-- MultiByte version of Errors
----------------------------------------------------------------

failIf :: (a -> Bool) -> String -> IO a -> IO a
failIf p wh act = do
  v <- act
  if p v then errorWin wh else return v

failIf_ :: (a -> Bool) -> String -> IO a -> IO ()
failIf_ p wh act = do
  v <- act
  if p v then errorWin wh else return ()

failIfNull :: String -> IO (Ptr a) -> IO (Ptr a)
failIfNull = failIf (== nullPtr)

failIfZero :: (Eq a, Num a) => String -> IO a -> IO a
failIfZero = failIf (== 0)

failIfFalse_ :: String -> IO Bool -> IO ()
failIfFalse_ = failIf_ not

failUnlessSuccess :: String -> IO ErrCode -> IO ()
failUnlessSuccess fn_name act = do
  r <- act
  if r == 0 then return () else failWith fn_name r

failUnlessSuccessOr :: ErrCode -> String -> IO ErrCode -> IO Bool
failUnlessSuccessOr val fn_name act = do
  r <- act
  if r == 0 then return False
    else if r == val then return True
    else failWith fn_name r

errorWin :: String -> IO a
errorWin fn_name = do
  err_code <- getLastError
  failWith fn_name err_code

failWith :: String -> ErrCode -> IO a
failWith fn_name err_code = do
  cp <- getCurrentCodePage
  failWithInternal getErrorMessage (encodeMultiByteIO cp) fn_name err_code

failWithInternal :: (ErrCode -> IO LPWSTR) -> (String -> IO String) -> String -> ErrCode -> IO a
failWithInternal msg conv fn_name err_code = do
  c_msg <- msg err_code
  msg <- if c_msg == nullPtr
           then return $ "Error 0x" ++ Numeric.showHex err_code ""
           else do msg <- peekTString c_msg
                   -- We ignore failure of freeing c_msg, given we're already failing
                   _ <- localFree c_msg
                   return msg
  c_maperrno -- turn GetLastError() into errno, which errnoToIOError knows
             -- how to convert to an IOException we can throw.
             -- XXX we should really do this directly.
  errno <- getErrno
  let msg' = reverse $ dropWhile isSpace $ reverse msg -- drop trailing \n
  msg'' <- conv msg' -- convert to multibytes message
  let ioerror = errnoToIOError fn_name errno Nothing Nothing
                  `ioeSetErrorString` msg''
  throwIO ioerror

-- | like failIfFalse_, but retried on sharing violations.
-- This is necessary for many file operations; see
--   <http://support.microsoft.com/kb/316609>
--
failIfWithRetry :: (a -> Bool) -> String -> IO a -> IO a
failIfWithRetry cond msg action = retryOrFail retries
  where
    delay   = 100*1000 -- in ms, we use threadDelay
    retries = 20 :: Int
      -- KB article recommends 250/5

    -- retryOrFail :: Int -> IO a
    retryOrFail times
      | times <= 0 = errorWin msg
      | otherwise  = do
         ret <- action
         if not (cond ret)
            then return ret
            else do
              err_code <- getLastError
              if err_code == (# const ERROR_SHARING_VIOLATION)
                then do threadDelay delay; retryOrFail (times - 1)
                else errorWin msg

failIfWithRetry_ :: (a -> Bool) -> String -> IO a -> IO ()
failIfWithRetry_ cond msg action = void $ failIfWithRetry cond msg action

failIfFalseWithRetry_ :: String -> IO Bool -> IO ()
failIfFalseWithRetry_ = failIfWithRetry_ not