{-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns #-} {-| Stability: unstable Portability: portable Contains internal implementation details for CUIDs. -} module Web.Cuid.Internal ( formatNumber, formatPadded, formatShort, getNextCount, getRandomValue, getTimestamp, myFingerprint ) where import Control.Monad (liftM) import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Char (ord) import Data.IORef (IORef, newIORef, atomicModifyIORef') import Data.Text (Text, append) import Data.Time.Clock.POSIX (getPOSIXTime) import Network.HostName (getHostName) import System.IO.Unsafe (unsafePerformIO) import System.Random.MWC (GenIO) import qualified System.Random.MWC as MWC import Web.Cuid.Internal.Formatting #if defined(mingw32_HOST_OS) import System.Win32 (ProcessId, failIfZero) #else import System.Posix.Process (getProcessID) #endif -- | maxCount is derived from the output format and defines the maximum random -- number we should generate. maxCount :: Int maxCount = formatBase ^ blockSize -- | A machine's fingerprint is derived from its PID and hostname. We do some -- maths on the hostname's contents to boil it down to a single integer instead -- of exposing a string. getFingerprint :: IO (Int, Int) getFingerprint = do pid <- getPid hostname <- getHostName let hostSum = 36 + length hostname + sum (map ord hostname) return (pid, hostSum) -- | For efficiency, calculate the fingerptint and format it once. myFingerprint :: Text myFingerprint = unsafePerformIO $ do (pid, host) <- getFingerprint return (formatShort pid `append` formatShort host) -- This ensures the action should only be evaluated once, rather than being -- inlined and potentially evaluated inside another call. {-# NOINLINE myFingerprint #-} -- | Global random number generator. generator :: GenIO generator = unsafePerformIO MWC.create -- Don't want two different generators being created because of inlining. -- For more info: https://wiki.haskell.org/Top_level_mutable_state {-# NOINLINE generator #-} -- | Just get a random integer. getRandomValue :: IO Int getRandomValue = MWC.uniformR (0, maxCount) generator -- | CUID calls for a globally incrementing counter per machine. This is ugly, -- but it satisfies the requirement. counter :: IORef Int counter = unsafePerformIO (newIORef 0) -- Don't want two different counters being created because of inlining. {-# NOINLINE counter #-} -- | Get the next value of the global counter required for CUID. getNextCount :: IO Int getNextCount = postIncrement counter -- | Increment the counter, and return the value before it was incremented. postIncrement :: MonadIO m => IORef Int -> m Int postIncrement c = liftIO (atomicModifyIORef' c incrementAndWrap) where incrementAndWrap count = (succ count `mod` maxCount, count) -- | Get the current UNIX time in milliseconds. getTimestamp :: IO Int getTimestamp = liftM toMillis getPOSIXTime where toMillis posix = round (posix * 1000) -- | Get the ID of the current process. This function has a platform-specific -- implementation. Fun times. getPid :: MonadIO m => m Int #if defined(mingw32_HOST_OS) foreign import stdcall unsafe "windows.h GetCurrentProcessId" c_GetCurrentProcessId :: IO ProcessId getCurrentProcessId :: IO ProcessId getCurrentProcessId = failIfZero "GetCurrentProcessId" c_GetCurrentProcessId getPid = liftM fromIntegral (liftIO getCurrentProcessId) #else getPid = liftM fromIntegral (liftIO getProcessID) #endif