packages feed

immutaball-core-0.1.0.4.1: Immutaball/Share/ImmutaballIO/DirectoryIO.hs

{-# OPTIONS_GHC -fno-warn-tabs #-}  -- Support tab indentation better, for a better default of no warning if tabs are used: https://dmitryfrank.com/articles/indent_with_tabs_align_with_spaces .
-- Enable warnings:
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

-- ImmutaballIO.hs.

{-# LANGUAGE Haskell2010 #-}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, InstanceSigs, ScopedTypeVariables, ExistentialQuantification #-}

module Immutaball.Share.ImmutaballIO.DirectoryIO
	(
		-- * DirectoryIO
		DirectoryIO,
		DirectoryIOF(..),
		runDirectoryIO,

		-- * mfix
		FixDirectoryIOException(..),
		fixDirectoryIOExceptionToException,
		fixDirectoryIOExceptionFromException,
		PrematureEvaluationFixDirectoryIOException(..),
		EmptyFixDirectoryIOException(..),
		fixDirectoryIOF,
		unsafeFixDirectoryIOFTo,

		-- * Runners
		runDirectoryIOIO,

		-- * DirectoryIO aliases that apply the Fixed wrapper
		mkGetXdgDirectoryData,
		mkGetXdgDirectoryDataSync,
		mkGetXdgDirectoryConfig,
		mkGetXdgDirectoryConfigSync,
		mkGetXdgDirectoryCache,
		mkGetXdgDirectoryCacheSync,
		mkGetXdgDirectoryState,
		mkGetXdgDirectoryStateSync
	) where

import Prelude ()
import Immutaball.Prelude

import Control.Concurrent.Async
import System.Directory

import Immutaball.Share.Utils

-- (mfix imports.)
import Control.Concurrent.MVar
import Control.Exception
import Data.Typeable
import GHC.IO.Unsafe (unsafeDupableInterleaveIO)
import System.IO.Unsafe (unsafePerformIO)

-- * DirectoryIO

type DirectoryIO = Fixed DirectoryIOF
data DirectoryIOF a =
	-- | Get ~/.local/share/path .
	  GetXdgDirectoryData FilePath (Async FilePath -> a)
	| GetXdgDirectoryDataSync FilePath (FilePath -> a)
	-- | Get ~/.local/config/path .
	| GetXdgDirectoryConfig FilePath (Async FilePath -> a)
	| GetXdgDirectoryConfigSync FilePath (FilePath -> a)
	-- | Get ~/.local/cache/path .
	| GetXdgDirectoryCache FilePath (Async FilePath -> a)
	| GetXdgDirectoryCacheSync FilePath (FilePath -> a)
	-- | Get ~/.local/state/path .
	| GetXdgDirectoryState FilePath (Async FilePath -> a)
	| GetXdgDirectoryStateSync FilePath (FilePath -> a)
instance Functor DirectoryIOF where
	fmap :: (a -> b) -> (DirectoryIOF a -> DirectoryIOF b)
	fmap f (GetXdgDirectoryData       path withDir) = GetXdgDirectoryData       path (f . withDir)
	fmap f (GetXdgDirectoryDataSync   path withDir) = GetXdgDirectoryDataSync   path (f . withDir)
	fmap f (GetXdgDirectoryConfig     path withDir) = GetXdgDirectoryConfig     path (f . withDir)
	fmap f (GetXdgDirectoryConfigSync path withDir) = GetXdgDirectoryConfigSync path (f . withDir)
	fmap f (GetXdgDirectoryCache      path withDir) = GetXdgDirectoryCache      path (f . withDir)
	fmap f (GetXdgDirectoryCacheSync  path withDir) = GetXdgDirectoryCacheSync  path (f . withDir)
	fmap f (GetXdgDirectoryState      path withDir) = GetXdgDirectoryState      path (f . withDir)
	fmap f (GetXdgDirectoryStateSync  path withDir) = GetXdgDirectoryStateSync  path (f . withDir)

runDirectoryIO :: DirectoryIO -> IO ()
runDirectoryIO dio = cata runDirectoryIOIO dio

-- TODO: revisit.
{-
instance Foldable DirectoryIOF where
	foldr :: (a -> b -> b) -> b -> DirectoryIOF a -> b
	foldr _reduce reduction0 (GetXdgDirectoryData   _path) = reduction0
	foldr _reduce reduction0 (GetXdgDirectoryConfig _path) = reduction0
	foldr _reduce reduction0 (GetXdgDirectoryCache  _path) = reduction0
	foldr _reduce reduction0 (GetXdgDirectoryState  _path) = reduction0
instance Traversable DirectoryIOF where
	traverse :: Applicative f => (a -> f b) -> DirectoryIOF a -> f (DirectoryIOF b)
	traverse _traversal (GetXdgDirectoryData   path) = pure GetXdgDirectoryData   <*> pure path
	traverse _traversal (GetXdgDirectoryConfig path) = pure GetXdgDirectoryConfig <*> pure path
	traverse _traversal (GetXdgDirectoryCache  path) = pure GetXdgDirectoryCache  <*> pure path
	traverse _traversal (GetXdgDirectoryState  path) = pure GetXdgDirectoryState  <*> pure path
-}

-- * mfix

data FixDirectoryIOException = forall e. Exception e => FixDirectoryIOException e
instance Show FixDirectoryIOException where
	show (FixDirectoryIOException e) = show e
instance Exception FixDirectoryIOException
fixDirectoryIOExceptionToException :: Exception e => e -> SomeException
fixDirectoryIOExceptionToException = toException . FixDirectoryIOException
fixDirectoryIOExceptionFromException :: Exception e => SomeException -> Maybe e
fixDirectoryIOExceptionFromException x = do
	FixDirectoryIOException a <- fromException x
	cast a

data PrematureEvaluationFixDirectoryIOException = PrematureEvaluationFixDirectoryIOException
	deriving (Show)
instance Exception PrematureEvaluationFixDirectoryIOException where
	toException = fixDirectoryIOExceptionToException
	fromException = fixDirectoryIOExceptionFromException

data EmptyFixDirectoryIOException = EmptyFixDirectoryIOException
	deriving (Show)
instance Exception EmptyFixDirectoryIOException where
	toException = fixDirectoryIOExceptionToException
	fromException = fixDirectoryIOExceptionFromException

--    mfix f = mfix f >>= f
-- => mfix f = join $ f <$> mfix f
-- Incorrect: runs f twice.
	--x -> f undefined >>= mfix f
{-
fixDirectoryIOF :: (me -> DirectoryIOF me) -> DirectoryIOF me
fixDirectoryIOF f = case f (error "Error: fixDirectoryIOF: premature evaluation of result before we could start it!") of
	x -> joinDirectoryIOF $ f <$> x
-}
-- Do it like fixIO and fixST (see also their notes; it's a little tricky).
-- Use a lazily read MVar.
fixDirectoryIOF :: (me -> DirectoryIOF me) -> DirectoryIOF me
fixDirectoryIOF f = unsafePerformIO $ do
	mme <- newEmptyMVar
	return $ unsafeFixDirectoryIOFTo mme f

-- | Helper for fixDirectoryIOF.
unsafeFixDirectoryIOFTo :: MVar me -> (me -> DirectoryIOF me) -> DirectoryIOF me
unsafeFixDirectoryIOFTo mme f = unsafePerformIO $ do
	me_ <- unsafeDupableInterleaveIO (readMVar mme `catch` \BlockedIndefinitelyOnMVar -> throwIO PrematureEvaluationFixDirectoryIOException)
	case f me_ of
		_y@(GetXdgDirectoryData       path withDir) -> return $ GetXdgDirectoryData       path ((\me -> unsafePerformIO $ putMVar mme me >> return me) . withDir)
		_y@(GetXdgDirectoryDataSync   path withDir) -> return $ GetXdgDirectoryDataSync   path ((\me -> unsafePerformIO $ putMVar mme me >> return me) . withDir)
		_y@(GetXdgDirectoryConfig     path withDir) -> return $ GetXdgDirectoryConfig     path ((\me -> unsafePerformIO $ putMVar mme me >> return me) . withDir)
		_y@(GetXdgDirectoryConfigSync path withDir) -> return $ GetXdgDirectoryConfigSync path ((\me -> unsafePerformIO $ putMVar mme me >> return me) . withDir)
		_y@(GetXdgDirectoryCache      path withDir) -> return $ GetXdgDirectoryCache      path ((\me -> unsafePerformIO $ putMVar mme me >> return me) . withDir)
		_y@(GetXdgDirectoryCacheSync  path withDir) -> return $ GetXdgDirectoryCacheSync  path ((\me -> unsafePerformIO $ putMVar mme me >> return me) . withDir)
		_y@(GetXdgDirectoryState      path withDir) -> return $ GetXdgDirectoryState      path ((\me -> unsafePerformIO $ putMVar mme me >> return me) . withDir)
		_y@(GetXdgDirectoryStateSync  path withDir) -> return $ GetXdgDirectoryStateSync  path ((\me -> unsafePerformIO $ putMVar mme me >> return me) . withDir)

-- * Runners

runDirectoryIOIO :: DirectoryIOF (IO ()) -> IO ()
runDirectoryIOIO (GetXdgDirectoryData       path withDir) = withAsync (getXdgDirectory XdgData   path) (withDir)
runDirectoryIOIO (GetXdgDirectoryDataSync   path withDir) = getXdgDirectory XdgData   path >>= withDir
runDirectoryIOIO (GetXdgDirectoryConfig     path withDir) = withAsync (getXdgDirectory XdgConfig path) (withDir)
runDirectoryIOIO (GetXdgDirectoryConfigSync path withDir) = getXdgDirectory XdgConfig path >>= withDir
runDirectoryIOIO (GetXdgDirectoryCache      path withDir) = withAsync (getXdgDirectory XdgCache  path) (withDir)
runDirectoryIOIO (GetXdgDirectoryCacheSync  path withDir) = getXdgDirectory XdgCache  path >>= withDir
runDirectoryIOIO (GetXdgDirectoryState      path withDir) = withAsync (getXdgDirectory XdgState  path) (withDir)
runDirectoryIOIO (GetXdgDirectoryStateSync  path withDir) = getXdgDirectory XdgState  path >>= withDir

-- * DirectoryIO aliases that apply the Fixed wrapper

mkGetXdgDirectoryData :: FilePath -> (Async FilePath -> DirectoryIO) -> DirectoryIO
mkGetXdgDirectoryData path withDir = Fixed $ GetXdgDirectoryData path withDir

mkGetXdgDirectoryDataSync :: FilePath -> (FilePath -> DirectoryIO) -> DirectoryIO
mkGetXdgDirectoryDataSync path withDir = Fixed $ GetXdgDirectoryDataSync path withDir

mkGetXdgDirectoryConfig :: FilePath -> (Async FilePath -> DirectoryIO) -> DirectoryIO
mkGetXdgDirectoryConfig path withDir = Fixed $ GetXdgDirectoryConfig path withDir

mkGetXdgDirectoryConfigSync :: FilePath -> (FilePath -> DirectoryIO) -> DirectoryIO
mkGetXdgDirectoryConfigSync path withDir = Fixed $ GetXdgDirectoryConfigSync path withDir

mkGetXdgDirectoryCache :: FilePath -> (Async FilePath -> DirectoryIO) -> DirectoryIO
mkGetXdgDirectoryCache path withDir = Fixed $ GetXdgDirectoryCache path withDir

mkGetXdgDirectoryCacheSync :: FilePath -> (FilePath -> DirectoryIO) -> DirectoryIO
mkGetXdgDirectoryCacheSync path withDir = Fixed $ GetXdgDirectoryCacheSync path withDir

mkGetXdgDirectoryState :: FilePath -> (Async FilePath -> DirectoryIO) -> DirectoryIO
mkGetXdgDirectoryState path withDir = Fixed $ GetXdgDirectoryState path withDir

mkGetXdgDirectoryStateSync :: FilePath -> (FilePath -> DirectoryIO) -> DirectoryIO
mkGetXdgDirectoryStateSync path withDir = Fixed $ GetXdgDirectoryStateSync path withDir