packages feed

hash-addressed 0.0.0.0 → 0.0.1.0

raw patch · 3 files changed

+63/−33 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ HashAddressed.Directory: writeEither :: ContentAddressedDirectory -> (forall m. MonadIO m => (ByteString -> m ()) -> m (Either bad good)) -> IO (Either bad (good, WriteResult))

Files

changelog.md view
@@ -1,3 +1,7 @@+## 0.0.1.0 (2023-01-27)++Add `HashAddressed.Directory.writeEither`+ ## 0.0.0.0 (2023-01-27)  Initial release
hash-addressed.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0  name: hash-addressed-version: 0.0.0.0+version: 0.0.1.0 synopsis: Hash-addressed file storage category: Hash, Filesystem 
library/HashAddressed/Directory.hs view
@@ -1,7 +1,8 @@ module HashAddressed.Directory   (     {- * Type -} ContentAddressedDirectory, init,-    {- * Write operations -} writeStreaming, writeLazy,+    {- * Write operations -}+            writeLazy, writeStreaming, writeEither,             WriteResult (..), WriteType (..),   )   where@@ -10,6 +11,7 @@ import HashAddressed.HashFunction  import Control.Monad.IO.Class (MonadIO)+import Data.Either (Either) import Data.Function (flip) import Prelude (FilePath, IO) import System.FilePath ((</>))@@ -25,6 +27,7 @@ import qualified Data.ByteString.Char8 as Strict.ByteString.Char8 import qualified Data.ByteString.Lazy as Lazy import qualified Data.ByteString.Lazy as Lazy.ByteString+import qualified Data.Either as Either import qualified System.Directory as Directory import qualified System.IO as IO import qualified System.IO.Temp as Temporary@@ -52,14 +55,16 @@ init SHA_256 = ContentAddressedDirectory  {-| Write a stream of strict byte strings to a content-addressed directory -}-writeStreaming ::+writeEither ::     ContentAddressedDirectory         {- ^ The content-addressed file store to write to; see 'init' -}-    -> (forall m. MonadIO m => (Strict.ByteString -> m ()) -> m ())+    -> (forall m. MonadIO m => (Strict.ByteString -> m ()) -> m (Either bad good))         {- ^ Monadic action which is allowed to emit 'Strict.ByteString's-             and do I/O -}-    -> IO WriteResult-writeStreaming cafs continue = Resource.runResourceT @IO+             and do I/O. The action should return 'Either.Right' once the content+             has been successfully written. If the action returns 'Either.Left' or+             throws an exception, then nothing will be committed to the store. -}+    -> IO (Either bad (good, WriteResult))+writeEither dir stream = Resource.runResourceT @IO   do     {-  Where the system in general keeps its temporary files  -}     temporaryRoot <- Monad.lift Temporary.getCanonicalTemporaryDirectory@@ -70,7 +75,7 @@     {-  We will write the file into this directory and then move it out in an         atomic rename operation that will commit the file to the store.  -}     (_, temporaryDirectory) <- Resource.allocate-        (Temporary.createTempDirectory temporaryRoot "cafs")+        (Temporary.createTempDirectory temporaryRoot "hash-addressed")         Directory.removeDirectoryRecursive {- (🧹) -}      {-  If the file never gets moved, then when the directory is removed@@ -80,7 +85,7 @@         but the file, which no longer resides within the directory, will remain. -}      {-  The path of the file we're writing, in its temporary location  -}-    let temporaryFile = temporaryDirectory </> "cafs-file"+    let temporaryFile = temporaryDirectory </> "hash-addressed-file"      {-  Create the file and open a handle to write to it  -}     (handleRelease, handle) <- Resource.allocate@@ -89,8 +94,8 @@      {-  Run the continuation, doing two things at once with the byte string         chunks it gives us:  -}-    hashState :: Hash.Ctx <- Monad.lift $ flip Monad.execStateT Hash.init $-        continue \chunk ->+    (badOrGood, hashState :: Hash.Ctx) <- Monad.lift $ flip Monad.runStateT Hash.init $+        stream \chunk ->           do             {-  1. Write to the file  -}             Monad.lift $ Strict.ByteString.hPut handle chunk@@ -101,33 +106,54 @@     {-  Once we're done writing the file, we no longer need the handle.  -}     Resource.release handleRelease {- (🍓) -} -    {-  The final location where the file will reside  -}-    let contentAddressedFile = directory cafs </>-            Strict.ByteString.Char8.unpack-                (Base16.encode (Hash.finalize hashState))+    case badOrGood of+        Either.Left bad -> pure $ Either.Left bad+        Either.Right good -> do -    {-  Another file of the same name in the content-addressed directory-        might already exist.  -}-    writeType <- Monad.lift (Directory.doesPathExist contentAddressedFile)-          <&> \case{ True -> AlreadyPresent; False -> NewContent }+            {-  The final location where the file will reside  -}+            let contentAddressedFile = directory dir </>+                    Strict.ByteString.Char8.unpack+                        (Base16.encode (Hash.finalize hashState)) -    case writeType of+            {-  Another file of the same name in the content-addressed directory+                might already exist.  -}+            writeType <- Monad.lift (Directory.doesPathExist contentAddressedFile)+                  <&> \case{ True -> AlreadyPresent; False -> NewContent } -        {-  In one atomic step, this action commits the file to the store-            and prevents it from being deleted by the directory cleanup-            action (🧹).  -}-        NewContent -> Monad.lift $-            Directory.renamePath temporaryFile contentAddressedFile+            case writeType of -        {-  Since the store is content-addressed, we assume that two files-            with the same name have the same contents. Therefore, if a file-            already exists at this path, there is no reason to take any-            action.  -}-        AlreadyPresent -> pure ()+                {-  In one atomic step, this action commits the file to the store+                    and prevents it from being deleted by the directory cleanup+                    action (🧹).  -}+                NewContent -> Monad.lift $+                    Directory.renamePath temporaryFile contentAddressedFile -    pure WriteResult{ contentAddressedFile, writeType }+                {-  Since the store is content-addressed, we assume that two files+                    with the same name have the same contents. Therefore, if a file+                    already exists at this path, there is no reason to take any+                    action.  -}+                AlreadyPresent -> pure () -{-| Write a lazy byte string to a content-addressed directory -}+            pure $ Either.Right (good, WriteResult{ contentAddressedFile, writeType })++{-| Write a stream of strict byte strings to a content-addressed directory++This is a simplified variant of 'writeEither'. -}+writeStreaming ::+    ContentAddressedDirectory+        {- ^ The content-addressed file store to write to; see 'init' -}+    -> (forall m. MonadIO m => (Strict.ByteString -> m ()) -> m ())+        {- ^ Monadic action which is allowed to emit 'Strict.ByteString's+             and do I/O. If this action throws an exception, nothing will+             be written to the store. -}+    -> IO WriteResult+writeStreaming dir stream = writeEither dir (fmap Either.Right . stream) <&> \case+    Either.Left x -> absurd x+    Either.Right ((), result) -> result++{-| Write a lazy byte string to a content-addressed directory++This is a simplified variant of 'writeStreaming'. -} writeLazy ::     ContentAddressedDirectory         {- ^ The content-addressed file store to write to; see 'init' -}@@ -136,5 +162,5 @@     -> IO WriteResult         {- ^ The file path where the contents of the lazy byte string              now reside. This path includes the store directory. -}-writeLazy cafs lbs = writeStreaming cafs \writeChunk ->+writeLazy dir lbs = writeStreaming dir \writeChunk ->     traverse_ writeChunk (Lazy.ByteString.toChunks lbs)