stream.lua

---------------------------------------------------------------------------
--- Utilities to handle Gio streams.
--
-- @module stream
-- @license GPL v3.0
---------------------------------------------------------------------------

local async = require("async")
local lgi = require("lgi")
local Gio = lgi.Gio
local GLib = lgi.GLib

local stream = {}


--- Constructors
-- @section constructors

--- Creates a dummy input stream.
--
-- Gio currently supports asynchronous splicing only between IOStreams, which combine both an input and output stream.
-- To be able to splice from just an output stream to just an input stream, dummy streams can be used to provide
-- the "ignored" side of the pipe.
--
-- See [docs.gtk.org](https://docs.gtk.org/gio/class.MemoryInputStream.html) for additional details.
--
-- @treturn Gio.MemoryInputStream
function stream.new_dummy_input()
    return Gio.MemoryInputStream.new()
end


--- Creates a dummy output stream.
--
-- Gio currently supports asynchronous splicing only between IOStreams, which combine both an input and output stream.
-- To be able to splice from just an output stream to just an input stream, dummy streams can be used to provide
-- the "ignored" side of the pipe.
--
-- See [docs.gtk.org](https://docs.gtk.org/gio/class.MemoryOutputStream.html) for additional details.
--
-- @treturn Gio.MemoryOutputStream
function stream.new_dummy_output()
    return Gio.MemoryOutputStream.new()
end


--- Combines an input and output stream into a single IOStream.
--
-- Either side may be omitted, in which case a dummy stream is used instead.
--
-- See [docs.gtk.org](https://docs.gtk.org/gio/class.SimpleIOStream.html) for additional details.
--
-- @tparam[opt] Gio.InputStream input_stream
-- @tparam[opt] Gio.OutputStream output_stream
-- @treturn Gio.SimpleIOStream
function stream.to_io_stream(input_stream, output_stream)
    input_stream = input_stream or stream.new_dummy_input()
    output_stream = output_stream or stream.new_dummy_output()
    return Gio.SimpleIOStream.new(input_stream, output_stream)
end


--- Utilities
-- @section utilities


--- Reads the entire stream into memory.
--
-- @since 0.2.0
-- @async
-- @tparam Gio.InputStream stream The stream to read from.
-- @tparam function cb
-- @treturn[opt] GLib.Error
-- @treturn nil|string
function stream.read_string(stream, cb)
    local priority = GLib.PRIORITY_DEFAULT
    local BUFFER_SIZE = 4096
    local str

    local function read_chunk(cb_chunk)
        stream:read_bytes_async(BUFFER_SIZE, priority, nil, function(_, token)
            local bytes, err = stream:read_bytes_finish(token)

            if err then
                return cb_chunk(err)
            end

            if bytes and #bytes > 0 then
                if not str then
                    str = bytes:get_data()
                else
                    str = str .. bytes:get_data()
                end
            end

            cb_chunk(nil, bytes)
        end)
    end

    local function check(bytes, cb_check)
        cb_check(nil, bytes ~= nil and #bytes == BUFFER_SIZE)
    end

    async.do_while(read_chunk, check, function(err)
        cb(err, str)
    end)
end

return stream