xeme/xeme

Add a simple unit test for the input stream and fix a few issues
/*
* Copyright (C) 2023 Dodo Developers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <https://www.gnu.org/licenses/>.
*/
#include "dodocore.h"
#include "dodooutputstream.h"
#include "dodostring.h"
struct _DodoOutputStream {
DodoStream parent;
gboolean running;
GBytes *buffer;
GOutputStream *output;
};
G_DEFINE_TYPE(DodoOutputStream, dodo_output_stream, DODO_TYPE_STREAM)
/******************************************************************************
* Helpers
*****************************************************************************/
static GBytes *
dodo_output_stream_serialize(DodoOutputStream *output_stream) {
DodoStream *stream = DODO_STREAM(output_stream);
GBytes *bytes = NULL;
GString *str = NULL;
const char *value = NULL;
str = g_string_new(DODO_STREAM_PI);
g_string_append(str, "<stream:stream");
/* to and from are validated to not be empty in
* dodo_output_stream_start. */
g_string_append_printf(str, " from=\"%s\"", dodo_stream_get_from(stream));
g_string_append_printf(str, " to=\"%s\"", dodo_stream_get_to(stream));
value = dodo_stream_get_id(stream);
if(!dodo_str_is_empty(value)) {
g_string_append_printf(str, " id=\"%s\"", value);
}
value = dodo_stream_get_version(stream);
if(!dodo_str_is_empty(value)) {
g_string_append_printf(str, " version=\"%s\"", value);
}
value = dodo_stream_get_language(stream);
if(!dodo_str_is_empty(value)) {
g_string_append_printf(str, " xml:lang=\"%s\"", value);
}
g_string_append_printf(str, " xmlns=\"%s\"", DODO_STREAM_XMLNS);
g_string_append_printf(str, " xmlns:stream=\"%s\"",
DODO_STREAM_XMLNS_STREAM);
g_string_append(str, ">");
/* Create a GBytes with the value from the GString and free the GString
* container.
*/
bytes = g_bytes_new_take(str->str, str->len);
g_string_free(str, FALSE);
return bytes;
}
/******************************************************************************
* Callbacks
*****************************************************************************/
static void
dodo_output_stream_write_cb(G_GNUC_UNUSED GObject *source, GAsyncResult *res,
gpointer data)
{
DodoOutputStream *stream = data;
GError *error = NULL;
gssize written = 0;
gsize expected = 0;
expected = g_bytes_get_size(stream->buffer);
written = g_output_stream_write_bytes_finish(stream->output, res, &error);
/* handle error */
if((gsize)written != expected) {
GBytes *remaining = NULL;
GCancellable *cancellable = NULL;
remaining = g_bytes_new_from_bytes(stream->buffer, (gsize)written,
expected - (gsize)written);
g_set_object(&stream->buffer, remaining);
g_bytes_unref(remaining);
cancellable = dodo_stream_get_cancellable(DODO_STREAM(stream));
g_output_stream_write_bytes_async(stream->output, stream->buffer,
G_PRIORITY_DEFAULT, cancellable,
dodo_output_stream_write_cb, stream);
} else {
g_clear_pointer(&stream->buffer, g_bytes_unref);
}
}
/******************************************************************************
* GObject Implementation
*****************************************************************************/
static void
dodo_output_stream_finalize(GObject *obj) {
DodoOutputStream *stream = DODO_OUTPUT_STREAM(obj);
g_clear_pointer(&stream->buffer, g_bytes_unref);
g_clear_object(&stream->output);
G_OBJECT_CLASS(dodo_output_stream_parent_class)->finalize(obj);
}
static void
dodo_output_stream_init(DodoOutputStream *stream) {
stream->running = FALSE;
}
static void
dodo_output_stream_class_init(DodoOutputStreamClass *klass) {
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
obj_class->finalize = dodo_output_stream_finalize;
}
/******************************************************************************
* Public API
*****************************************************************************/
DodoOutputStream *
dodo_output_stream_new(const char *to, const char *from,
GCancellable *cancellable)
{
return g_object_new(
DODO_TYPE_OUTPUT_STREAM,
"cancellable", cancellable,
"from", from,
"to", to,
NULL);
}
gboolean
dodo_output_stream_start(DodoOutputStream *stream, GOutputStream *output,
GError **error)
{
GCancellable *cancellable = NULL;
g_return_val_if_fail(DODO_IS_OUTPUT_STREAM(stream), FALSE);
g_return_val_if_fail(G_IS_OUTPUT_STREAM(output), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if(stream->running) {
g_set_error_literal(error, DODO_DOMAIN, 0,
"output stream has already been started");
return FALSE;
}
if(!g_set_object(&stream->output, output)) {
g_set_error_literal(error, DODO_DOMAIN, 0,
"failed to set the output stream");
return FALSE;
}
cancellable = dodo_stream_get_cancellable(DODO_STREAM(stream));
stream->buffer = dodo_output_stream_serialize(stream);
g_output_stream_write_bytes_async(stream->output, stream->buffer,
G_PRIORITY_DEFAULT, cancellable,
dodo_output_stream_write_cb,
stream);
stream->running = TRUE;
return TRUE;
}
gboolean
dodo_output_stream_stop(DodoOutputStream *stream, GError **error) {
GCancellable *cancellable = NULL;
GError *local_error = NULL;
gboolean ret = TRUE;
g_return_val_if_fail(DODO_IS_OUTPUT_STREAM(stream), FALSE);
cancellable = dodo_stream_get_cancellable(DODO_STREAM(stream));
if(!g_output_stream_close(stream->output, cancellable, &local_error)) {
g_propagate_error(error, local_error);
ret = FALSE;
}
stream->output = NULL;
return ret;
}