@@ -11,23 +11,66 @@
11-- Copyright : (c) 2018 Composewell Technologies
11-- Copyright : (c) 2018 Composewell Technologies
13-- License : BSD3
13-- License : BSD3
14−-- Maintainer : harendra.kumar@gmail.com
14+-- Maintainer : streamly@composewell.com
15-- Stability : experimental
15-- Stability : experimental
16-- Portability : GHC
16-- Portability : GHC
18−-- Streaming based IO write rotuines based on file handles.
20module Streamly.Internal.FileSystem.Handle
18module Streamly.Internal.FileSystem.Handle
22− writeArray
20+ -- ** Read from Handle
35+ -- ** Write to Handle
36+ -- Byte stream write (Folds)
39+ -- , writeUtf8ByLines
24− -- Byte stream write
43+ -- Byte stream write (Streams)
26 , writeSInChunksOf
45 , writeSInChunksOf
50+ , writeArraysInChunksOf
28 -- -- * Array stream Write
52 -- -- * Array stream Write
29 , writeSArrays
53 , writeSArrays
30 , writeSArraysInChunksOf
54 , writeSArraysInChunksOf
56+ -- -- * Random Access (Seek)
57+ -- -- | Unlike the streaming APIs listed above, these APIs apply to devices or
58+ -- files that have random access or seek capability. This type of devices
59+ -- include disks, files, memory devices and exclude terminals, pipes,
60+ -- sockets and fifos.
65+ -- , readAt -- read from a given position to th end of file
66+ -- , readSliceArrayUpto
67+ -- , readSliceArrayOf
72+ -- , writeAt -- start writing at the given position
@@ -35,18 +78,144 @@ import Control.Monad.IO.Class (MonadIO(..))
35import Data.Word (Word8)
78import Data.Word (Word8)
36import Foreign.ForeignPtr (withForeignPtr)
79import Foreign.ForeignPtr (withForeignPtr)
37import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
80import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
38−import Foreign.Ptr (minusPtr)
81+import Foreign.Ptr (minusPtr, plusPtr)
39import Foreign.Storable (Storable(..))
82import Foreign.Storable (Storable(..))
83+import GHC.ForeignPtr (mallocPlainForeignPtrBytes)
40−import System.IO (Handle, hPutBuf)
84+import System.IO (Handle, hGetBufSome, hPutBuf)
41import Prelude hiding (read)
85import Prelude hiding (read)
87+import Streamly.Data.Fold (Fold)
43−import Streamly.Internal.Memory.Array.Types (Array(..))
88+import Streamly.Internal.Memory.Array.Types
89+ (Array(..), writeNUnsafe, defaultChunkSize, shrinkToFit,
44import Streamly.Streams.Serial (SerialT)
91import Streamly.Streams.Serial (SerialT)
45−import Streamly.Internal.Memory.Array.Types (defaultChunkSize)
92+import Streamly.Streams.StreamK.Type (IsStream, mkStream)
93+-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
47−import qualified Streamly.Memory.Array as A
95+import qualified Streamly.Data.Fold as FL
96+import qualified Streamly.Internal.Data.Fold.Types as FL
48import qualified Streamly.Internal.Memory.ArrayStream as AS
97import qualified Streamly.Internal.Memory.ArrayStream as AS
49import qualified Streamly.Internal.Prelude as S
98import qualified Streamly.Internal.Prelude as S
99+import qualified Streamly.Memory.Array as A
100+import qualified Streamly.Streams.StreamD.Type as D
102+-------------------------------------------------------------------------------
104+-------------------------------------------------------------------------------
106+-- The following references may be useful to build an understanding about the
109+-- http://www.linux-mag.com/id/308/ for blocking/non-blocking IO on linux.
110+-- https://lwn.net/Articles/612483/ Non-blocking buffered file read operations
111+-- https://en.wikipedia.org/wiki/C_file_input/output for C APIs.
112+-- https://docs.oracle.com/javase/tutorial/essential/io/file.html for Java API.
113+-- https://www.w3.org/TR/FileAPI/ for http file API.
115+-------------------------------------------------------------------------------
117+-------------------------------------------------------------------------------
119+-- | Read a 'ByteArray' from a file handle. If no data is available on the
120+-- handle it blocks until some data becomes available. If data is available
121+-- then it immediately returns that data without blocking. It reads a maximum
122+-- of up to the size requested.
123+{-# INLINABLE readArrayUpto #-}
124+readArrayUpto :: Int -> Handle -> IO (Array Word8)
125+readArrayUpto size h = do
126+ ptr <- mallocPlainForeignPtrBytes size
127+ -- ptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: Word8))
128+ withForeignPtr ptr $ \p -> do
129+ n <- hGetBufSome h p size
132+ , aEnd = p `plusPtr` n
133+ , aBound = p `plusPtr` size
135+ -- XXX shrink only if the diff is significant
138+-------------------------------------------------------------------------------
139+-- Stream of Arrays IO
140+-------------------------------------------------------------------------------
142+-- | @readArraysOf size h@ reads a stream of arrays from file handle @h@.
143+-- The maximum size of a single array is specified by @size@. The actual size
144+-- read may be less than or equal to @size@.
145+{-# INLINABLE _readArraysOf #-}
146+_readArraysOf :: (IsStream t, MonadIO m)
147+ => Int -> Handle -> t m (Array Word8)
148+_readArraysOf size h = go
150+ -- XXX use cons/nil instead
151+ go = mkStream $ \_ yld _ stp -> do
152+ arr <- liftIO $ readArrayUpto size h
153+ if A.length arr == 0
157+-- | @readArraysOf size handle@ reads a stream of arrays from the file
158+-- handle @handle@. The maximum size of a single array is limited to @size@.
159+-- The actual size read may be less than or equal to @size@.
162+{-# INLINE_NORMAL readArraysOf #-}
163+readArraysOf :: (IsStream t, MonadIO m) => Int -> Handle -> t m (Array Word8)
164+readArraysOf size h = D.fromStreamD (D.Stream step ())
166+ {-# INLINE_LATE step #-}
168+ arr <- liftIO $ readArrayUpto size h
170+ case A.length arr of
174+-- XXX read 'Array a' instead of Word8
176+-- | @readArrays handle@ reads a stream of arrays from the specified file
177+-- handle. The maximum size of a single array is limited to
178+-- @defaultChunkSize@. The actual size read may be less than or equal to
179+-- @defaultChunkSize@.
181+-- > readArrays = readArraysOf defaultChunkSize
184+{-# INLINE readArrays #-}
185+readArrays :: (IsStream t, MonadIO m) => Handle -> t m (Array Word8)
186+readArrays = readArraysOf defaultChunkSize
188+-------------------------------------------------------------------------------
189+-- Read File to Stream
190+-------------------------------------------------------------------------------
192+-- TODO for concurrent streams implement readahead IO. We can send multiple
193+-- read requests at the same time. For serial case we can use async IO. We can
194+-- also control the read throughput in mbps or IOPS.
196+-- | @readInChunksOf chunkSize handle@ reads a byte stream from a file
197+-- handle, reads are performed in chunks of up to @chunkSize@.
200+{-# INLINE readInChunksOf #-}
201+readInChunksOf :: (IsStream t, MonadIO m) => Int -> Handle -> t m Word8
202+readInChunksOf chunkSize h = AS.concat $ readArraysOf chunkSize h
205+-- Generate a stream of elements of the given type from a file 'Handle'.
206+-- read :: (IsStream t, MonadIO m, Storable a) => Handle -> t m a
208+-- > read = 'readInChunksOf' A.defaultChunkSize
209+-- | Generate a byte stream from a file 'Handle'.
213+read :: (IsStream t, MonadIO m) => Handle -> t m Word8
214+read = AS.concat . readArrays
216+-------------------------------------------------------------------------------
218+-------------------------------------------------------------------------------
51-------------------------------------------------------------------------------
220-------------------------------------------------------------------------------
52-- Array IO (output)
221-- Array IO (output)
@@ -113,3 +282,230 @@ writeSInChunksOf n h m = writeSArrays h $ S.arraysOf n m
113{-# INLINE writeS #-}
282{-# INLINE writeS #-}
114writeS :: MonadIO m => Handle -> SerialT m Word8 -> m ()
283writeS :: MonadIO m => Handle -> SerialT m Word8 -> m ()
115writeS = writeSInChunksOf defaultChunkSize
284writeS = writeSInChunksOf defaultChunkSize
286+-- | Write a stream of arrays to a handle.
289+{-# INLINE writeArrays #-}
290+writeArrays :: (MonadIO m, Storable a) => Handle -> Fold m (Array a) ()
291+writeArrays h = FL.drainBy (liftIO . writeArray h)
293+-- | @writeArraysInChunksOf chunkSize handle@ writes a stream of arrays
294+-- to @handle@ after coalescing the adjacent arrays in chunks of @chunkSize@.
295+-- We never split an array, if a single array is bigger than the specified size
296+-- it emitted as it is. Multiple arrays are coalesed as long as the total size
297+-- remains below the specified size.
300+{-# INLINE writeArraysInChunksOf #-}
301+writeArraysInChunksOf :: (MonadIO m, Storable a)
302+ => Int -> Handle -> Fold m (Array a) ()
303+writeArraysInChunksOf n h = lpackArraysChunksOf n (writeArrays h)
305+-- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes.
308+-- Note that if you use a chunk size less than 8K (GHC's default buffer
309+-- size) then you are advised to use 'NOBuffering' mode on the 'Handle' in case you
310+-- do not want buffering to occur at GHC level as well. Same thing applies to
313+-- | @writeInChunksOf chunkSize handle@ writes the input stream to @handle@ in
314+-- chunks of @chunkSize@. A write is performed to the IO device as soon as we
315+-- collect the required input size.
318+{-# INLINE writeInChunksOf #-}
319+writeInChunksOf :: MonadIO m => Int -> Handle -> Fold m Word8 ()
320+writeInChunksOf n h = FL.lchunksOf n (writeNUnsafe n) (writeArrays h)
322+-- > write = 'writeInChunksOf' A.defaultChunkSize
324+-- | Write a byte stream to a file handle. Accumulates the input in chunks of
325+-- up to 'A.defaultChunkSize' before writing.
329+write :: MonadIO m => Handle -> Fold m Word8 ()
330+write = writeInChunksOf defaultChunkSize
334+write :: (MonadIO m, Storable a) => Handle -> SerialT m a -> m ()
335+write = toHandleWith A.defaultChunkSize
338+-- XXX mmap a file into an array. This could be useful for in-place operations
339+-- on a file. For example, we can quicksort the contents of a file by mmapping
342+-------------------------------------------------------------------------------
343+-- IO with encoding/decoding Unicode characters
344+-------------------------------------------------------------------------------
348+-- > readUtf8 = decodeUtf8 . read
350+-- Read a UTF8 encoded stream of unicode characters from a file handle.
353+{-# INLINE readUtf8 #-}
354+readUtf8 :: (IsStream t, MonadIO m) => Handle -> t m Char
355+readUtf8 = decodeUtf8 . read
358+-- > writeUtf8 h s = write h $ encodeUtf8 s
360+-- Encode a stream of unicode characters to UTF8 and write it to the given file
361+-- handle. Default block buffering applies to the writes.
364+{-# INLINE writeUtf8 #-}
365+writeUtf8 :: MonadIO m => Handle -> SerialT m Char -> m ()
366+writeUtf8 h s = write h $ encodeUtf8 s
368+-- | Write a stream of unicode characters after encoding to UTF-8 in chunks
369+-- separated by a linefeed character @'\n'@. If the size of the buffer exceeds
370+-- @defaultChunkSize@ and a linefeed is not yet found, the buffer is written
371+-- anyway. This is similar to writing to a 'Handle' with the 'LineBuffering'
375+{-# INLINE writeUtf8ByLines #-}
376+writeUtf8ByLines :: (IsStream t, MonadIO m) => Handle -> t m Char -> m ()
377+writeUtf8ByLines = undefined
379+-- | Read UTF-8 lines from a file handle and apply the specified fold to each
380+-- line. This is similar to reading a 'Handle' with the 'LineBuffering' option.
383+{-# INLINE readLines #-}
384+readLines :: (IsStream t, MonadIO m) => Handle -> Fold m Char b -> t m b
385+readLines h f = foldLines (readUtf8 h) f
387+-------------------------------------------------------------------------------
388+-- Framing on a sequence
389+-------------------------------------------------------------------------------
391+-- | Read a stream from a file handle and split it into frames delimited by
392+-- the specified sequence of elements. The supplied fold is applied on each
396+{-# INLINE readFrames #-}
397+readFrames :: (IsStream t, MonadIO m, Storable a)
398+ => Array a -> Handle -> Fold m a b -> t m b
399+readFrames = undefined -- foldFrames . read
401+-- | Write a stream to the given file handle buffering up to frames separated
402+-- by the given sequence or up to a maximum of @defaultChunkSize@.
405+{-# INLINE writeByFrames #-}
406+writeByFrames :: (IsStream t, MonadIO m, Storable a)
407+ => Array a -> Handle -> t m a -> m ()
408+writeByFrames = undefined
410+-------------------------------------------------------------------------------
412+-------------------------------------------------------------------------------
414+-- | Write collecting the input in sessions of n seconds or if chunkSize
416+{-# INLINE writeByChunksOrSessionsOf #-}
417+writeByChunksOrSessionsOf :: MonadIO m
418+ => Int -> Double -> Handle -> SerialT m Word8 -> m ()
419+writeByChunksOrSessionsOf chunkSize sessionSize h m = undefined
421+-- | Write collecting the input in sessions of n seconds or if defaultChunkSize
423+{-# INLINE writeBySessionsOf #-}
424+writeBySessionsOf :: MonadIO m => Double -> Handle -> SerialT m Word8 -> m ()
425+writeBySessionsOf n = writeByChunksOrSessionsOf defaultChunkSize n
427+-------------------------------------------------------------------------------
428+-- Random Access IO (Seek)
429+-------------------------------------------------------------------------------
431+-- XXX handles could be shared, so we may not want to use the handle state at
432+-- all for these APIs. we can use pread and pwrite instead. On windows we will
433+-- need to use readFile/writeFile with an offset argument.
435+-------------------------------------------------------------------------------
437+-- | Read the element at the given index treating the file as an array.
440+{-# INLINE readIndex #-}
441+readIndex :: Storable a => Handle -> Int -> Maybe a
442+readIndex arr i = undefined
444+-- NOTE: To represent a range to read we have chosen (start, size) instead of
445+-- (start, end). This helps in removing the ambiguity of whether "end" is
446+-- included in the range or not.
448+-- We could avoid specifying the range to be read and instead use "take size"
449+-- on the stream, but it may end up reading more and then consume it partially.
451+-- | @readSliceWith chunkSize handle pos len@ reads up to @len@ bytes
452+-- from @handle@ starting at the offset @pos@ from the beginning of the file.
454+-- Reads are performed in chunks of size @chunkSize@. For block devices, to
455+-- avoid reading partial blocks @chunkSize@ must align with the block size of
456+-- the underlying device. If the underlying block size is unknown, it is a good
457+-- idea to keep it a multiple 4KiB. This API ensures that the start of each
458+-- chunk is aligned with @chunkSize@ from second chunk onwards.
460+{-# INLINE readSliceWith #-}
461+readSliceWith :: (IsStream t, MonadIO m, Storable a)
462+ => Int -> Handle -> Int -> Int -> t m a
463+readSliceWith chunkSize h pos len = undefined
465+-- | @readSlice h i count@ streams a slice from the file handle @h@ starting
466+-- at index @i@ and reading up to @count@ elements in the forward direction
467+-- ending at the index @i + count - 1@.
470+{-# INLINE readSlice #-}
471+readSlice :: (IsStream t, MonadIO m, Storable a)
472+ => Handle -> Int -> Int -> t m a
473+readSlice = readSliceWith A.defaultChunkSize
475+-- | @readSliceRev h i count@ streams a slice from the file handle @h@ starting
476+-- at index @i@ and reading up to @count@ elements in the reverse direction
477+-- ending at the index @i - count + 1@.
480+{-# INLINE readSliceRev #-}
481+readSliceRev :: (IsStream t, MonadIO m, Storable a)
482+ => Handle -> Int -> Int -> t m a
483+readSliceRev h i count = undefined
485+-- | Write the given element at the given index in the file.
488+{-# INLINE writeIndex #-}
489+writeIndex :: (MonadIO m, Storable a) => Handle -> Int -> a -> m ()
490+writeIndex h i a = undefined
492+-- | @writeSlice h i count stream@ writes a stream to the file handle @h@
493+-- starting at index @i@ and writing up to @count@ elements in the forward
494+-- direction ending at the index @i + count - 1@.
497+{-# INLINE writeSlice #-}
498+writeSlice :: (IsStream t, Monad m, Storable a)
499+ => Handle -> Int -> Int -> t m a -> m ()
500+writeSlice h i len s = undefined
502+-- | @writeSliceRev h i count stream@ writes a stream to the file handle @h@
503+-- starting at index @i@ and writing up to @count@ elements in the reverse
504+-- direction ending at the index @i - count + 1@.
507+{-# INLINE writeSliceRev #-}
508+writeSliceRev :: (IsStream t, Monad m, Storable a)
509+ => Handle -> Int -> Int -> t m a -> m ()
510+writeSliceRev arr i len s = undefined