|
2 | 2 |
|
3 | 3 | module Distribution.Client.Utils ( MergeResult(..) |
4 | 4 | , mergeBy, duplicates, duplicatesBy |
5 | | - , moreRecentFile, inDir, numberOfProcessors ) |
| 5 | + , moreRecentFile, inDir, numberOfProcessors |
| 6 | + , writeFileAtomic ) |
6 | 7 | where |
7 | 8 |
|
8 | 9 | import Data.List |
9 | 10 | ( sortBy, groupBy ) |
10 | 11 | import Foreign.C.Types ( CInt(..) ) |
11 | 12 | import System.Directory |
12 | 13 | ( doesFileExist, getModificationTime |
13 | | - , getCurrentDirectory, setCurrentDirectory ) |
| 14 | + , getCurrentDirectory, setCurrentDirectory |
| 15 | + , renameFile, removeFile ) |
| 16 | +import System.FilePath ( splitFileName, (<.>) ) |
| 17 | +import System.IO ( openBinaryTempFile, hClose ) |
14 | 18 | import System.IO.Unsafe ( unsafePerformIO ) |
15 | 19 | import qualified Control.Exception as Exception |
16 | | - ( finally ) |
| 20 | + ( bracketOnError, finally ) |
| 21 | +import qualified Data.ByteString.Lazy as BS |
17 | 22 |
|
18 | 23 | -- | Generic merging utility. For sorted input lists this is a full outer join. |
19 | 24 | -- |
@@ -72,3 +77,22 @@ foreign import ccall "getNumberOfProcessors" c_getNumberOfProcessors :: IO CInt |
72 | 77 | -- program, so unsafePerformIO is safe here. |
73 | 78 | numberOfProcessors :: Int |
74 | 79 | numberOfProcessors = fromEnum $ unsafePerformIO c_getNumberOfProcessors |
| 80 | + |
| 81 | +-- | Writes a file atomically. |
| 82 | +-- |
| 83 | +-- The file is either written sucessfully or an IO exception is raised and |
| 84 | +-- the original file is left unchanged. |
| 85 | +-- |
| 86 | +-- On windows it is not possible to delete a file that is open by a process. |
| 87 | +-- This case will give an IO exception but the atomic property is not affected. |
| 88 | +-- |
| 89 | +writeFileAtomic :: FilePath -> BS.ByteString -> IO () |
| 90 | +writeFileAtomic targetPath content = do |
| 91 | + let (targetDir, targetFile) = splitFileName targetPath |
| 92 | + Exception.bracketOnError |
| 93 | + (openBinaryTempFile targetDir $ targetFile <.> "tmp") |
| 94 | + (\(tmpPath, handle) -> hClose handle >> removeFile tmpPath) |
| 95 | + (\(tmpPath, handle) -> do |
| 96 | + BS.hPut handle content |
| 97 | + hClose handle |
| 98 | + renameFile tmpPath targetPath) |
0 commit comments