-- | -- Stability : Ultra-Violence -- Portability : I'm too young to die -- Higher-level file patterns. Don't support read/write operations at offsets and handling stat changes as for now. {-# LANGUAGE ScopedTypeVariables, FlexibleContexts, OverloadedStrings #-} module Network.NineP.File ( isDir , simpleFile , simpleFileBy , simpleDirectory , rwFile , memoryFile , memoryDirectory ) where import Control.Concurrent.Chan import Control.Exception import Control.Monad import Control.Monad.EmbedIO import Control.Monad.Trans import Data.ByteString.Lazy.Char8 (ByteString) import qualified Data.ByteString.Lazy.Char8 as B import Data.Bits import Data.Convertible.Base import Data.IORef import Data.StateRef import Data.Word import Prelude hiding (read) import Network.NineP.Error import Network.NineP.Internal.File -- |Tests if the file is a directory isDir :: Word32 -- ^Permissions -> Bool isDir perms = testBit perms 31 simpleRead :: (Monad m, EmbedIO m) => m ByteString -> Word64 -> Word32 -> m ByteString simpleRead get offset count = do r <- get (return . B.take (fromIntegral count) . B.drop (fromIntegral offset)) r -- TODO make it offset-aware simpleWrite :: (Monad m, EmbedIO m) => (ByteString -> m ()) -> Word64 -> ByteString -> m Word32 simpleWrite put offset d = case offset of _ -> do r <- put d (const $ return $ fromIntegral $ B.length d) r --_ -> throwError $ OtherError "can't write at offset" -- TODO remove -- |A file that reads and writes using simple user-specified callbacks. rwFile :: forall m. (EmbedIO m) => String -- ^File name -> Maybe (m ByteString) -- ^Read handler -> Maybe (ByteString -> m ()) -- ^Write handler -> NineFile m rwFile name rc wc = simpleFileBy name (maybe (fst nulls) id rc, maybe (snd nulls) id wc) (id, id) -- FIXME sane errors -- |Placeholder source and sink nulls :: MonadIO m => (m a, a -> m ()) nulls = (throw $ Underflow, const $ return ()) -- |A file that reads from and writes to the supplied 'Ref' instances, with converstion to the appropriate types. See 'Network.NineP.File.Instances', 'Data.Convertible.Instances' and 'Data.StateRef.Instances'. Use '()', if the file is meant to be read-only/write-only. simpleFile :: forall a b m rr wr. (Monad m, EmbedIO m, ReadRef rr m a, Convertible a ByteString, WriteRef wr m b, Convertible ByteString b) => String -- ^File name -> rr -- ^Reading function -> wr -- ^Writing function -> NineFile m simpleFile name rr wr = simpleFileBy name (readReference rr, writeReference wr) (convert, convert) -- Shouldn't we make it simpler? -- |Typeclass-free version of 'simpleFile'. simpleFileBy :: forall a b m. (Monad m, EmbedIO m) => String -- ^File name -> (m a, b -> m ()) -- ^Reading and writing handle -> (a -> ByteString, ByteString -> b) -- ^Type conversion handles -> NineFile m simpleFileBy name (rd, wr) (rdc, wrc) = (boringFile name :: NineFile m) { read = simpleRead $ liftM rdc $ rd, write = simpleWrite $ wr . wrc } -- |A file that stores its contents in the form of 'IORef' 'ByteString' memoryFile :: forall m. (Monad m, EmbedIO m) => String -- ^File name -> IO (NineFile m) memoryFile name = do c <- newIORef "" :: IO (IORef ByteString) return $ simpleFileBy name ( liftIO $ readIORef c, liftIO . writeIORef c ) (id, id) -- |A directory that stores its contents in the form of 'IORef [(String, NineFile m)]' simpleDirectory :: forall m. (Monad m, EmbedIO m) => String -- ^File name -> (String -> m (NineFile m)) -- ^A function for creating new files -> (String -> m (NineFile m)) -- ^A function for creating new directories -> IO (NineFile m, IORef [(String, NineFile m)]) simpleDirectory name newfile newdir = do files <- newIORef [] :: IO (IORef [(String, NineFile m)]) return $ (\f -> (f, files)) $ (boringDir name [] :: NineFile m) { create = \name perms -> do nf <- (if isDir perms then newdir else newfile) name let nelem = (name, nf) liftIO $ atomicModifyIORef' files (\l -> (nelem:l, ())) return nf, getFiles = liftIO $ liftM (map snd) $ readIORef files, descend = \name -> do d <- liftIO $ readIORef files maybe (throw $ ENoFile name) (return) $ lookup name d } -- |A composition of a 'simpleDirectory' and a 'memoryFile' memoryDirectory :: forall m. (Monad m, EmbedIO m) => String -- ^File name -> IO (NineFile m) memoryDirectory name = liftM fst $ simpleDirectory name (liftIO . memoryFile) (liftIO . memoryDirectory)