Catch more IO exceptions during file reading

This commit is contained in:
Michael Snoyman 2012-12-25 15:46:31 +02:00
parent 9886d0c5e7
commit a3ca024d1e
2 changed files with 40 additions and 19 deletions

View File

@ -7,22 +7,27 @@ module Build
, touch , touch
, recompDeps , recompDeps
, isNewerThan , isNewerThan
, safeReadFile
) where ) where
-- FIXME there's a bug when getFileStatus applies to a file -- FIXME there's a bug when getFileStatus applies to a file
-- temporary deleted (e.g., Vim saving a file) -- temporary deleted (e.g., Vim saving a file)
import Control.Applicative ((<|>), many, (<$>)) import Control.Applicative ((<|>), many, (<$>))
import qualified Data.Attoparsec.Text.Lazy as A import qualified Data.Attoparsec.Text as A
import Data.Char (isSpace, isUpper) import Data.Char (isSpace, isUpper)
import qualified Data.Text.Lazy.IO as TIO import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import Data.ByteString (ByteString)
import qualified Data.ByteString as S
import Control.Exception (SomeException, try) import Control.Exception (SomeException, try, IOException)
import Control.Exception.Lifted (handle) import Control.Exception.Lifted (handle)
import Control.Monad (when, filterM, forM, forM_, (>=>)) import Control.Monad (when, filterM, forM, forM_, (>=>))
import Control.Monad.Trans.State (StateT, get, put, execStateT) import Control.Monad.Trans.State (StateT, get, put, execStateT)
import Control.Monad.Trans.Writer (WriterT, tell, execWriterT) import Control.Monad.Trans.Writer (WriterT, tell, execWriterT)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Class (lift)
import Data.Monoid (Monoid (mappend, mempty)) import Data.Monoid (Monoid (mappend, mempty))
@ -40,6 +45,9 @@ import Text.Julius (juliusUsedIdentifiers)
import Text.Cassius (cassiusUsedIdentifiers) import Text.Cassius (cassiusUsedIdentifiers)
import Text.Lucius (luciusUsedIdentifiers) import Text.Lucius (luciusUsedIdentifiers)
safeReadFile :: MonadIO m => FilePath -> m (Either IOException ByteString)
safeReadFile = liftIO . try . S.readFile
touch :: IO () touch :: IO ()
touch = do touch = do
m <- handle (\(_ :: SomeException) -> return Map.empty) $ readFile touchCache >>= readIO m <- handle (\(_ :: SomeException) -> return Map.empty) $ readFile touchCache >>= readIO
@ -86,8 +94,11 @@ touchDeps f action deps = (mapM_ go . Map.toList) deps
AlwaysOutdated -> return True AlwaysOutdated -> return True
CompareUsedIdentifiers getDerefs -> do CompareUsedIdentifiers getDerefs -> do
derefMap <- get derefMap <- get
s <- liftIO $ readFile x ebs <- safeReadFile x
let newDerefs = Set.fromList $ getDerefs s let newDerefs =
case ebs of
Left _ -> Set.empty
Right bs -> Set.fromList $ getDerefs $ T.unpack $ decodeUtf8With lenientDecode bs
put $ Map.insert x newDerefs derefMap put $ Map.insert x newDerefs derefMap
case Map.lookup x derefMap of case Map.lookup x derefMap of
Just oldDerefs | oldDerefs == newDerefs -> return False Just oldDerefs | oldDerefs == newDerefs -> return False
@ -176,11 +187,15 @@ data ComparisonType = AlwaysOutdated
determineDeps :: FilePath -> IO [(ComparisonType, FilePath)] determineDeps :: FilePath -> IO [(ComparisonType, FilePath)]
determineDeps x = do determineDeps x = do
y <- TIO.readFile x -- FIXME catch IO exceptions y <- safeReadFile x
let z = A.parse (many $ (parser <|> (A.anyChar >> return Nothing))) y case y of
case z of Left _ -> return []
A.Fail{} -> return [] Right bs -> do
A.Done _ r -> mapM go r >>= filterM (doesFileExist . snd) . concat let z = A.parseOnly (many $ (parser <|> (A.anyChar >> return Nothing)))
$ decodeUtf8With lenientDecode bs
case z of
Left _ -> return []
Right r -> mapM go r >>= filterM (doesFileExist . snd) . concat
where where
go (Just (StaticFiles fp, _)) = map ((,) AlwaysOutdated) <$> getFolderContents fp go (Just (StaticFiles fp, _)) = map ((,) AlwaysOutdated) <$> getFolderContents fp
go (Just (Hamlet, f)) = return [(AlwaysOutdated, f)] go (Just (Hamlet, f)) = return [(AlwaysOutdated, f)]

View File

@ -39,6 +39,9 @@ import qualified Data.List as L
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import System.Directory import System.Directory
import System.Environment (getEnvironment) import System.Environment (getEnvironment)
@ -62,7 +65,7 @@ import System.Process (ProcessHandle,
import System.Timeout (timeout) import System.Timeout (timeout)
import Build (getDeps, isNewerThan, import Build (getDeps, isNewerThan,
recompDeps) recompDeps, safeReadFile)
import GhcBuild (buildPackage, import GhcBuild (buildPackage,
getBuildFlags) getBuildFlags)
@ -439,13 +442,16 @@ getPersistConfigLenient opts = do
if not exists if not exists
then return (Left $ "file does not exist: " ++ file) then return (Left $ "file does not exist: " ++ file)
else do else do
xs <- readFile file xs <- safeReadFile file
return $ case lines xs of case xs of
[_,l2] -> -- two lines, header and serialized rest Left e -> return $ Left $ show e
case reads l2 of Right bs ->
[(bi,_)] -> Right bi return $ case lines $ T.unpack $ decodeUtf8With lenientDecode bs of
_ -> (Left "cannot parse contents") [_,l2] -> -- two lines, header and serialized rest
_ -> (Left "not a valid header/content file") case reads l2 of
[(bi,_)] -> Right bi
_ -> (Left "cannot parse contents")
_ -> (Left "not a valid header/content file")
fromMaybeErr :: String -> Maybe b -> IO b fromMaybeErr :: String -> Maybe b -> IO b
fromMaybeErr err Nothing = failWith err fromMaybeErr err Nothing = failWith err