packages feed

HsSVN-0.3.2: Subversion/Stream/Pipe.hs

module Subversion.Stream.Pipe
    ( newPipe
    )
    where

import           Control.Concurrent
import           Control.Concurrent.STM
import           Control.Monad
import qualified Data.ByteString      as Strict
import qualified Data.ByteString.Lazy as Lazy
import           Data.Int
import           Subversion.Stream


data Pipe
    = Pipe {
        pRequestLen :: TVar Int             -- 要求された殘りバイト數
      , pWrittenStr :: TVar Lazy.ByteString -- 書き込まれたが、未だ讀込まれてゐない文字列
      , pIsClosed   :: TVar Bool            -- パイプが閉ぢられた
      }


newPipe :: IO Stream
newPipe = do unless rtsSupportsBoundThreads
                 $ fail "Subversion.Stream.Pipe.newPipe requires threaded RTS!"

             req <- newTVarIO 0
             str <- newTVarIO Lazy.empty
             isC <- newTVarIO False
             
             let pipe    = Pipe {
                             pRequestLen = req
                           , pWrittenStr = str
                           , pIsClosed   = isC
                           }
                 actions = StreamActions {
                             saRead  = mkReadAction  pipe
                           , saWrite = mkWriteAction pipe
                           , saClose = mkCloseAction pipe
                           }

             newStream actions


mkReadAction :: Pipe -> ReadAction
mkReadAction pipe reqLen = loop
    where
      loop :: IO Strict.ByteString
      loop = do nextAction <- tryToRead
                nextAction

      tryToRead :: IO (IO Strict.ByteString)
      tryToRead
          = atomically $
            do str <- readTVar (pWrittenStr pipe)
               if Lazy.length str < fromIntegral reqLen then
                   -- 書込まれた文字列が足りないので、要求されたバイト
                   -- 數をパイプに書いて書込みを待つ。但しパイプが閉ぢ
                   -- られてゐたら reqLen に滿たない長さの文字列を返し
                   -- て EOF を示す。
                   do isClosed <- readTVar (pIsClosed pipe)
                      if isClosed then
                          do writeTVar (pWrittenStr pipe) Lazy.empty
                             return (return (Strict.concat (Lazy.toChunks str)))
                        else
                          do oldReqLen <- readTVar (pRequestLen pipe)
                             if oldReqLen < reqLen then
                                 do writeTVar (pRequestLen pipe) reqLen
                                    return loop
                               else
                                 retry
                 else
                   -- reqLen バイトを上限としてバッファの頭を切り取る。
                   do let (readStr, remaining) = Lazy.splitAt (fromIntegral reqLen) str
                      writeTVar (pWrittenStr pipe) remaining
                      return (return (Strict.concat (Lazy.toChunks readStr)))


mkWriteAction :: Pipe -> WriteAction
mkWriteAction pipe input = loop input >> return (Strict.length input)
    where
      loop :: Strict.ByteString -> IO ()
      loop str = do nextAction <- tryToWrite str
                    nextAction

      tryToWrite :: Strict.ByteString -> IO (IO ())
      tryToWrite str
          = atomically $
            do isClosed <- readTVar (pIsClosed pipe)
               if isClosed then
                   -- パイプが閉ぢられてゐたら書込まれた文字列を捨てる
                   -- FIXME: 本當にそれで良いのか?
                   return (return ())
                 else
                   do reqLen <- readTVar (pRequestLen pipe)
                      let (strToWrite, remaining) = Strict.splitAt reqLen str
                      if Strict.null strToWrite then
                          if Strict.null remaining then
                              return (return ())
                          else
                              retry
                        else
                          do writtenStr <- readTVar (pWrittenStr pipe)
                             writeTVar (pWrittenStr pipe) (writtenStr `Lazy.append` (Lazy.fromChunks [strToWrite]))
                             writeTVar (pRequestLen pipe) (reqLen - Strict.length strToWrite)
                             return (loop remaining)


mkCloseAction :: Pipe -> CloseAction
mkCloseAction pipe
    = atomically $
      writeTVar (pIsClosed pipe) True