{- Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. -} module Glean.Util.IO ( HandleIO(..), readFileContents, writeFileContents, withTempFileContents, backupFileOnError, ) where import Util.Control.Exception import Util.Log import Control.Monad.Extra import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS import Data.Maybe import qualified Data.Text as Text import qualified Data.Text.IO as Text import qualified Data.Text.Lazy as LText import qualified Data.Text.Lazy.IO as LText import System.Directory import System.FilePath import System.IO import qualified System.IO.Temp as Temp -- Things that can be read from and written to a file handle class HandleIO a where getHandleContents :: Handle -> IO a writeHandle :: Handle -> a -> IO () instance HandleIO String where getHandleContents = hGetContents writeHandle = hPutStr instance HandleIO BS.ByteString where getHandleContents = BS.hGetContents writeHandle = BS.hPutStr instance HandleIO LBS.ByteString where getHandleContents = LBS.hGetContents writeHandle = LBS.hPutStr instance HandleIO Text.Text where getHandleContents = Text.hGetContents writeHandle = Text.hPutStr instance HandleIO LText.Text where getHandleContents = LText.hGetContents writeHandle = LText.hPutStr -- Read the contents of the entire file readFileContents :: HandleIO a => FilePath -> IO a readFileContents file = getHandleContents =<< openFile file ReadMode withContents :: HandleIO a => a -> (FilePath -> IO b) -> FilePath -> Handle -> IO b withContents s f path handle = do writeHandle handle s hClose handle f path -- Execute the action with a temp file containing some data withTempFileContents :: HandleIO a => a -> (FilePath -> IO b) -> IO b withTempFileContents s f = Temp.withSystemTempFile "glean" $ withContents s f withTempFileContentsIn :: HandleIO a => FilePath -> a -> (FilePath -> IO b) -> IO b withTempFileContentsIn dir s f = Temp.withTempFile dir ".glean" $ withContents s f -- Atomically create or replace the file with the given contents writeFileContents :: HandleIO a => FilePath -> a -> IO () writeFileContents file s = withTempFileContentsIn (takeDirectory file) s $ \tmp -> renameFile tmp file -- | This is a combinator to use with withTempFileContents, to -- keep a copy of the temp file when there has been an error. These -- backups are in the @glean-backup-file-on-error@ subdirectory. backupFileOnError :: Maybe FilePath -- ^ Folder to place backup, defaults to a subfolder in the same folder -> (FilePath -> IO b) -> (FilePath -> IO b) backupFileOnError dir action filepath = do whenJust dir $ createDirectoryIfMissing False action filepath `onSomeException` backup filepath where backup filepath _exc = do let subdir = fromMaybe (takeDirectory filepath "glean-backup-file-on-error") dir dest = subdir takeFileName filepath logError $ "Making backup of temp file: " <> dest createDirectoryIfMissing True subdir copyFile filepath dest