safeio (empty) → 0.0.1.0
raw patch · 7 files changed
+183/−0 lines, 7 filesdep +HUnitdep +basedep +directorysetup-changed
Dependencies added: HUnit, base, directory, filepath, test-framework, test-framework-hunit, test-framework-th, unix
Files
- COPYING +23/−0
- ChangeLog +2/−0
- README.md +22/−0
- Setup.hs +2/−0
- System/IO/SafeWrite.hs +48/−0
- System/IO/SafeWrite/Tests.hs +41/−0
- safeio.cabal +45/−0
+ COPYING view
@@ -0,0 +1,23 @@+Copyright (c) 2017+Luis Pedro Coelho <luis@luispedro.org>++Permission is hereby granted, free of charge, to any person+obtaining a copy of this software and associated documentation+files (the "Software"), to deal in the Software without+restriction, including without limitation the rights to use,+copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the+Software is furnished to do so, subject to the following+conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.
+ ChangeLog view
@@ -0,0 +1,2 @@+Version 0.0.1.0 2017-05-29 by luispedro+ * First version
+ README.md view
@@ -0,0 +1,22 @@+# SafeIO: Haskell library for safe (atomic) IO++This is a simple module, which enables writing in atomic mode. It+implements the following 4 step procedure:++1. Open a temporary file in the same directory as the final output.+2. Write to this temporary file.+3. Close and sync the file.+4. Atomically rename the file to its final destination.++## Example++ import System.IO.SafeWrite+ ...+ main = do+ withOutputFile "output.txt" $ \hout -> do+ hPutStrLn hout "Hello World"+++## Author++Luis Pedro Coelho [Email](mailto:luis@luispedro.org)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/IO/SafeWrite.hs view
@@ -0,0 +1,48 @@+module System.IO.SafeWrite+ ( withOutputFile+ , syncFile+ ) where++import System.FilePath (takeDirectory)+import System.Posix.IO (openFd, defaultFileFlags, closeFd, OpenMode(..))+import System.Posix.Unistd (fileSynchronise)+import Control.Exception (bracket, onException)+import System.IO (Handle, hClose, openTempFile)+import System.Directory (renameFile, removeFile)+++-- | Sync a file to disk+--+-- Only supported on Posix (patches with Windows support are welcome)+syncFile :: FilePath -- ^ File to sync+ -> IO ()+syncFile fname = do+ bracket (openFd fname ReadWrite Nothing defaultFileFlags)+ closeFd+ fileSynchronise+ -- The code below will not work on Windows+ bracket (openFd (takeDirectory fname) ReadOnly Nothing defaultFileFlags)+ closeFd+ fileSynchronise++-- | Variation of 'withFile' for output files.+--+-- Output is written to a temporary file. Once the action has completed, this+-- file is then sync'ed to disk (see |syncFile|) and renamed to its final+-- destination. In Posix, this is an atomic operation. If an exception is+-- raised, then the temporary output file will be deleted and not saved to+-- disk. Thus, the result file will either contain the complete result or will+-- be empty.+withOutputFile ::+ FilePath -- ^ Final desired file path+ -> (Handle -> IO a) -- ^ action to execute+ -> IO a+withOutputFile finalname act = do+ (tname, th) <- openTempFile (takeDirectory finalname) finalname+ (do+ r <- act th+ hClose th+ syncFile tname+ renameFile tname finalname+ return r) `onException` (hClose th >> removeFile tname)+
+ System/IO/SafeWrite/Tests.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}++module Main where++import Test.Framework.TH+import Test.HUnit+import Test.Framework.Providers.HUnit++import Control.Exception (throwIO)+import System.IO.Error (isDoesNotExistError, catchIOError)+import System.Directory (doesFileExist, removeFile)+import System.IO (hPutStrLn)++import System.IO.SafeWrite++main :: IO ()+main = do+ removeFileIfExists outname+ $(defaultMainGenerator)++removeFileIfExists :: FilePath -> IO ()+removeFileIfExists fp = removeFile fp `catchIOError` ignoreDoesNotExistError+ where+ ignoreDoesNotExistError e+ | isDoesNotExistError e = return ()+ | otherwise = throwIO e++outname :: FilePath+outname = "testing-output.txt"++case_create_output = do+ withOutputFile outname $ flip hPutStrLn "Hello World"+ (doesFileExist outname) >>= assertBool "Output file was not created"+ removeFile outname++case_not_create_on_exception = do+ (withOutputFile outname $ \h -> do+ hPutStrLn h "Hello World"+ throwIO $ userError "Something bad happened") `catchIOError` \_ -> return ()+ (not <$> doesFileExist outname) >>= assertBool "Output file was created despite exception being raised"+
+ safeio.cabal view
@@ -0,0 +1,45 @@+name: safeio+version: 0.0.1.0+synopsis: Write output to disk atomically+description: This package implements utilities to perform atomic output+ so as to avoid the problem of partial intermediate files.+category: IO+author: Luis Pedro Coelho+maintainer: Luis Pedro Coelho+license: MIT+license-file: COPYING+cabal-version: >= 1.10+build-type: Simple+bug-reports: https://github.com/luispedro/safeio/issues+extra-source-files: README.md ChangeLog++library+ default-language: Haskell2010+ exposed-modules: System.IO.SafeWrite+ ghc-options: -Wall+ build-depends:+ base > 4 && < 5,+ directory,+ filepath,+ unix++Test-Suite safeiotest+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: System/IO/SafeWrite/Tests.hs+ other-modules: System.IO.SafeWrite+ ghc-options: -Wall+ hs-source-dirs: .+ build-depends:+ base > 4 && < 5,+ directory,+ filepath,+ unix,+ HUnit,+ test-framework,+ test-framework-hunit,+ test-framework-th++source-repository head+ type: git+ location: https://github.com/luispedro/safeio