2014-03-02 48 views
3

我正在嘗試從文件讀取一些不規則輸入(例如,可能不時出現的命令)。例如。最初源文件是空的,並且我的程序已啓動。然後一個字符串被追加到文件中,我的程序必須讀取這個字符串。從文件無限讀取

頭一個幼稚的:

import System.IO 
import Control.Monad 

listen :: Handle -> IO() 
listen file = forever $ do 
    ineof <- hIsEOF file 
    if ineof 
     then do 
      s <- hGetLine file 
      putStrLn s 
     else 
      return() 

但它不(首先是因爲的性能問題)正常工作的過程。我怎樣才能正確實施這個(可能與導管使用)?

回答

3

我已經放在一起實施下面的一個例子。其基本思想是:

  • 使用fsnotify軟件包監視文件更改。
  • 使用sourceFileRange來流式傳輸文件以前未消耗的部分。
  • 使用MVar讓fsnotify回調信號Source繼續讀取。

這裏假定源文件只添加到永遠不會刪除或縮短。

import   Control.Concurrent  (forkIO, threadDelay) 
import   Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, 
              takeMVar) 
import   Control.Exception   (IOException, try) 
import   Control.Monad    (forever, void, when) 
import   Control.Monad.IO.Class (liftIO) 
import   Data.ByteString   (ByteString) 
import qualified Data.ByteString   as S 
import   Data.Conduit    (MonadResource, Source, bracketP, 
              runResourceT, ($$), ($=)) 
import   Data.Conduit.Binary  (sourceFileRange) 
import qualified Data.Conduit.List   as CL 
import   Data.IORef    (IORef, modifyIORef, newIORef, 
              readIORef) 
import   Data.Time     (getCurrentTime) 
import   Filesystem    (canonicalizePath) 
import   Filesystem.Path.CurrentOS (decodeString, directory) 
import   System.FSNotify   (Event (..), startManager, 
              stopManager, watchDir) 

tryIO :: IO a -> IO (Either IOException a) 
tryIO = try 

sourceFileForever :: MonadResource m => FilePath -> Source m ByteString 
sourceFileForever fp' = bracketP startManager stopManager $ \manager -> do 
    fp <- liftIO $ canonicalizePath $ decodeString fp' 
    baton <- liftIO newEmptyMVar 
    liftIO $ watchDir manager (directory fp) (const True) $ \event -> void $ tryIO $ do 
     fpE <- canonicalizePath $ 
      case event of 
       Added x _ -> x 
       Modified x _ -> x 
       Removed x _ -> x 
     when (fpE == fp) $ putMVar baton() 
    consumedRef <- liftIO $ newIORef 0 
    loop baton consumedRef 
    where 
    loop :: MonadResource m => MVar() -> IORef Integer -> Source m ByteString 
    loop baton consumedRef = forever $ do 
     consumed <- liftIO $ readIORef consumedRef 
     sourceFileRange fp' (Just consumed) Nothing $= CL.iterM counter 
     liftIO $ takeMVar baton 
     where 
     counter bs = liftIO $ modifyIORef consumedRef (+ fromIntegral (S.length bs)) 

main :: IO() 
main = do 
    let fp = "foo.txt" 
    writeFile fp "Hello World!" 
    _ <- forkIO $ runResourceT $ sourceFileForever fp $$ CL.mapM_ (liftIO . print) 
    forever $ do 
     now <- getCurrentTime 
     appendFile fp $ show now ++ "\n" 
     threadDelay 1000000