@@ -9,15 +9,24 @@
9-- Stability : experimental
9-- Stability : experimental
10-- Portability : GHC
10-- Portability : GHC
12−-- The fundamental singleton APIs are 'getChunk' and 'putChunk' and the
12+-- The fundamental singleton IO APIs are 'getChunk' and 'putChunk' and the
13−-- fundamental stream APIs built on top of those are 'readChunksWithBufferOf'
13+-- fundamental stream IO APIs built on top of those are
14−-- and 'writeChunks'. Rest of this module is just combinatorial programming
14+-- 'readChunksWithBufferOf' and 'writeChunks'. Rest of this module is just
15−-- using these.
15+-- combinatorial programming using these.
17+-- TODO: Need a separate module for pread/pwrite based reading writing for
18+-- seekable devices. Stateless read/write can be helpful in multithreaded
17module Streamly.Internal.FileSystem.Handle
21module Streamly.Internal.FileSystem.Handle
19− -- ** Read from Handle
23+ -- * Singleton APIs
28+ -- * Byte Stream Read
21 -- , readUtf8
30 -- , readUtf8
22 -- , readLines
31 -- , readLines
23 -- , readFrames
32 -- , readFrames
@@ -26,16 +35,14 @@ module Streamly.Internal.FileSystem.Handle
27 , toBytesWithBufferOf
36 , toBytesWithBufferOf
29− -- -- * Array Read
38+ -- * Chunked Stream Read
32 , readChunks
39 , readChunks
33 , readChunksWithBufferOf
40 , readChunksWithBufferOf
35 , toChunksWithBufferOf
42 , toChunksWithBufferOf
36 , toChunks
43 , toChunks
38− -- ** Write to Handle
45+ -- * Byte Stream Write
39 -- Byte stream write (Folds)
46 -- Byte stream write (Folds)
@@ -45,25 +52,22 @@ module Streamly.Internal.FileSystem.Handle
45 -- , writeLines
52 -- , writeLines
46 , writeWithBufferOf
53 , writeWithBufferOf
48− -- Byte stream write (Streams)
49 , putBytes
55 , putBytes
50 , putBytesWithBufferOf
56 , putBytesWithBufferOf
52− -- -- * Array Write
58+ -- * Chunked Stream Write
54 , writeChunks
59 , writeChunks
55 , writeChunksWithBufferOf
60 , writeChunksWithBufferOf
57− -- -- * Array stream Write
58 , putChunksWithBufferOf
62 , putChunksWithBufferOf
59 , putChunks
63 , putChunks
61− -- -- * Random Access (Seek)
65+ -- * Random Access (Seek)
62− -- -- | Unlike the streaming APIs listed above, these APIs apply to devices or
66+ -- | Unlike the streaming APIs listed above, these APIs apply to devices or
63 -- files that have random access or seek capability. This type of devices
67 -- files that have random access or seek capability. This type of devices
64 -- include disks, files, memory devices and exclude terminals, pipes,
68 -- include disks, files, memory devices and exclude terminals, pipes,
65 -- sockets and fifos.
69 -- sockets and fifos.
67 -- We can also generate the request pattern using a funciton.
71 -- We can also generate the request pattern using a funciton.
69 -- , readIndex
73 -- , readIndex
@@ -76,7 +80,7 @@ module Streamly.Internal.FileSystem.Handle
77 -- , readChunksFrom
81 -- , readChunksFrom
78 -- , readChunksFromTo
82 -- , readChunksFromTo
79− -- , readChunksFromToWith
83+ , readChunksFromToWith
80 -- , readChunksFromThenToWith
84 -- , readChunksFromThenToWith
82 -- , writeIndex
86 -- , writeIndex
@@ -154,13 +158,16 @@ import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
154-- Array IO (Input)
158-- Array IO (Input)
155-------------------------------------------------------------------------------
159-------------------------------------------------------------------------------
157−-- | Read a 'ByteArray' from a file handle. If no data is available on the
161+-- | Read a 'ByteArray' consisting of one or more bytes from a file handle. If
158−-- handle it blocks until some data becomes available. If data is available
162+-- no data is available on the handle it blocks until at least one byte becomes
159−-- then it immediately returns that data without blocking. It reads a maximum
163+-- available. If any data is available then it immediately returns that data
164+-- without blocking. As a result of this behavior, it may read less than or
160−-- of up to the size requested.
165+-- equal to the size requested.
161{-# INLINABLE getChunk #-}
168{-# INLINABLE getChunk #-}
162−getChunk :: Int -> Handle -> IO (Array Word8)
169+getChunk :: MonadIO m => Int -> Handle -> m (Array Word8)
163−getChunk size h = do
170+getChunk size h = liftIO $ do
164 ptr <- mallocPlainForeignPtrBytes size
171 ptr <- mallocPlainForeignPtrBytes size
165 -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
172 -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
166 withForeignPtr ptr $ \p -> do
173 withForeignPtr ptr $ \p -> do
@@ -170,6 +177,18 @@ getChunk size h = do
170 unsafeFreezeWithShrink $
177 unsafeFreezeWithShrink $
171 mutableArray ptr (p `plusPtr` n) (p `plusPtr` size)
178 mutableArray ptr (p `plusPtr` n) (p `plusPtr` size)
180+-- This could be useful in implementing the "reverse" read APIs or if you want
181+-- to read arrays of exact size instead of compacting them later. Compacting
182+-- later requires more copying.
184+-- | Read a 'ByteArray' consisting of exactly the specified number of bytes
185+-- from a file handle.
188+{-# INLINABLE getChunkOf #-}
189+getChunkOf :: Int -> Handle -> IO (Array Word8)
190+getChunkOf = undefined
173-------------------------------------------------------------------------------
192-------------------------------------------------------------------------------
174-- Stream of Arrays IO
193-- Stream of Arrays IO
175-------------------------------------------------------------------------------
194-------------------------------------------------------------------------------
@@ -184,7 +203,7 @@ _toChunksWithBufferOf size h = go
185 -- XXX use cons/nil instead
204 -- XXX use cons/nil instead
186 go = mkStream $ \_ yld _ stp -> do
205 go = mkStream $ \_ yld _ stp -> do
187− arr <- liftIO $ getChunk size h
206+ arr <- getChunk size h
188 if A.length arr == 0
207 if A.length arr == 0
190 else yld arr go
209 else yld arr go
@@ -203,7 +222,7 @@ toChunksWithBufferOf size h = D.fromStreamD (D.Stream step ())
204 {-# INLINE_LATE step #-}
223 {-# INLINE_LATE step #-}
205 step _ _ = do
224 step _ _ = do
206− arr <- liftIO $ getChunk size h
225+ arr <- getChunk size h
208 case A.length arr of
227 case A.length arr of
209 0 -> D.Stop
228 0 -> D.Stop
@@ -221,12 +240,27 @@ readChunksWithBufferOf = Unfold step return
222 {-# INLINE_LATE step #-}
241 {-# INLINE_LATE step #-}
223 step (size, h) = do
242 step (size, h) = do
224− arr <- liftIO $ getChunk size h
243+ arr <- getChunk size h
226 case A.length arr of
245 case A.length arr of
227 0 -> D.Stop
246 0 -> D.Stop
228 _ -> D.Yield arr (size, h)
247 _ -> D.Yield arr (size, h)
249+-- There are two ways to implement this.
251+-- 1. Idiomatic: use a scan on the output of readChunksWithBufferOf to total
252+-- the array lengths and trim the last array to correct size.
253+-- 2. Simply implement it from scratch like readChunksWithBufferOf.
255+-- | The input to the unfold is @(from, to, bufferSize, handle)@. It starts
256+-- reading from the offset `from` in the file and reads up to the offset `to`.
259+{-# INLINE_NORMAL readChunksFromToWith #-}
260+readChunksFromToWith :: -- MonadIO m =>
261+ Unfold m (Int, Int, Int, Handle) (Array Word8)
262+readChunksFromToWith = undefined
230-- XXX read 'Array a' instead of Word8
264-- XXX read 'Array a' instead of Word8
232-- | @toChunks handle@ reads a stream of arrays from the specified file
266-- | @toChunks handle@ reads a stream of arrays from the specified file
@@ -316,7 +350,7 @@ toBytes = AS.concat . toChunks
317-- | Write an 'Array' to a file handle.
351-- | Write an 'Array' to a file handle.
319−-- @since 0.7.0
353+-- @since 0.8.1
320{-# INLINABLE putChunk #-}
354{-# INLINABLE putChunk #-}
321putChunk :: (MonadIO m, Storable a) => Handle -> Array a -> m ()
355putChunk :: (MonadIO m, Storable a) => Handle -> Array a -> m ()
322putChunk _ arr | A.length arr == 0 = return ()
356putChunk _ arr | A.length arr == 0 = return ()