{-| Module : Data.Blob Stability : Experimental Portability : non-portable (requires POSIX) This module provides interface for handling large objects - also known as blobs. One of the use cases for bloc is storing large objects in databases. Instead of storing the entire blob in the database, you can just store the 'BlobId' of the blob. Multiple values in the database can share a 'BlobId' and we provide an interface for garbage collection for such cases. -} module Data.Blob ( Blob (..) , BlobId , BlobStore , WriteContext , ReadContext , openBlobStore , newBlob , writePartial , endWrite , createBlob , startRead , readPartial , skipBytes , endRead , readBlob , deleteBlob ) where import qualified Crypto.Hash.SHA512 as SHA512 import qualified Data.Blob.FileOperations as F import Data.Blob.GC (markAsAccessible) import Data.Blob.Types -- | All the blobs of an application are stored in the -- same directory. 'openBlobStore' returns the 'BlobStore' -- corresponding to a given directory. openBlobStore :: FilePath -> IO BlobStore openBlobStore dir = do F.createTempIfMissing dir F.createCurrIfMissing dir return $ BlobStore dir -- | Creates an empty blob in the given BlobStore. -- -- Use 'writePartial' to write contents to the newly -- created blob. newBlob :: BlobStore -> IO WriteContext newBlob (BlobStore dir) = do filename <- F.createUniqueFile dir let temploc = TempLocation dir filename h <- F.openFileForWrite $ F.getTempPath temploc return $ WriteContext temploc h SHA512.init -- | 'writePartial' appends the given blob to the -- blob referenced by the 'WriteContext'. writePartial :: WriteContext -> Blob -> IO WriteContext writePartial (WriteContext l h ctx) (Blob b) = do F.writeToHandle h b let newctx = SHA512.update ctx b return $ WriteContext l h newctx -- | Finalize the write to the given blob. -- -- After calling 'endWrite' no more updates are possible -- on the blob. endWrite :: WriteContext -> IO BlobId endWrite (WriteContext l h ctx) = do F.syncAndClose h markAsAccessible blobId F.moveFile (F.getTempPath l) (baseDir l) newfilename return blobId where newfilename = "sha512-" ++ F.toFileName (SHA512.finalize ctx) blobId = BlobId (baseDir l) newfilename -- | Create a blob from the given contents. -- -- Use 'createBlob' only for small contents. -- For large contents, use the partial write interface -- ('newBlob' followed by calls to 'writePartial'). createBlob :: BlobStore -> Blob -> IO BlobId createBlob blobstore blob = newBlob blobstore >>= \wc -> writePartial wc blob >>= endWrite -- | Open blob for reading. startRead :: BlobId -> IO ReadContext startRead loc = fmap ReadContext $ F.openFileForRead (F.getCurrPath loc) -- | Read given number of bytes from the blob. readPartial :: ReadContext -> Int -> IO Blob readPartial (ReadContext h) sz = fmap Blob $ F.readFromHandle h sz -- | Skip given number of bytes ahead in the blob. skipBytes :: ReadContext -> Integer -> IO () skipBytes (ReadContext h) = F.seekHandle h -- | Complete reading from a blob. endRead :: ReadContext -> IO () endRead (ReadContext h) = F.closeHandle h -- | 'readBlob' reads an entire blob. -- -- Use 'readBlob' only for small blobs. -- For large blobs, use 'readPartial' instead. readBlob :: BlobId -> IO Blob readBlob blobid = fmap Blob $ F.readFile (F.getCurrPath blobid) -- | Deletes the given blob. -- -- Use 'deleteBlob' only when you are sure that the given blob -- is not accessible by anyone. If the blob is shared, you should -- use the GC interface instead. deleteBlob :: BlobId -> IO () deleteBlob = F.deleteFile . F.getCurrPath