From 5d4688679e9d3f9daa00ec53f0302f89d36028bf Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Sat, 13 Aug 2016 04:50:29 -0300 Subject: [PATCH] efl.io: introduce basic interfaces and classes. These interfaces allows generic operations on objects that can store or provide data, such as a file or a buffer. With well defined interfaces and events we can create code such as Efl.Io.Copier, that will link a source with a destination and progressively copy data as they appear. --- src/Makefile_Ecore.am | 20 + src/Makefile_Efl.am | 12 + src/examples/ecore/.gitignore | 1 + src/examples/ecore/Makefile.am | 10 +- src/examples/ecore/efl_io_copier_example.c | 476 +++++++++++++ src/lib/ecore/Ecore_Eo.h | 22 + src/lib/ecore/efl_io_closer_fd.c | 49 ++ src/lib/ecore/efl_io_closer_fd.eo | 21 + src/lib/ecore/efl_io_copier.c | 712 ++++++++++++++++++++ src/lib/ecore/efl_io_copier.eo | 112 +++ src/lib/ecore/efl_io_file.c | 195 ++++++ src/lib/ecore/efl_io_file.eo | 49 ++ src/lib/ecore/efl_io_positioner_fd.c | 65 ++ src/lib/ecore/efl_io_positioner_fd.eo | 21 + src/lib/ecore/efl_io_reader_fd.c | 100 +++ src/lib/ecore/efl_io_reader_fd.eo | 24 + src/lib/ecore/efl_io_sizer_fd.c | 53 ++ src/lib/ecore/efl_io_sizer_fd.eo | 21 + src/lib/ecore/efl_io_stderr.c | 60 ++ src/lib/ecore/efl_io_stderr.eo | 14 + src/lib/ecore/efl_io_stdin.c | 61 ++ src/lib/ecore/efl_io_stdin.eo | 14 + src/lib/ecore/efl_io_stdout.c | 59 ++ src/lib/ecore/efl_io_stdout.eo | 14 + src/lib/ecore/efl_io_writer_fd.c | 88 +++ src/lib/ecore/efl_io_writer_fd.eo | 22 + src/lib/efl/Efl.h | 9 + src/lib/efl/interfaces/efl_io_buffer.c | 487 +++++++++++++ src/lib/efl/interfaces/efl_io_buffer.eo | 129 ++++ src/lib/efl/interfaces/efl_io_closer.c | 13 + src/lib/efl/interfaces/efl_io_closer.eo | 49 ++ src/lib/efl/interfaces/efl_io_positioner.c | 10 + src/lib/efl/interfaces/efl_io_positioner.eo | 41 ++ src/lib/efl/interfaces/efl_io_reader.c | 5 + src/lib/efl/interfaces/efl_io_reader.eo | 80 +++ src/lib/efl/interfaces/efl_io_sizer.c | 10 + src/lib/efl/interfaces/efl_io_sizer.eo | 37 + src/lib/efl/interfaces/efl_io_writer.c | 5 + src/lib/efl/interfaces/efl_io_writer.eo | 62 ++ 39 files changed, 3230 insertions(+), 2 deletions(-) create mode 100644 src/examples/ecore/efl_io_copier_example.c create mode 100644 src/lib/ecore/efl_io_closer_fd.c create mode 100644 src/lib/ecore/efl_io_closer_fd.eo create mode 100644 src/lib/ecore/efl_io_copier.c create mode 100644 src/lib/ecore/efl_io_copier.eo create mode 100644 src/lib/ecore/efl_io_file.c create mode 100644 src/lib/ecore/efl_io_file.eo create mode 100644 src/lib/ecore/efl_io_positioner_fd.c create mode 100644 src/lib/ecore/efl_io_positioner_fd.eo create mode 100644 src/lib/ecore/efl_io_reader_fd.c create mode 100644 src/lib/ecore/efl_io_reader_fd.eo create mode 100644 src/lib/ecore/efl_io_sizer_fd.c create mode 100644 src/lib/ecore/efl_io_sizer_fd.eo create mode 100644 src/lib/ecore/efl_io_stderr.c create mode 100644 src/lib/ecore/efl_io_stderr.eo create mode 100644 src/lib/ecore/efl_io_stdin.c create mode 100644 src/lib/ecore/efl_io_stdin.eo create mode 100644 src/lib/ecore/efl_io_stdout.c create mode 100644 src/lib/ecore/efl_io_stdout.eo create mode 100644 src/lib/ecore/efl_io_writer_fd.c create mode 100644 src/lib/ecore/efl_io_writer_fd.eo create mode 100644 src/lib/efl/interfaces/efl_io_buffer.c create mode 100644 src/lib/efl/interfaces/efl_io_buffer.eo create mode 100644 src/lib/efl/interfaces/efl_io_closer.c create mode 100644 src/lib/efl/interfaces/efl_io_closer.eo create mode 100644 src/lib/efl/interfaces/efl_io_positioner.c create mode 100644 src/lib/efl/interfaces/efl_io_positioner.eo create mode 100644 src/lib/efl/interfaces/efl_io_reader.c create mode 100644 src/lib/efl/interfaces/efl_io_reader.eo create mode 100644 src/lib/efl/interfaces/efl_io_sizer.c create mode 100644 src/lib/efl/interfaces/efl_io_sizer.eo create mode 100644 src/lib/efl/interfaces/efl_io_writer.c create mode 100644 src/lib/efl/interfaces/efl_io_writer.eo diff --git a/src/Makefile_Ecore.am b/src/Makefile_Ecore.am index ebdcb1ccc5..303a648b38 100644 --- a/src/Makefile_Ecore.am +++ b/src/Makefile_Ecore.am @@ -10,6 +10,16 @@ ecore_eolian_files = \ lib/ecore/efl_loop.eo \ lib/ecore/efl_loop_user.eo \ lib/ecore/efl_loop_fd.eo \ + lib/ecore/efl_io_closer_fd.eo \ + lib/ecore/efl_io_positioner_fd.eo \ + lib/ecore/efl_io_reader_fd.eo \ + lib/ecore/efl_io_sizer_fd.eo \ + lib/ecore/efl_io_writer_fd.eo \ + lib/ecore/efl_io_stdin.eo \ + lib/ecore/efl_io_stdout.eo \ + lib/ecore/efl_io_stderr.eo \ + lib/ecore/efl_io_file.eo \ + lib/ecore/efl_io_copier.eo \ lib/ecore/ecore_parent.eo \ $(ecore_eolian_files_legacy) @@ -59,6 +69,16 @@ lib/ecore/ecore_job.c \ lib/ecore/ecore_main.c \ lib/ecore/efl_loop_user.c \ lib/ecore/efl_loop_fd.c \ +lib/ecore/efl_io_closer_fd.c \ +lib/ecore/efl_io_positioner_fd.c \ +lib/ecore/efl_io_reader_fd.c \ +lib/ecore/efl_io_sizer_fd.c \ +lib/ecore/efl_io_writer_fd.c \ +lib/ecore/efl_io_stdin.c \ +lib/ecore/efl_io_stdout.c \ +lib/ecore/efl_io_stderr.c \ +lib/ecore/efl_io_file.c \ +lib/ecore/efl_io_copier.c \ lib/ecore/ecore_pipe.c \ lib/ecore/ecore_poller.c \ lib/ecore/ecore_time.c \ diff --git a/src/Makefile_Efl.am b/src/Makefile_Efl.am index 0e23e4d877..2ba7bf0ec4 100644 --- a/src/Makefile_Efl.am +++ b/src/Makefile_Efl.am @@ -46,6 +46,12 @@ efl_eolian_files = \ lib/efl/interfaces/efl_input_interface.eo \ lib/efl/interfaces/efl_input_state.eo \ lib/efl/interfaces/efl_screen.eo \ + lib/efl/interfaces/efl_io_closer.eo \ + lib/efl/interfaces/efl_io_positioner.eo \ + lib/efl/interfaces/efl_io_reader.eo \ + lib/efl/interfaces/efl_io_sizer.eo \ + lib/efl/interfaces/efl_io_writer.eo \ + lib/efl/interfaces/efl_io_buffer.eo \ $(efl_eolian_legacy_files) \ $(NULL) @@ -88,6 +94,12 @@ lib/efl/interfaces/efl_vpath_manager.c \ lib/efl/interfaces/efl_vpath_core.c \ lib/efl/interfaces/efl_vpath_file_core.c \ lib/efl/interfaces/efl_input_device.c \ +lib/efl/interfaces/efl_io_closer.c \ +lib/efl/interfaces/efl_io_positioner.c \ +lib/efl/interfaces/efl_io_reader.c \ +lib/efl/interfaces/efl_io_sizer.c \ +lib/efl/interfaces/efl_io_writer.c \ +lib/efl/interfaces/efl_io_buffer.c \ $(NULL) lib_efl_libefl_la_CPPFLAGS = -I$(top_builddir)/src/lib/efl -I$(top_srcdir)/src/lib/efl @EFL_CFLAGS@ -DEFL_GFX_FILTER_BETA diff --git a/src/examples/ecore/.gitignore b/src/examples/ecore/.gitignore index 8b84fca973..bdf185c2f2 100644 --- a/src/examples/ecore/.gitignore +++ b/src/examples/ecore/.gitignore @@ -46,3 +46,4 @@ /ecore_buffer_example /ecore_buffer_consumer_example /ecore_buffer_provider_example +/efl_io_copier_example diff --git a/src/examples/ecore/Makefile.am b/src/examples/ecore/Makefile.am index c45558d1f7..7f20909c02 100644 --- a/src/examples/ecore/Makefile.am +++ b/src/examples/ecore/Makefile.am @@ -76,7 +76,8 @@ ecore_time_functions_example \ ecore_timer_example \ ecore_getopt_example \ ecore_con_eet_client_example \ -ecore_con_eet_server_example +ecore_con_eet_server_example \ +efl_io_copier_example ECORE_COMMON_LDADD = \ $(top_builddir)/src/lib/ecore/libecore.la \ @@ -276,6 +277,10 @@ ecore_con_eet_server_example_SOURCES = ecore_con_eet_server_example.c \ ecore_con_eet_server_example_LDADD = $(ECORE_CON_COMMON_LDADD) \ $(top_builddir)/src/lib/eet/libeet.la + +efl_io_copier_example_SOURCES = efl_io_copier_example.c +efl_io_copier_example_LDADD = $(ECORE_CON_COMMON_LDADD) + SRCS = \ ecore_animator_example.c \ ecore_buffer_example.c \ @@ -321,7 +326,8 @@ ecore_timer_example.c \ ecore_getopt_example.c \ ecore_con_eet_client_example.c \ ecore_con_eet_server_example.c \ -ecore_con_eet_descriptor_example.c +ecore_con_eet_descriptor_example.c \ +efl_io_copier_example.c DATA_FILES = red.png Makefile.examples diff --git a/src/examples/ecore/efl_io_copier_example.c b/src/examples/ecore/efl_io_copier_example.c new file mode 100644 index 0000000000..5af644ee64 --- /dev/null +++ b/src/examples/ecore/efl_io_copier_example.c @@ -0,0 +1,476 @@ +#define EFL_BETA_API_SUPPORT 1 +#define EFL_EO_API_SUPPORT 1 +#include +#include +#include + +static int retval = EXIT_SUCCESS; + +/* NOTE: input events are only used as debug, you can omit these */ + +static void +_input_can_read_changed(void *data EINA_UNUSED, const Eo_Event *event) +{ + fprintf(stderr, "INFO: input can_read=%d\n", + efl_io_reader_can_read_get(event->object)); +} + +static void +_input_eos(void *data EINA_UNUSED, const Eo_Event *event) +{ + fprintf(stderr, "INFO: input eos=%d\n", + efl_io_reader_eos_get(event->object)); +} + +EFL_CALLBACKS_ARRAY_DEFINE(input_cbs, + { EFL_IO_READER_EVENT_CAN_READ_CHANGED, _input_can_read_changed }, + { EFL_IO_READER_EVENT_EOS, _input_eos }); + + +/* NOTE: output events are only used as debug, you can omit these */ + +static void +_output_can_write_changed(void *data EINA_UNUSED, const Eo_Event *event) +{ + fprintf(stderr, "INFO: output can_write=%d\n", + efl_io_writer_can_write_get(event->object)); +} + +EFL_CALLBACKS_ARRAY_DEFINE(output_cbs, + { EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, _output_can_write_changed }); + +static void +_output_buffer_reallocated(void *data EINA_UNUSED, const Eo_Event *event) +{ + Eina_Slice slice; + + if (!efl_io_buffer_slice_get(event->object, &slice)) + { + fprintf(stderr, "ERROR: could not get buffer slice\n"); + return; + } + + fprintf(stderr, "INFO: output buffer reallocated=" EINA_SLICE_FMT "\n", + EINA_SLICE_PRINT(slice)); +} + +EFL_CALLBACKS_ARRAY_DEFINE(output_buffer_cbs, + { EFL_IO_BUFFER_EVENT_REALLOCATED, _output_buffer_reallocated }); + + +/* copier events are of interest, you should hook to at least "done" + * and "error" + */ + +static void +_copier_done(void *data EINA_UNUSED, const Eo_Event *event) +{ + Eo *destination = efl_io_copier_destination_get(event->object); + + if (!destination) + { + /* if :none: was used, you get Efl_Io_Copier to buffer all + * source data up to efl_io_copier_buffer_limit_get(). + * + * Then if it finished, you can steal the binbuf and do + * something with that. It's a simple way to use + * Efl_Io_Copier. + */ + Eina_Binbuf *binbuf = efl_io_copier_binbuf_steal(event->object); + + fprintf(stderr, + "INFO: :none: resulted in binbuf=%p, string=%p, size=%zd:" + "\n--BEGIN DATA--\n" + EINA_SLICE_STR_FMT + "\n--END DATA--\n", + binbuf, + eina_binbuf_string_get(binbuf), + eina_binbuf_length_get(binbuf), + EINA_SLICE_STR_PRINT(eina_binbuf_slice_get(binbuf)) + ); + + eina_binbuf_free(binbuf); + } + else if (efl_isa(destination, EFL_IO_BUFFER_CLASS)) + { + /* if :memory: was used, then an Efl_Io_Buffer is created + * implementing all the interfaces required by Efl_Io_Copier. + * + * This allows the buffer to be resized if source size is + * known, avoiding realloc()s during execution. + * + * You can use: + * + * - efl_io_buffer_slice_get() for a read-only view of that + * buffer, + * + * - efl_io_buffer_binbuf_steal() to take the buffer memory as + * an Eina_Binbuf, you own the ownership and must call + * eina_binbuf_free() to release it. + */ + Eina_Slice slice; + + if (!efl_io_buffer_slice_get(destination, &slice)) + fprintf(stderr, "ERROR: could not get buffer slice\n"); + else + fprintf(stderr, + "INFO: :memory: resulted in slice=" EINA_SLICE_FMT ":" + "\n--BEGIN DATA--\n" + EINA_SLICE_STR_FMT + "\n--END DATA--\n", + EINA_SLICE_PRINT(slice), EINA_SLICE_STR_PRINT(slice)); + } + + fprintf(stderr, "INFO: done\n"); + ecore_main_loop_quit(); +} + +static void +_copier_error(void *data EINA_UNUSED, const Eo_Event *event) +{ + const Eina_Error *perr = event->info; + fprintf(stderr, "INFO: error: %d\n", *perr); + retval = EXIT_FAILURE; + ecore_main_loop_quit(); +} + +static void +_copier_progress(void *data EINA_UNUSED, const Eo_Event *event) +{ + uint64_t r, w, t; + + efl_io_copier_progress_get(event->object, &r, &w, &t); + /* if total is zero, that means the source object doesn't provide a + * fixed size, consider it a "stream" such as a socket, a pipe, + * stdin... + */ + if (t == 0) + fprintf(stderr, "INFO: read=%" PRIu64 ", written=%" PRIu64 "\n", r, w); + else + { + fprintf(stderr, + "INFO: read=%" PRIu64 ", written=%" PRIu64 ", total=%" PRIu64 + " (%4.1f%%/%4.1f%%)\n", + r, w, t, + (100.0 * r) / (double)t, + (100.0 * w) / (double)t); + } +} + +static void +_copier_data(void *data EINA_UNUSED, const Eo_Event *event) +{ + const Eina_Slice *slice = event->info; + /* a piece of data was processed, it's ready-only and will only be + * good for immediate consumption. + * + * It's only usable inside this function as it may be gone (freed, + * reallocated) once it return and more data is processed. + * + * Shall you want to take over the internal binbuf, use + * efl_io_copier_binbuf_steal() and then stop event propagation + * with efl_event_callback_stop(). + * + * However be aware that other events will be dispatched with empty + * slices, like if you steal the buffer here, _copier_line() will + * get empty slice. + */ + fprintf(stderr, "INFO: data: " EINA_SLICE_FMT "\n", + EINA_SLICE_PRINT(*slice)); +} + +static void +_copier_line(void *data EINA_UNUSED, const Eo_Event *event) +{ + const Eina_Slice *slice = event->info; + + /* a line_delimiter was provided and a line was processed, it's + * ready-only and will only be good for immediate consumption. + * + * It's only usable inside this function as it may be gone (freed, + * reallocated) once it return and more data is processed. + * + * The line may not contain the line delimiter in the following + * cases: + * + * - efl_io_copier_buffer_limit_set() was used and limit was + * reached. + * + * - source reached end-of-stream and pending data was there. + * + * Shall you want to take over the internal binbuf, use + * efl_io_copier_binbuf_steal() and then stop event propagation + * with efl_event_callback_stop(). + */ + + fprintf(stderr, "INFO: line: " EINA_SLICE_STR_FMT "\n", + EINA_SLICE_STR_PRINT(*slice)); +} + +EFL_CALLBACKS_ARRAY_DEFINE(copier_cbs, + { EFL_IO_COPIER_EVENT_DONE, _copier_done }, + { EFL_IO_COPIER_EVENT_ERROR, _copier_error }, + { EFL_IO_COPIER_EVENT_PROGRESS, _copier_progress }, + { EFL_IO_COPIER_EVENT_DATA, _copier_data}, + { EFL_IO_COPIER_EVENT_LINE, _copier_line}); + + +static const Ecore_Getopt options = { + "efl_io_copier_example", /* program name */ + NULL, /* usage line */ + "1", /* version */ + "(C) 2016 Enlightenment Project", /* copyright */ + "BSD 2-Clause", /* license */ + /* long description, may be multiline and contain \n */ + "Example of Efl_Io_Copier usage.\n" + "\n" + "This example copies from an Efl_Io_Reader to an Efl_Io_Writer, listening" + "for events and showing progress.", + EINA_FALSE, + { + ECORE_GETOPT_STORE_STR('d', "line-delimiter", + "If set will define a line delimiter for copy operation, instead of a fixed chunk size. This will trigger line events."), + ECORE_GETOPT_STORE_ULONG('l', "buffer-limit", + "If set will limit buffer size to this limit of bytes. If used alongside with --line-delimiter and that delimiter was not found but bffer limit was reached, the line event will be triggered without the delimiter at the end."), + ECORE_GETOPT_STORE_ULONG('c', "read-chunk-size", + "If set will change the base chunk size used while reading."), + ECORE_GETOPT_VERSION('V', "version"), + ECORE_GETOPT_COPYRIGHT('C', "copyright"), + ECORE_GETOPT_LICENSE('L', "license"), + ECORE_GETOPT_HELP('h', "help"), + + ECORE_GETOPT_STORE_METAVAR_STR(0, NULL, + "The input file name or ':stdin:' to read from stdin.", + "input-file"), + ECORE_GETOPT_STORE_METAVAR_STR(0, NULL, + "The output file name or:\n" + ":stdout: to write to stdout.\n" + ":stderr: to write to stderr.\n" + ":memory: to write to a memory buffer.\n" + ":none: to not use a destination object.\n" + "", + "output-file"), + ECORE_GETOPT_SENTINEL + } +}; + +int +main(int argc, char **argv) +{ + char *input_fname = NULL; + char *output_fname = NULL; + char *line_delimiter = NULL; + unsigned long buffer_limit = 0; + unsigned long read_chunk_size = 0; + Eina_Bool quit_option = EINA_FALSE; + Ecore_Getopt_Value values[] = { + ECORE_GETOPT_VALUE_STR(line_delimiter), + ECORE_GETOPT_VALUE_ULONG(buffer_limit), + ECORE_GETOPT_VALUE_ULONG(read_chunk_size), + + /* standard block to provide version, copyright, license and help */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */ + + /* positional argument */ + ECORE_GETOPT_VALUE_STR(input_fname), + ECORE_GETOPT_VALUE_STR(output_fname), + + ECORE_GETOPT_VALUE_NONE /* sentinel */ + }; + int args; + Eo *input, *output, *copier; + Eina_Slice line_delm_slice = EINA_SLICE_STR_LITERAL(""); + + ecore_init(); + + args = ecore_getopt_parse(&options, values, argc, argv); + if (args < 0) + { + fputs("ERROR: Could not parse command line options.\n", stderr); + retval = EXIT_FAILURE; + goto end; + } + + if (quit_option) goto end; + + args = ecore_getopt_parse_positional(&options, values, argc, argv, args); + if (args < 0) + { + fputs("ERROR: Could not parse positional arguments.\n", stderr); + retval = EXIT_FAILURE; + goto end; + } + + /* Efl_Io_Copier works with any object that implements + * Efl_Io_Reader and Efl_Io_Writer interfaces. Here we create + * couple of different objects to showcase that. + * + * Note that input_cbs(), output_cbs() are OPTIONAL, here are only + * used to print out how each object behaves. + */ + + if (strcmp(input_fname, ":stdin:") == 0) + { + input = efl_add(EFL_IO_STDIN_CLASS, NULL, + efl_event_callback_array_add(efl_self, input_cbs(), NULL)); + if (!input) + { + fprintf(stderr, "ERROR: could not open stdin.\n"); + retval = EXIT_FAILURE; + goto end; + } + } + else + { + /* regular file, open with flags: read-only and close-on-exec */ + input = efl_add(EFL_IO_FILE_CLASS, NULL, + efl_file_set(efl_self, input_fname, NULL), /* mandatory */ + efl_io_file_flags_set(efl_self, O_RDONLY | O_CLOEXEC), /* recommended */ + efl_event_callback_array_add(efl_self, input_cbs(), NULL) /* optional */ + ); + if (!input) + { + fprintf(stderr, "ERROR: could not open '%s' for read.\n", + input_fname); + retval = EXIT_FAILURE; + goto end; + } + } + + if (strcmp(output_fname, ":stdout:") == 0) + { + output = efl_add(EFL_IO_STDOUT_CLASS, NULL, + efl_event_callback_array_add(efl_self, output_cbs(), NULL) /* optional */ + ); + if (!output) + { + fprintf(stderr, "ERROR: could not open stdout.\n"); + retval = EXIT_FAILURE; + goto end_input; + } + } + else if (strcmp(output_fname, ":stderr:") == 0) + { + output = efl_add(EFL_IO_STDERR_CLASS, NULL, + efl_event_callback_array_add(efl_self, output_cbs(), NULL) /* optional */ + ); + if (!output) + { + fprintf(stderr, "ERROR: could not open stderr.\n"); + retval = EXIT_FAILURE; + goto end_input; + } + } + else if (strcmp(output_fname, ":memory:") == 0) + { + /* + * This uses a memory destination, then the process will + * result in that object holding all the memory. + * + * One can use things like efl_io_buffer_limit_set() to limit + * it's size. + * + * If the source object provides a size (ie: a file), then + * this buffer will be resized only once at the start, saving + * reallocs. Otherwise it will grow as needed. Contrast this + * with ":none:" method below, that always resize. + * + * When finished get the efl_io_buffer_slice_get(), see + * _copier_done(). + */ + output = efl_add(EFL_IO_BUFFER_CLASS, NULL, + efl_event_callback_array_add(efl_self, output_cbs(), NULL), /* optional */ + efl_event_callback_array_add(efl_self, output_buffer_cbs(), NULL) /* optional */ + ); + if (!output) + { + fprintf(stderr, "ERROR: could not open memory buffer.\n"); + retval = EXIT_FAILURE; + goto end_input; + } + } + else if (strcmp(output_fname, ":none:") == 0) + { + /* + * No output means that the Efl_Io_Copier will cache + * internally up to efl_io_copier_buffer_limit_set(). + * + * When finished you can steal copier's buffer in order to use + * that yourself. See _copier_done(). + */ + output = NULL; + } + else + { + /* regular file, open with flags: write-only, close-on-exec, + * create if did not exist and truncate if exist. + */ + output = efl_add(EFL_IO_FILE_CLASS, NULL, + efl_file_set(efl_self, output_fname, NULL), /* mandatory */ + efl_io_file_flags_set(efl_self, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC), /* mandatory for write */ + efl_io_file_mode_set(efl_self, 0644), /* mandatory for write */ + efl_event_callback_array_add(efl_self, output_cbs(), NULL) /* optional */ + ); + if (!output) + { + fprintf(stderr, "ERROR: could not open '%s' for write.\n", + output_fname); + retval = EXIT_FAILURE; + goto end_input; + } + } + + /* A delimiter is optional, if empty or unset, copier will execute + * writes based on read_chunk_size and only event "data" is emitted. + * + * If a line delimiter is set, copier will hold writes until the + * delimiter is found, source reached End-of-Stream (eos) or the + * copier buffer limit is reached. The "line" event is emitted. + */ + if (line_delimiter) + line_delm_slice = (Eina_Slice)EINA_SLICE_STR(line_delimiter); + + copier = efl_add(EFL_IO_COPIER_CLASS, ecore_main_loop_get(), + efl_io_copier_source_set(efl_self, input), /* mandatory */ + efl_io_copier_destination_set(efl_self, output), /* optional, see :none: */ + efl_io_copier_line_delimiter_set(efl_self, &line_delm_slice), /* optional */ + efl_io_copier_buffer_limit_set(efl_self, buffer_limit), /* optional, defaults to unlimited */ + efl_io_copier_read_chunk_size_set(efl_self, read_chunk_size), /* optional, defaults to 4096 */ + efl_event_callback_array_add(efl_self, copier_cbs(), NULL) /* recommended, at least EFL_IO_COPIER_EVENT_DONE. */ + ); + if (!copier) + { + fprintf(stderr, "ERROR: could not create copier.\n"); + retval = EXIT_FAILURE; + goto end_output; + } + + fprintf(stderr, "INFO: copy source=%p (%s) to destination=%p (%s)\n", + input, + efl_class_name_get(efl_class_get(input)), + output, + output ? efl_class_name_get(efl_class_get(output)) : ":none:"); + + ecore_main_loop_begin(); + efl_io_closer_close(copier); + efl_del(copier); + copier = NULL; + + end_output: + if (output) + { + efl_unref(output); + output = NULL; + } + end_input: + efl_unref(input); + input = NULL; + + end: + ecore_shutdown(); + + return retval; +} diff --git a/src/lib/ecore/Ecore_Eo.h b/src/lib/ecore/Ecore_Eo.h index a7e8d93403..feb66dc722 100644 --- a/src/lib/ecore/Ecore_Eo.h +++ b/src/lib/ecore/Ecore_Eo.h @@ -61,6 +61,28 @@ EAPI Eo *ecore_main_loop_get(void); * @} */ +/** + * @ingroup Ecore_Fd_Io_Group + * + * @{ + */ + +#include "efl_io_closer_fd.eo.h" +#include "efl_io_positioner_fd.eo.h" +#include "efl_io_reader_fd.eo.h" +#include "efl_io_sizer_fd.eo.h" +#include "efl_io_writer_fd.eo.h" +#include "efl_io_stdin.eo.h" +#include "efl_io_stdout.eo.h" +#include "efl_io_stderr.eo.h" +#include "efl_io_file.eo.h" +#include "efl_io_copier.eo.h" + +/** + * @} + */ + + #ifdef __cplusplus } #endif diff --git a/src/lib/ecore/efl_io_closer_fd.c b/src/lib/ecore/efl_io_closer_fd.c new file mode 100644 index 0000000000..a0ad5eca2d --- /dev/null +++ b/src/lib/ecore/efl_io_closer_fd.c @@ -0,0 +1,49 @@ +#define EFL_IO_CLOSER_FD_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_CLOSER_FD_CLASS + +typedef struct _Efl_Io_Closer_Fd_Data +{ + int fd; +} Efl_Io_Closer_Fd_Data; + +EOLIAN static void +_efl_io_closer_fd_closer_fd_set(Eo *o EINA_UNUSED, Efl_Io_Closer_Fd_Data *pd, int fd) +{ + pd->fd = fd; +} + +EOLIAN static int +_efl_io_closer_fd_closer_fd_get(Eo *o EINA_UNUSED, Efl_Io_Closer_Fd_Data *pd) +{ + return pd->fd; +} + +EOLIAN static Eina_Error +_efl_io_closer_fd_efl_io_closer_close(Eo *o, Efl_Io_Closer_Fd_Data *pd EINA_UNUSED) +{ + int fd = efl_io_closer_fd_closer_fd_get(o); + Eina_Error err = 0; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(fd < 0, EBADF); + + efl_io_closer_fd_closer_fd_set(o, -1); + if (close(fd) < 0) err = errno; + efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL); + return err; +} + +EOLIAN static Eina_Bool +_efl_io_closer_fd_efl_io_closer_closed_get(Eo *o, Efl_Io_Closer_Fd_Data *pd EINA_UNUSED) +{ + return efl_io_closer_fd_closer_fd_get(o) < 0; +} + +#include "efl_io_closer_fd.eo.c" diff --git a/src/lib/ecore/efl_io_closer_fd.eo b/src/lib/ecore/efl_io_closer_fd.eo new file mode 100644 index 0000000000..35f881b036 --- /dev/null +++ b/src/lib/ecore/efl_io_closer_fd.eo @@ -0,0 +1,21 @@ +mixin Efl.Io.Closer.Fd (Efl.Io.Closer) { + [[Close fd using close(2). + + @since 1.19 + ]] + + methods { + @property closer_fd { + get {} + set @protected {} + values { + fd: int; + } + } + } + + implements { + Efl.Io.Closer.close; + Efl.Io.Closer.closed.get; + } +} diff --git a/src/lib/ecore/efl_io_copier.c b/src/lib/ecore/efl_io_copier.c new file mode 100644 index 0000000000..029c49411c --- /dev/null +++ b/src/lib/ecore/efl_io_copier.c @@ -0,0 +1,712 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_COPIER_CLASS +#define DEF_READ_CHUNK_SIZE 4096 + +typedef struct _Efl_Io_Copier_Data +{ + Efl_Io_Reader *source; + Efl_Io_Writer *destination; + Eina_Promise *job; + Eina_Binbuf *buf; + uint8_t *read_chunk; /* TODO: method to grow Eina_Binbuf so we can expand it and read directly to that */ + Eina_Slice line_delimiter; + size_t buffer_limit; + size_t read_chunk_size; + struct { + uint64_t read, written, total; + } progress; + Eina_Bool closed; + Eina_Bool done; +} Efl_Io_Copier_Data; + +static void _efl_io_copier_write(Eo *o, Efl_Io_Copier_Data *pd); +static void _efl_io_copier_read(Eo *o, Efl_Io_Copier_Data *pd); + +#define _COPIER_DBG(o, pd) \ + do \ + { \ + if (eina_log_domain_level_check(_ecore_log_dom, EINA_LOG_LEVEL_DBG)) \ + { \ + DBG("copier={%p %s, refs=%d, closed=%d, done=%d, buf=%zd}", \ + o, \ + efl_class_name_get(efl_class_get(o)), \ + efl_ref_get(o), \ + efl_io_closer_closed_get(o), \ + pd->done, \ + pd->buf ? eina_binbuf_length_get(pd->buf): 0); \ + if (!pd->source) \ + DBG("source=NULL"); \ + else \ + DBG("source={%p %s, refs=%d, can_read=%d, eos=%d, closed=%d}", \ + pd->source, \ + efl_class_name_get(efl_class_get(pd->source)), \ + efl_ref_get(pd->source), \ + efl_io_reader_can_read_get(pd->source), \ + efl_io_reader_eos_get(pd->source), \ + efl_isa(pd->source, EFL_IO_CLOSER_MIXIN) ? \ + efl_io_closer_closed_get(pd->source) : 0); \ + if (!pd->destination) \ + DBG("destination=NULL"); \ + else \ + DBG("destination={%p %s, refs=%d, can_write=%d, closed=%d}", \ + pd->destination, \ + efl_class_name_get(efl_class_get(pd->destination)), \ + efl_ref_get(pd->destination), \ + efl_io_writer_can_write_get(pd->destination), \ + efl_isa(pd->destination, EFL_IO_CLOSER_MIXIN) ? \ + efl_io_closer_closed_get(pd->destination) : 0); \ + } \ + } \ + while (0) + +static void +_efl_io_copier_job(void *data, void *value EINA_UNUSED) +{ + Eo *o = data; + Efl_Io_Copier_Data *pd = efl_data_scope_get(o, MY_CLASS); + + pd->job = NULL; + + _COPIER_DBG(o, pd); + + if (pd->source && efl_io_reader_can_read_get(pd->source)) + _efl_io_copier_read(o, pd); + + if (pd->destination && efl_io_writer_can_write_get(pd->destination)) + _efl_io_copier_write(o, pd); + + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_PROGRESS, NULL); + + if (!pd->source || efl_io_reader_eos_get(pd->source)) + { + if ((!pd->done) && + ((!pd->destination) || (eina_binbuf_length_get(pd->buf) == 0))) + { + pd->done = EINA_TRUE; + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_DONE, NULL); + } + } +} + +static void +_efl_io_copier_job_schedule(Eo *o, Efl_Io_Copier_Data *pd) +{ + if (pd->job) return; + + pd->job = efl_loop_job(efl_loop_user_loop_get(o), o); + eina_promise_then(pd->job, _efl_io_copier_job, NULL, o); +} + +/* NOTE: the returned slice may be smaller than requested since the + * internal binbuf may be modified from inside event calls. + * + * parameter slice_of_binbuf must have mem pointing to pd->binbuf + */ +static Eina_Slice +_efl_io_copier_dispatch_data_events(Eo *o, Efl_Io_Copier_Data *pd, Eina_Slice slice_of_binbuf) +{ + Eina_Slice tmp; + size_t offset; + + tmp = eina_binbuf_slice_get(pd->buf); + if ((slice_of_binbuf.bytes < tmp.bytes) || + (eina_slice_end_get(slice_of_binbuf) > eina_slice_end_get(tmp))) + { + CRI("slice_of_binbuf=" EINA_SLICE_FMT " must be inside binbuf=" EINA_SLICE_FMT, + EINA_SLICE_PRINT(slice_of_binbuf), EINA_SLICE_PRINT(tmp)); + return (Eina_Slice){.mem = NULL, .len = 0}; + } + + offset = slice_of_binbuf.bytes - tmp.bytes; + + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_DATA, &slice_of_binbuf); + /* user may have modified pd->buf, like calling + * efl_io_copier_buffer_limit_set() + */ + tmp = eina_binbuf_slice_get(pd->buf); + if (offset <= tmp.len) + { + tmp.len -= offset; + tmp.bytes += offset; + } + if (tmp.len > slice_of_binbuf.len) + tmp.len = slice_of_binbuf.len; + slice_of_binbuf = tmp; + + if (pd->line_delimiter.len > 0) + { + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_LINE, &slice_of_binbuf); + /* user may have modified pd->buf, like calling + * efl_io_copier_buffer_limit_set() + */ + tmp = eina_binbuf_slice_get(pd->buf); + if (offset <= tmp.len) + { + tmp.len -= offset; + tmp.bytes += offset; + } + if (tmp.len > slice_of_binbuf.len) + tmp.len = slice_of_binbuf.len; + slice_of_binbuf = tmp; + } + + return slice_of_binbuf; +} + +static void +_efl_io_copier_read(Eo *o, Efl_Io_Copier_Data *pd) +{ + Eina_Rw_Slice rw_slice; + Eina_Slice ro_slice; + Eina_Error err; + size_t used; + + EINA_SAFETY_ON_TRUE_RETURN(pd->closed); + + rw_slice.mem = pd->read_chunk; + rw_slice.len = pd->read_chunk_size; + + used = eina_binbuf_length_get(pd->buf); + if (pd->buffer_limit > 0) + { + if (pd->buffer_limit <= used) + { + // TODO: disconnect 'read' so stops calling? + return; + } + else if (pd->buffer_limit > used) + { + size_t available = pd->buffer_limit - used; + if (rw_slice.len > available) + rw_slice.len = available; + } + } + + err = efl_io_reader_read(pd->source, &rw_slice); + if (err) + { + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_ERROR, &err); + return; + } + + ro_slice = eina_rw_slice_slice_get(rw_slice); + if (!eina_binbuf_append_slice(pd->buf, ro_slice)) + { + err = ENOMEM; + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_ERROR, &err); + return; + } + + pd->progress.read += rw_slice.len; + pd->done = EINA_FALSE; + + if (!pd->destination) + { + /* Note: if there is a destination, dispatch data and line + * from write since it will remove from binbuf and make it + * simple to not repeat data that was already sent. + * + * however, if there is no destination, then emit the event + * here. + * + * Remember to get the actual binbuf memory, rw_slice/ro_slice + * contains the pointer to pd->read_chunk and + * _efl_io_copier_dispatch_data_events() needs a slice to + * internal binbuf. + */ + Eina_Slice binbuf_slice = eina_binbuf_slice_get(pd->buf); + Eina_Slice ev_slice = { + .mem = binbuf_slice.bytes + used, + .len = binbuf_slice.len - used, + }; + _efl_io_copier_dispatch_data_events(o, pd, ev_slice); + } + + _efl_io_copier_job_schedule(o, pd); +} + +static void +_efl_io_copier_write(Eo *o, Efl_Io_Copier_Data *pd) +{ + Eina_Slice ro_slice = eina_binbuf_slice_get(pd->buf); + Eina_Error err; + + EINA_SAFETY_ON_TRUE_RETURN(pd->closed); + + if (ro_slice.len == 0) + { + // TODO: disconnect 'write' so stops calling? + return; + } + + if ((pd->line_delimiter.len > 0) && + (pd->source && !efl_io_reader_eos_get(pd->source))) + { + const uint8_t *p = eina_slice_find(ro_slice, pd->line_delimiter); + if (p) + ro_slice.len = p - ro_slice.bytes + pd->line_delimiter.len; + else if ((pd->buffer_limit == 0) || (ro_slice.len < pd->buffer_limit)) + { + // TODO: disconnect 'write' so stops calling? + return; + } + } + + err = efl_io_writer_write(pd->destination, &ro_slice, NULL); + if (err) + { + if (err != EAGAIN) + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_ERROR, &err); + return; + } + pd->progress.written += ro_slice.len; + pd->done = EINA_FALSE; + + /* Note: dispatch data and line from write since it will remove + * from binbuf and make it simple to not repeat data that was + * already sent. + */ + ro_slice = _efl_io_copier_dispatch_data_events(o, pd, ro_slice); + + if (!eina_binbuf_remove(pd->buf, 0, ro_slice.len)) + { + err = ENOMEM; + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_ERROR, &err); + return; + } + + _efl_io_copier_job_schedule(o, pd); +} + +static void +_efl_io_copier_source_can_read_changed(void *data, const Eo_Event *event EINA_UNUSED) +{ + Eo *o = data; + Efl_Io_Copier_Data *pd = efl_data_scope_get(o, MY_CLASS); + if (pd->closed) return; + + _COPIER_DBG(o, pd); + + if (efl_io_reader_can_read_get(pd->source)) + _efl_io_copier_job_schedule(o, pd); +} + +static void +_efl_io_copier_source_eos(void *data, const Eo_Event *event EINA_UNUSED) +{ + Eo *o = data; + Efl_Io_Copier_Data *pd = efl_data_scope_get(o, MY_CLASS); + if (pd->closed) return; + + _COPIER_DBG(o, pd); + + _efl_io_copier_job_schedule(o, pd); +} + +static void +_efl_io_copier_source_size_apply(Eo *o, Efl_Io_Copier_Data *pd) +{ + if (pd->closed) return; + pd->progress.total = efl_io_sizer_size_get(pd->source); + + _COPIER_DBG(o, pd); + + if (pd->destination && efl_isa(pd->destination, EFL_IO_SIZER_MIXIN)) + efl_io_sizer_resize(pd->destination, pd->progress.total); + + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_PROGRESS, NULL); +} + +static void +_efl_io_copier_source_resized(void *data, const Eo_Event *event EINA_UNUSED) +{ + Eo *o = data; + Efl_Io_Copier_Data *pd = efl_data_scope_get(o, MY_CLASS); + _efl_io_copier_source_size_apply(o, pd); +} + +static void +_efl_io_copier_source_closed(void *data, const Eo_Event *event EINA_UNUSED) +{ + Eo *o = data; + Efl_Io_Copier_Data *pd = efl_data_scope_get(o, MY_CLASS); + if (pd->closed) return; + + _COPIER_DBG(o, pd); + + _efl_io_copier_job_schedule(o, pd); +} + +EFL_CALLBACKS_ARRAY_DEFINE(source_cbs, + { EFL_IO_READER_EVENT_CAN_READ_CHANGED, _efl_io_copier_source_can_read_changed }, + { EFL_IO_READER_EVENT_EOS, _efl_io_copier_source_eos }); + +EOLIAN static Efl_Io_Reader * +_efl_io_copier_source_get(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd) +{ + return pd->source; +} + +EOLIAN static void +_efl_io_copier_source_set(Eo *o, Efl_Io_Copier_Data *pd, Efl_Io_Reader *source) +{ + if (pd->source == source) return; + + if (pd->source) + { + if (efl_isa(pd->source, EFL_IO_SIZER_MIXIN)) + { + efl_event_callback_del(pd->source, EFL_IO_SIZER_EVENT_SIZE_CHANGED, + _efl_io_copier_source_resized, o); + pd->progress.total = 0; + } + if (efl_isa(pd->source, EFL_IO_CLOSER_MIXIN)) + { + efl_event_callback_del(pd->source, EFL_IO_CLOSER_EVENT_CLOSED, + _efl_io_copier_source_closed, o); + } + efl_event_callback_array_del(pd->source, source_cbs(), o); + efl_unref(pd->source); + pd->source = NULL; + } + + if (source) + { + EINA_SAFETY_ON_TRUE_RETURN(pd->closed); + pd->source = efl_ref(source); + efl_event_callback_array_add(pd->source, source_cbs(), o); + + if (efl_isa(pd->source, EFL_IO_SIZER_MIXIN)) + { + efl_event_callback_add(pd->source, EFL_IO_SIZER_EVENT_SIZE_CHANGED, + _efl_io_copier_source_resized, o); + _efl_io_copier_source_size_apply(o, pd); + } + + if (efl_isa(pd->source, EFL_IO_CLOSER_MIXIN)) + { + efl_event_callback_add(pd->source, EFL_IO_CLOSER_EVENT_CLOSED, + _efl_io_copier_source_closed, o); + } + } +} + +static void +_efl_io_copier_destination_can_write_changed(void *data, const Eo_Event *event EINA_UNUSED) +{ + Eo *o = data; + Efl_Io_Copier_Data *pd = efl_data_scope_get(o, MY_CLASS); + if (pd->closed) return; + + _COPIER_DBG(o, pd); + + if (efl_io_writer_can_write_get(pd->destination)) + _efl_io_copier_job_schedule(o, pd); +} + +static void +_efl_io_copier_destination_closed(void *data, const Eo_Event *event EINA_UNUSED) +{ + Eo *o = data; + Efl_Io_Copier_Data *pd = efl_data_scope_get(o, MY_CLASS); + if (pd->closed) return; + + _COPIER_DBG(o, pd); + + if (eina_binbuf_length_get(pd->buf) == 0) + { + if (!pd->done) + { + pd->done = EINA_TRUE; + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_DONE, NULL); + } + } + else + { + Eina_Error err = EBADF; + efl_event_callback_call(o, EFL_IO_COPIER_EVENT_ERROR, &err); + } +} + +EFL_CALLBACKS_ARRAY_DEFINE(destination_cbs, + { EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, _efl_io_copier_destination_can_write_changed }); + +EOLIAN static Efl_Io_Writer * +_efl_io_copier_destination_get(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd) +{ + return pd->destination; +} + +EOLIAN static void +_efl_io_copier_destination_set(Eo *o, Efl_Io_Copier_Data *pd, Efl_Io_Writer *destination) +{ + if (pd->destination == destination) return; + + if (pd->destination) + { + efl_event_callback_array_del(pd->destination, destination_cbs(), o); + if (efl_isa(pd->destination, EFL_IO_CLOSER_MIXIN)) + { + efl_event_callback_del(pd->destination, EFL_IO_CLOSER_EVENT_CLOSED, + _efl_io_copier_destination_closed, o); + } + efl_unref(pd->destination); + pd->destination = NULL; + } + + if (destination) + { + EINA_SAFETY_ON_TRUE_RETURN(pd->closed); + pd->destination = efl_ref(destination); + efl_event_callback_array_add(pd->destination, destination_cbs(), o); + + if (efl_isa(pd->destination, EFL_IO_CLOSER_MIXIN)) + { + efl_event_callback_add(pd->destination, EFL_IO_CLOSER_EVENT_CLOSED, + _efl_io_copier_destination_closed, o); + } + if (efl_isa(pd->destination, EFL_IO_SIZER_MIXIN) && + pd->source && efl_isa(pd->source, EFL_IO_SIZER_MIXIN)) + { + efl_io_sizer_resize(pd->destination, pd->progress.total); + } + } +} + +EOLIAN static void +_efl_io_copier_buffer_limit_set(Eo *o, Efl_Io_Copier_Data *pd, size_t size) +{ + size_t used; + + EINA_SAFETY_ON_TRUE_RETURN(pd->closed); + + if (pd->buffer_limit == size) return; + pd->buffer_limit = size; + if (size == 0) return; + + used = eina_binbuf_length_get(pd->buf); + if (used > size) eina_binbuf_remove(pd->buf, size, used); + if (pd->read_chunk_size > size) efl_io_copier_read_chunk_size_set(o, size); +} + +EOLIAN static size_t +_efl_io_copier_buffer_limit_get(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd) +{ + return pd->buffer_limit; +} + +EOLIAN static void +_efl_io_copier_line_delimiter_set(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd, const Eina_Slice *slice) +{ + EINA_SAFETY_ON_NULL_RETURN(slice); + if (pd->line_delimiter.mem == slice->mem) + { + pd->line_delimiter.len = slice->len; + return; + } + + free((void *)pd->line_delimiter.mem); + if (slice->len == 0) + { + pd->line_delimiter.mem = NULL; + pd->line_delimiter.len = 0; + } + else + { + Eina_Rw_Slice rw_slice = eina_slice_dup(*slice); + pd->line_delimiter = eina_rw_slice_slice_get(rw_slice); + } +} + +EOLIAN static const Eina_Slice * +_efl_io_copier_line_delimiter_get(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd) +{ + return &pd->line_delimiter; +} + + +EOLIAN static void +_efl_io_copier_read_chunk_size_set(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd, size_t size) +{ + void *tmp; + + EINA_SAFETY_ON_TRUE_RETURN(pd->closed); + + if (size == 0) size = DEF_READ_CHUNK_SIZE; + if ((pd->read_chunk_size == size) && pd->read_chunk) return; + + tmp = realloc(pd->read_chunk, size); + EINA_SAFETY_ON_NULL_RETURN(tmp); + + pd->read_chunk = tmp; + pd->read_chunk_size = size; +} + +EOLIAN static size_t +_efl_io_copier_read_chunk_size_get(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd) +{ + return pd->read_chunk_size > 0 ? pd->read_chunk_size : DEF_READ_CHUNK_SIZE; +} + +EOLIAN static Eina_Error +_efl_io_copier_efl_io_closer_close(Eo *o, Efl_Io_Copier_Data *pd) +{ + Eina_Error err = 0, r; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->closed, EINVAL); + + _COPIER_DBG(o, pd); + + if (pd->job) + { + eina_promise_cancel(pd->job); + pd->job = NULL; + } + + if (pd->source) + { + if (efl_isa(pd->source, EFL_IO_SIZER_MIXIN)) + { + efl_event_callback_del(pd->source, EFL_IO_SIZER_EVENT_SIZE_CHANGED, + _efl_io_copier_source_resized, o); + pd->progress.total = 0; + } + efl_event_callback_array_del(pd->source, source_cbs(), o); + if (efl_isa(pd->source, EFL_IO_CLOSER_MIXIN) && + !efl_io_closer_closed_get(pd->source)) + { + efl_event_callback_del(pd->source, EFL_IO_CLOSER_EVENT_CLOSED, + _efl_io_copier_source_closed, o); + err = efl_io_closer_close(pd->source); + } + } + + if (pd->destination) + { + efl_event_callback_array_del(pd->destination, destination_cbs(), o); + if (efl_isa(pd->destination, EFL_IO_CLOSER_MIXIN) && + !efl_io_closer_closed_get(pd->destination)) + { + efl_event_callback_del(pd->destination, EFL_IO_CLOSER_EVENT_CLOSED, + _efl_io_copier_destination_closed, o); + r = efl_io_closer_close(pd->destination); + if (!err) err = r; + } + } + + pd->closed = EINA_TRUE; + efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL); + + if (pd->buf) + { + eina_binbuf_free(pd->buf); + pd->buf = NULL; + } + + if (pd->read_chunk) + { + free(pd->read_chunk); + pd->read_chunk = NULL; + pd->read_chunk_size = 0; + } + + return err; +} + +EOLIAN static Eina_Bool +_efl_io_copier_efl_io_closer_closed_get(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd) +{ + return pd->closed; +} + +EOLIAN static void +_efl_io_copier_progress_get(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd, uint64_t *read, uint64_t *written, uint64_t *total) +{ + if (read) *read = pd->progress.read; + if (written) *written = pd->progress.written; + if (total) *total = pd->progress.total; +} + +EOLIAN static Eina_Binbuf * +_efl_io_copier_binbuf_steal(Eo *o EINA_UNUSED, Efl_Io_Copier_Data *pd) +{ + Eina_Binbuf *ret = pd->buf; + pd->buf = eina_binbuf_new(); + return ret; +} + +EOLIAN static Eo * +_efl_io_copier_efl_object_constructor(Eo *o, Efl_Io_Copier_Data *pd) +{ + pd->buf = eina_binbuf_new(); + + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->buf, NULL); + + return efl_constructor(efl_super(o, MY_CLASS)); +} + +EOLIAN static Eo * +_efl_io_copier_efl_object_finalize(Eo *o, Efl_Io_Copier_Data *pd) +{ + if (pd->read_chunk_size == 0) + efl_io_copier_read_chunk_size_set(o, DEF_READ_CHUNK_SIZE); + + if (!efl_loop_user_loop_get(o)) + { + ERR("Set a loop provider as parent of this copier!"); + return NULL; + } + + if ((pd->source && efl_io_reader_can_read_get(pd->source)) || + (pd->destination && efl_io_writer_can_write_get(pd->destination))) + _efl_io_copier_job_schedule(o, pd); + + _COPIER_DBG(o, pd); + + return efl_finalize(efl_super(o, MY_CLASS)); +} + +EOLIAN static void +_efl_io_copier_efl_object_destructor(Eo *o, Efl_Io_Copier_Data *pd) +{ + _COPIER_DBG(o, pd); + + efl_io_copier_source_set(o, NULL); + efl_io_copier_destination_set(o, NULL); + + if (pd->job) + { + eina_promise_cancel(pd->job); + pd->job = NULL; + } + + efl_destructor(efl_super(o, MY_CLASS)); + + if (pd->buf) + { + eina_binbuf_free(pd->buf); + pd->buf = NULL; + } + + if (pd->read_chunk) + { + free(pd->read_chunk); + pd->read_chunk = NULL; + pd->read_chunk_size = 0; + } + + if (pd->line_delimiter.mem) + { + free((void *)pd->line_delimiter.mem); + pd->line_delimiter.mem = NULL; + pd->line_delimiter.len = 0; + } +} + +#include "efl_io_copier.eo.c" diff --git a/src/lib/ecore/efl_io_copier.eo b/src/lib/ecore/efl_io_copier.eo new file mode 100644 index 0000000000..4a14a0737b --- /dev/null +++ b/src/lib/ecore/efl_io_copier.eo @@ -0,0 +1,112 @@ +import eina_types; + +class Efl.Io.Copier (Efl.Loop_User, Efl.Io.Closer) { + [[Copy from an @Efl.Io.Reader source to @Efl.Io.Writer destination. + + During usage it will keep reference to @.source and + @.destination objects, automatically relasing them on + destructor. + + By default the read-write process is done based on fixed-size + chunks (@.read_chunk_size), however if @.line_delimiter is set, + the behavior changes to wait for such delimiter or a maximum + buffer limit is reached (@.buffer_limit). + + If @Efl.Io.Closer.close is called, then it will be called on + @.source and @.destination if they implement those interfaces. + + @since 1.19 + ]] + + methods { + @property source { + get { + } + set { + [[Constructor-only property to set where to read data from]] + } + values { + source: Efl.Io.Reader; + } + } + + @property destination { + get { + } + set { + [[Constructor-only property to set where to write data to]] + } + values { + destination: Efl.Io.Writer; + } + } + + @property line_delimiter { + [[If there is a line delimiter, the reads will buffer/queue up to the line delimiter before calling @Efl.Io.Writer.write on the @.destination and the event line is emitted with current line. The line may include the delimiter, unless it's end-of-stream on @.source or @.buffer_limit was reached.]] + get { } + set { + [[Change line delimiter to use. If NULL or empty, no delimiter is to be used]] + } + values { + slice: const(Eina.Slice)*; [[The contents may contain \0 and will be copied]] + } + } + + @property buffer_limit { + get { + } + set { + [[Constructor-only property to set buffer limit. 0 is unlimited]] + } + values { + size: size; [[Defines a maximum buffer limit, or 0 to allow unlimited amount of bytes]] + } + } + + @property read_chunk_size { + get { + } + set { + [[Set chunk size for each basic @Efl.Io.Reader.read operation.]] + } + values { + size: size; [[This is the chunk size to use for read operations]] + } + } + + @property progress { + get { + } + values { + read: uint64 @optional; [[amount of bytes read from source]] + written: uint64 @optional; [[amount of bytes written to destination]] + total: uint64 @optional; [[If @.source is an Efl.Io.Sizer, its total size. Otherwise 0 to report unknown size]] + } + } + + binbuf_steal { + [[Steals the internal binbuf and return it to caller. + + The buffer is then owned by caller, which should call + eina_binbuf_free() when it's done. + ]] + return: free(own(Eina.Binbuf*), eina_binbuf_free) @warn_unused; + } + } + + events { + done; [[All available data was copied from source to destination]] + error: Eina.Error; [[An error happened and the copy stopped]] + progress; [[Total size changed or Data was read/written]] + data: const(Eina.Slice)*; [[When data is read to internal buffer, it's emitted in this event. The memory is only valid during event callback dispatched and should not be modified.]] + line: const(Eina.Slice)*; [[If @.line_delimiter is set, will be emitted with current line. The memory is only valid during event callback dispatched and should not be modified.]] + } + + implements { + Efl.Object.constructor; + Efl.Object.destructor; + Efl.Object.finalize; + Efl.Io.Closer.close; + Efl.Io.Closer.closed.get; + } +} diff --git a/src/lib/ecore/efl_io_file.c b/src/lib/ecore/efl_io_file.c new file mode 100644 index 0000000000..40c0346f01 --- /dev/null +++ b/src/lib/ecore/efl_io_file.c @@ -0,0 +1,195 @@ +#define EFL_IO_READER_PROTECTED 1 +#define EFL_IO_WRITER_PROTECTED 1 +#define EFL_IO_READER_FD_PROTECTED 1 +#define EFL_IO_WRITER_FD_PROTECTED 1 +#define EFL_IO_CLOSER_FD_PROTECTED 1 +#define EFL_IO_SIZER_FD_PROTECTED 1 +#define EFL_IO_POSITIONER_FD_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" +#include +#include +#include + +#define MY_CLASS EFL_IO_FILE_CLASS + +typedef struct _Efl_Io_File_Data +{ + const char *path; + uint32_t flags; + uint32_t mode; + uint64_t last_position; + // TODO: monitor reader.can_read,changed/writer.can_write,changed events in order to dynamically connect to Loop_Fd events. +} Efl_Io_File_Data; + +static void +_efl_io_file_state_update(Eo *o, Efl_Io_File_Data *pd) +{ + uint64_t pos = efl_io_positioner_position_get(o); + uint64_t size = efl_io_sizer_size_get(o); + uint32_t flags = pd->flags & O_ACCMODE; + + if ((flags == O_RDWR) || (flags == O_RDONLY)) + { + efl_io_reader_can_read_set(o, pos < size); + efl_io_reader_eos_set(o, pos >= size); + } + + if ((flags == O_RDWR) || (flags == O_WRONLY)) + efl_io_writer_can_write_set(o, EINA_TRUE); + + if (pd->last_position != pos) + { + pd->last_position = pos; + efl_event_callback_call(o, EFL_IO_POSITIONER_EVENT_POSITION_CHANGED, NULL); + } +} + +EOLIAN static void +_efl_io_file_efl_loop_fd_fd_file_set(Eo *o, Efl_Io_File_Data *pd, int fd) +{ + efl_loop_fd_file_set(efl_super(o, MY_CLASS), fd); + efl_io_positioner_fd_positioner_fd_set(o, fd); + efl_io_sizer_fd_sizer_fd_set(o, fd); + efl_io_reader_fd_reader_fd_set(o, fd); + efl_io_writer_fd_writer_fd_set(o, fd); + efl_io_closer_fd_closer_fd_set(o, fd); + if (fd >= 0) _efl_io_file_state_update(o, pd); +} + +EOLIAN static void +_efl_io_file_flags_set(Eo *o, Efl_Io_File_Data *pd, uint32_t flags) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_finalized_get(o)); + pd->flags = flags; +} + +EOLIAN static uint32_t +_efl_io_file_flags_get(Eo *o EINA_UNUSED, Efl_Io_File_Data *pd) +{ + return pd->flags; // TODO: query from fd? +} + +EOLIAN static void +_efl_io_file_mode_set(Eo *o, Efl_Io_File_Data *pd, uint32_t mode) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_finalized_get(o)); + pd->mode = mode; +} + +EOLIAN static uint32_t +_efl_io_file_mode_get(Eo *o EINA_UNUSED, Efl_Io_File_Data *pd) +{ + return pd->mode; +} + +EOLIAN static void +_efl_io_file_efl_object_destructor(Eo *o, Efl_Io_File_Data *pd) +{ + if (!efl_io_closer_closed_get(o)) + efl_io_closer_close(o); + + efl_destructor(efl_super(o, MY_CLASS)); + + eina_stringshare_del(pd->path); +} + +EOLIAN static Efl_Object * +_efl_io_file_efl_object_finalize(Eo *o, Efl_Io_File_Data *pd) +{ + int fd = efl_loop_fd_file_get(o); + if (fd < 0) + { + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->path, NULL); + + if (pd->mode) + fd = open(pd->path, pd->flags, pd->mode); + else + fd = open(pd->path, pd->flags); + + if (fd < 0) + { + eina_error_set(errno); + ERR("Could not open file '%s': %s", pd->path, strerror(errno)); + return NULL; + } + + efl_loop_fd_file_set(o, fd); + } + + return efl_finalize(efl_super(o, MY_CLASS)); +} + +EOLIAN static Eina_Bool +_efl_io_file_efl_file_file_set(Eo *o, Efl_Io_File_Data *pd, const char *file, const char *key EINA_UNUSED) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_finalized_get(o), EINA_FALSE); + + eina_stringshare_replace(&pd->path, file); + return EINA_TRUE; +} + +EOLIAN static void +_efl_io_file_efl_file_file_get(Eo *o EINA_UNUSED, Efl_Io_File_Data *pd, const char **file, const char **key) +{ + if (file) *file = pd->path; + if (key) *key = NULL; +} + +EOLIAN static Eina_Error +_efl_io_file_efl_io_reader_read(Eo *o, Efl_Io_File_Data *pd, Eina_Rw_Slice *rw_slice) +{ + Eina_Error err = efl_io_reader_read(efl_super(o, MY_CLASS), rw_slice); + if (err) return err; + _efl_io_file_state_update(o, pd); + return 0; +} + +EOLIAN static Eina_Error +_efl_io_file_efl_io_writer_write(Eo *o, Efl_Io_File_Data *pd, Eina_Slice *slice, Eina_Slice *remaining) +{ + Eina_Error err = efl_io_writer_write(efl_super(o, MY_CLASS), slice, remaining); + if (err) return err; + _efl_io_file_state_update(o, pd); + return 0; +} + +EOLIAN static Eina_Error +_efl_io_file_efl_io_closer_close(Eo *o, Efl_Io_File_Data *pd EINA_UNUSED) +{ + Eina_Error ret; + efl_io_reader_can_read_set(o, EINA_FALSE); + efl_io_reader_eos_set(o, EINA_TRUE); + efl_io_writer_can_write_set(o, EINA_FALSE); + + ret = efl_io_closer_close(efl_super(o, MY_CLASS)); + + efl_loop_fd_file_set(o, -1); + + return ret; +} + +EOLIAN static Eina_Error +_efl_io_file_efl_io_sizer_resize(Eo *o, Efl_Io_File_Data *pd, uint64_t size) +{ + Eina_Error err = efl_io_sizer_resize(efl_super(o, MY_CLASS), size); + if (err) return err; + _efl_io_file_state_update(o, pd); + return 0; +} + +EOLIAN static Eina_Error +_efl_io_file_efl_io_positioner_seek(Eo *o, Efl_Io_File_Data *pd, int64_t offset, Efl_Io_Positioner_Whence whence) +{ + Eina_Error err = efl_io_positioner_seek(efl_super(o, MY_CLASS), offset, whence); + if (err) return err; + _efl_io_file_state_update(o, pd); + return 0; +} + +#include "efl_io_file.eo.c" diff --git a/src/lib/ecore/efl_io_file.eo b/src/lib/ecore/efl_io_file.eo new file mode 100644 index 0000000000..6139634ba9 --- /dev/null +++ b/src/lib/ecore/efl_io_file.eo @@ -0,0 +1,49 @@ +class Efl.Io.File (Efl.Loop.Fd, Efl.File, Efl.Io.Reader.Fd, Efl.Io.Writer.Fd, Efl.Io.Closer.Fd, Efl.Io.Sizer.Fd, Efl.Io.Positioner.Fd) { + [[File access (open, close, read, write, lseek, ftruncate) + + Files are closed automatically (@Efl.Io.Closer.Fd) on destruction. + + @since 1.19 + ]] + + methods { + @property flags { + [[bitwise OR'ed flags to open the file, like O_CREAT, O_CLOEXEC...]] + get { + } + + set { + [[constructor property to define flags to open the file]] + } + + values { + flags: uint32; [[flags to open file, see man:open(2).]] + } + } + + @property mode { + get { + } + + set { + [[constructor property to define mode to open the file]] + } + + values { + mode: uint32; [[mode to open file, see man:open(2).]] + } + } + } + + implements { + Efl.Object.destructor; + Efl.Object.finalize; + Efl.Loop.Fd.fd_file.set; + Efl.File.file; + Efl.Io.Reader.read; + Efl.Io.Writer.write; + Efl.Io.Closer.close; + Efl.Io.Sizer.resize; + Efl.Io.Positioner.seek; + } +} diff --git a/src/lib/ecore/efl_io_positioner_fd.c b/src/lib/ecore/efl_io_positioner_fd.c new file mode 100644 index 0000000000..a1df02f107 --- /dev/null +++ b/src/lib/ecore/efl_io_positioner_fd.c @@ -0,0 +1,65 @@ +#define EFL_IO_POSITIONER_FD_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_POSITIONER_FD_CLASS + +typedef struct _Efl_Io_Positioner_Fd_Data +{ + int fd; +} Efl_Io_Positioner_Fd_Data; + +EOLIAN static void +_efl_io_positioner_fd_positioner_fd_set(Eo *o EINA_UNUSED, Efl_Io_Positioner_Fd_Data *pd, int fd) +{ + pd->fd = fd; +} + +EOLIAN static int +_efl_io_positioner_fd_positioner_fd_get(Eo *o EINA_UNUSED, Efl_Io_Positioner_Fd_Data *pd) +{ + return pd->fd; +} + +static inline int +_efl_io_positioner_whence_convert(Efl_Io_Positioner_Whence whence) +{ + switch (whence) + { + case EFL_IO_POSITIONER_WHENCE_START: return SEEK_SET; + case EFL_IO_POSITIONER_WHENCE_CURRENT: return SEEK_CUR; + case EFL_IO_POSITIONER_WHENCE_END: return SEEK_END; + } + return SEEK_SET; +} + +EOLIAN static Eina_Error +_efl_io_positioner_fd_efl_io_positioner_seek(Eo *o, Efl_Io_Positioner_Fd_Data *pd EINA_UNUSED, int64_t offset, Efl_Io_Positioner_Whence whence) +{ + int fd = efl_io_positioner_fd_positioner_fd_get(o); + if (lseek(fd, (off_t)offset, _efl_io_positioner_whence_convert(whence)) < 0) + return errno; + efl_event_callback_call(o, EFL_IO_POSITIONER_EVENT_POSITION_CHANGED, NULL); + return 0; +} + +EOLIAN static uint64_t +_efl_io_positioner_fd_efl_io_positioner_position_get(Eo *o, Efl_Io_Positioner_Fd_Data *pd EINA_UNUSED) +{ + int fd = efl_io_positioner_fd_positioner_fd_get(o); + off_t offset; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(fd < 0, 0); + + offset = lseek(fd, 0, SEEK_CUR); + EINA_SAFETY_ON_TRUE_RETURN_VAL(offset < 0, 0); + + return offset; +} + +#include "efl_io_positioner_fd.eo.c" diff --git a/src/lib/ecore/efl_io_positioner_fd.eo b/src/lib/ecore/efl_io_positioner_fd.eo new file mode 100644 index 0000000000..475d938e6e --- /dev/null +++ b/src/lib/ecore/efl_io_positioner_fd.eo @@ -0,0 +1,21 @@ +mixin Efl.Io.Positioner.Fd (Efl.Io.Positioner) { + [[Position fd using lseek(2). + + @since 1.19 + ]] + + methods { + @property positioner_fd { + get {} + set @protected {} + values { + fd: int; + } + } + } + + implements { + Efl.Io.Positioner.seek; + Efl.Io.Positioner.position.get; + } +} diff --git a/src/lib/ecore/efl_io_reader_fd.c b/src/lib/ecore/efl_io_reader_fd.c new file mode 100644 index 0000000000..b5a94275c4 --- /dev/null +++ b/src/lib/ecore/efl_io_reader_fd.c @@ -0,0 +1,100 @@ +#define EFL_IO_READER_PROTECTED 1 +#define EFL_IO_READER_FD_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_READER_FD_CLASS + +typedef struct _Efl_Io_Reader_Fd_Data +{ + int fd; + Eina_Bool can_read; + Eina_Bool eos; +} Efl_Io_Reader_Fd_Data; + +EOLIAN static void +_efl_io_reader_fd_reader_fd_set(Eo *o EINA_UNUSED, Efl_Io_Reader_Fd_Data *pd, int fd) +{ + pd->fd = fd; +} + +EOLIAN static int +_efl_io_reader_fd_reader_fd_get(Eo *o EINA_UNUSED, Efl_Io_Reader_Fd_Data *pd) +{ + return pd->fd; +} + +EOLIAN static Eina_Error +_efl_io_reader_fd_efl_io_reader_read(Eo *o, Efl_Io_Reader_Fd_Data *pd EINA_UNUSED, Eina_Rw_Slice *rw_slice) +{ + int fd = efl_io_reader_fd_reader_fd_get(o); + ssize_t r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(rw_slice, EINVAL); + if (fd < 0) goto error; + do + { + r = read(fd, rw_slice->mem, rw_slice->len); + if (r < 0) + { + if (errno == EINTR) continue; + + rw_slice->len = 0; + rw_slice->mem = NULL; + if (errno == EAGAIN) efl_io_reader_can_read_set(o, EINA_FALSE); + return errno; + } + } + while (r < 0); + + rw_slice->len = r; + if (r == 0) + { + efl_io_reader_can_read_set(o, EINA_FALSE); + efl_io_reader_eos_set(o, EINA_TRUE); + } + return 0; + + error: + rw_slice->len = 0; + rw_slice->mem = NULL; + return EINVAL; +} + +EOLIAN static Eina_Bool +_efl_io_reader_fd_efl_io_reader_can_read_get(Eo *o EINA_UNUSED, Efl_Io_Reader_Fd_Data *pd) +{ + return pd->can_read; +} + +EOLIAN static void +_efl_io_reader_fd_efl_io_reader_can_read_set(Eo *o, Efl_Io_Reader_Fd_Data *pd, Eina_Bool can_read) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_reader_fd_reader_fd_get(o) < 0); + if (pd->can_read == can_read) return; + pd->can_read = can_read; + efl_event_callback_call(o, EFL_IO_READER_EVENT_CAN_READ_CHANGED, NULL); +} + +EOLIAN static Eina_Bool +_efl_io_reader_fd_efl_io_reader_eos_get(Eo *o EINA_UNUSED, Efl_Io_Reader_Fd_Data *pd) +{ + return pd->eos; +} + +EOLIAN static void +_efl_io_reader_fd_efl_io_reader_eos_set(Eo *o, Efl_Io_Reader_Fd_Data *pd, Eina_Bool is_eos) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_reader_fd_reader_fd_get(o) < 0); + if (pd->eos == is_eos) return; + pd->eos = is_eos; + if (is_eos) + efl_event_callback_call(o, EFL_IO_READER_EVENT_EOS, NULL); +} + +#include "efl_io_reader_fd.eo.c" diff --git a/src/lib/ecore/efl_io_reader_fd.eo b/src/lib/ecore/efl_io_reader_fd.eo new file mode 100644 index 0000000000..4d2a61ece8 --- /dev/null +++ b/src/lib/ecore/efl_io_reader_fd.eo @@ -0,0 +1,24 @@ +mixin Efl.Io.Reader.Fd (Efl.Io.Reader) { + [[Read fd using read(2). + + @since 1.19 + ]] + + methods { + @property reader_fd { + get {} + set @protected {} + values { + fd: int; + } + } + } + + implements { + Efl.Io.Reader.read; + Efl.Io.Reader.can_read.get; + Efl.Io.Reader.can_read.set; + Efl.Io.Reader.eos.get; + Efl.Io.Reader.eos.set; + } +} diff --git a/src/lib/ecore/efl_io_sizer_fd.c b/src/lib/ecore/efl_io_sizer_fd.c new file mode 100644 index 0000000000..a57d1a9496 --- /dev/null +++ b/src/lib/ecore/efl_io_sizer_fd.c @@ -0,0 +1,53 @@ +#define EFL_IO_SIZER_FD_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_SIZER_FD_CLASS + +typedef struct _Efl_Io_Sizer_Fd_Data +{ + int fd; +} Efl_Io_Sizer_Fd_Data; + +EOLIAN static void +_efl_io_sizer_fd_sizer_fd_set(Eo *o EINA_UNUSED, Efl_Io_Sizer_Fd_Data *pd, int fd) +{ + pd->fd = fd; +} + +EOLIAN static int +_efl_io_sizer_fd_sizer_fd_get(Eo *o EINA_UNUSED, Efl_Io_Sizer_Fd_Data *pd) +{ + return pd->fd; +} + +EOLIAN static Eina_Error +_efl_io_sizer_fd_efl_io_sizer_resize(Eo *o, Efl_Io_Sizer_Fd_Data *pd EINA_UNUSED, uint64_t size) +{ + int fd = efl_io_sizer_fd_sizer_fd_get(o); + if (ftruncate(fd, size) < 0) return errno; + efl_event_callback_call(o, EFL_IO_SIZER_EVENT_SIZE_CHANGED, NULL); + return 0; +} + +EOLIAN static uint64_t +_efl_io_sizer_fd_efl_io_sizer_size_get(Eo *o, Efl_Io_Sizer_Fd_Data *pd EINA_UNUSED) +{ + int fd = efl_io_sizer_fd_sizer_fd_get(o); + struct stat st; + int r; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(fd < 0, 0); + + r = fstat(fd, &st); + EINA_SAFETY_ON_TRUE_RETURN_VAL(r < 0, 0); + + return st.st_size; +} + +#include "efl_io_sizer_fd.eo.c" diff --git a/src/lib/ecore/efl_io_sizer_fd.eo b/src/lib/ecore/efl_io_sizer_fd.eo new file mode 100644 index 0000000000..82cbb87e31 --- /dev/null +++ b/src/lib/ecore/efl_io_sizer_fd.eo @@ -0,0 +1,21 @@ +mixin Efl.Io.Sizer.Fd (Efl.Io.Sizer) { + [[Resize fd usign ftruncate(2). + + @since 1.19 + ]] + + methods { + @property sizer_fd { + get {} + set @protected {} + values { + fd: int; + } + } + } + + implements { + Efl.Io.Sizer.resize; + Efl.Io.Sizer.size.get; + } +} diff --git a/src/lib/ecore/efl_io_stderr.c b/src/lib/ecore/efl_io_stderr.c new file mode 100644 index 0000000000..c41308afbb --- /dev/null +++ b/src/lib/ecore/efl_io_stderr.c @@ -0,0 +1,60 @@ +#define EFL_IO_WRITER_FD_PROTECTED 1 +#define EFL_IO_WRITER_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_STDERR_CLASS + +static void +_efl_io_stderr_event_write(void *data EINA_UNUSED, const Eo_Event *event) +{ + efl_io_writer_can_write_set(event->object, EINA_TRUE); +} + +static void +_efl_io_stderr_event_error(void *data EINA_UNUSED, const Eo_Event *event) +{ + efl_io_writer_can_write_set(event->object, EINA_FALSE); +} + +EOLIAN static void +_efl_io_stderr_efl_loop_fd_fd_set(Eo *o, void *pd EINA_UNUSED, int fd) +{ + efl_loop_fd_file_set(efl_super(o, MY_CLASS), fd); + efl_io_writer_fd_writer_fd_set(o, fd); +} + +EOLIAN static Efl_Object * +_efl_io_stderr_efl_object_finalize(Eo *o, void *pd EINA_UNUSED) +{ + int fd = efl_loop_fd_get(o); + if (fd < 0) efl_loop_fd_set(o, STDIN_FILENO); + + o = efl_finalize(efl_super(o, MY_CLASS)); + if (!o) return NULL; + + // TODO: only register "write" if "can_write" is being monitored? + efl_event_callback_add(o, EFL_LOOP_FD_EVENT_WRITE, _efl_io_stderr_event_write, NULL); + efl_event_callback_add(o, EFL_LOOP_FD_EVENT_ERROR, _efl_io_stderr_event_error, NULL); + + return o; +} + +EOLIAN static Eina_Error +_efl_io_stderr_efl_io_writer_write(Eo *o, void *pd EINA_UNUSED, Eina_Slice *ro_slice, Eina_Slice *remaining) +{ + Eina_Error ret; + + ret = efl_io_writer_write(efl_super(o, MY_CLASS), ro_slice, remaining); + if (ro_slice && ro_slice->len > 0) + efl_io_writer_can_write_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "write" */ + + return ret; +} + +#include "efl_io_stderr.eo.c" diff --git a/src/lib/ecore/efl_io_stderr.eo b/src/lib/ecore/efl_io_stderr.eo new file mode 100644 index 0000000000..5da5641515 --- /dev/null +++ b/src/lib/ecore/efl_io_stderr.eo @@ -0,0 +1,14 @@ +class Efl.Io.Stderr (Efl.Loop.Fd, Efl.Io.Writer.Fd) { + [[Application's standard error (stderr). + + @since 1.19 + ]] + + data: null; + + implements { + Efl.Object.finalize; + Efl.Loop.Fd.fd.set; + Efl.Io.Writer.write; + } +} diff --git a/src/lib/ecore/efl_io_stdin.c b/src/lib/ecore/efl_io_stdin.c new file mode 100644 index 0000000000..640d512353 --- /dev/null +++ b/src/lib/ecore/efl_io_stdin.c @@ -0,0 +1,61 @@ +#define EFL_IO_READER_PROTECTED 1 +#define EFL_IO_READER_FD_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_STDIN_CLASS + +static void +_efl_io_stdin_event_read(void *data EINA_UNUSED, const Eo_Event *event) +{ + efl_io_reader_can_read_set(event->object, EINA_TRUE); + efl_io_reader_eos_set(event->object, EINA_FALSE); +} + +static void +_efl_io_stdin_event_error(void *data EINA_UNUSED, const Eo_Event *event) +{ + efl_io_reader_can_read_set(event->object, EINA_FALSE); + efl_io_reader_eos_set(event->object, EINA_TRUE); +} + +EOLIAN static void +_efl_io_stdin_efl_loop_fd_fd_set(Eo *o, void *pd EINA_UNUSED, int fd) +{ + efl_loop_fd_file_set(efl_super(o, MY_CLASS), fd); + efl_io_reader_fd_reader_fd_set(o, fd); +} + +EOLIAN static Efl_Object * +_efl_io_stdin_efl_object_finalize(Eo *o, void *pd EINA_UNUSED) +{ + int fd = efl_loop_fd_get(o); + if (fd < 0) efl_loop_fd_set(o, STDIN_FILENO); + + o = efl_finalize(efl_super(o, MY_CLASS)); + if (!o) return NULL; + + // TODO: only register "read" if "can_read" is being monitored? + efl_event_callback_add(o, EFL_LOOP_FD_EVENT_READ, _efl_io_stdin_event_read, NULL); + efl_event_callback_add(o, EFL_LOOP_FD_EVENT_ERROR, _efl_io_stdin_event_error, NULL); + return o; +} + +EOLIAN static Eina_Error +_efl_io_stdin_efl_io_reader_read(Eo *o, void *pd EINA_UNUSED, Eina_Rw_Slice *rw_slice) +{ + Eina_Error ret; + + ret = efl_io_reader_read(efl_super(o, MY_CLASS), rw_slice); + if (rw_slice && rw_slice->len > 0) + efl_io_reader_can_read_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "read" */ + + return ret; +} + +#include "efl_io_stdin.eo.c" diff --git a/src/lib/ecore/efl_io_stdin.eo b/src/lib/ecore/efl_io_stdin.eo new file mode 100644 index 0000000000..4a865581ed --- /dev/null +++ b/src/lib/ecore/efl_io_stdin.eo @@ -0,0 +1,14 @@ +class Efl.Io.Stdin (Efl.Loop.Fd, Efl.Io.Reader.Fd) { + [[Application's standard input (stdin). + + @since 1.19 + ]] + + data: null; + + implements { + Efl.Object.finalize; + Efl.Loop.Fd.fd.set; + Efl.Io.Reader.read; + } +} diff --git a/src/lib/ecore/efl_io_stdout.c b/src/lib/ecore/efl_io_stdout.c new file mode 100644 index 0000000000..df4c31631c --- /dev/null +++ b/src/lib/ecore/efl_io_stdout.c @@ -0,0 +1,59 @@ +#define EFL_IO_WRITER_FD_PROTECTED 1 +#define EFL_IO_WRITER_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_STDOUT_CLASS + +static void +_efl_io_stdout_event_write(void *data EINA_UNUSED, const Eo_Event *event) +{ + efl_io_writer_can_write_set(event->object, EINA_TRUE); +} + +static void +_efl_io_stdout_event_error(void *data EINA_UNUSED, const Eo_Event *event) +{ + efl_io_writer_can_write_set(event->object, EINA_FALSE); +} + +EOLIAN static void +_efl_io_stdout_efl_loop_fd_fd_set(Eo *o, void *pd EINA_UNUSED, int fd) +{ + efl_loop_fd_file_set(efl_super(o, MY_CLASS), fd); + efl_io_writer_fd_writer_fd_set(o, fd); +} + +EOLIAN static Efl_Object * +_efl_io_stdout_efl_object_finalize(Eo *o, void *pd EINA_UNUSED) +{ + int fd = efl_loop_fd_get(o); + if (fd < 0) efl_loop_fd_set(o, STDOUT_FILENO); + + o = efl_finalize(efl_super(o, MY_CLASS)); + if (!o) return NULL; + + // TODO: only register "write" if "can_write" is being monitored? + efl_event_callback_add(o, EFL_LOOP_FD_EVENT_WRITE, _efl_io_stdout_event_write, NULL); + efl_event_callback_add(o, EFL_LOOP_FD_EVENT_ERROR, _efl_io_stdout_event_error, NULL); + return o; +} + +EOLIAN static Eina_Error +_efl_io_stdout_efl_io_writer_write(Eo *o, void *pd EINA_UNUSED, Eina_Slice *ro_slice, Eina_Slice *remaining) +{ + Eina_Error ret; + + ret = efl_io_writer_write(efl_super(o, MY_CLASS), ro_slice, remaining); + if (ro_slice && ro_slice->len > 0) + efl_io_writer_can_write_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "write" */ + + return ret; +} + +#include "efl_io_stdout.eo.c" diff --git a/src/lib/ecore/efl_io_stdout.eo b/src/lib/ecore/efl_io_stdout.eo new file mode 100644 index 0000000000..0dcc6eb2b5 --- /dev/null +++ b/src/lib/ecore/efl_io_stdout.eo @@ -0,0 +1,14 @@ +class Efl.Io.Stdout (Efl.Loop.Fd, Efl.Io.Writer.Fd) { + [[Application's standard output (stdout). + + @since 1.19 + ]] + + data: null; + + implements { + Efl.Object.finalize; + Efl.Loop.Fd.fd.set; + Efl.Io.Writer.write; + } +} diff --git a/src/lib/ecore/efl_io_writer_fd.c b/src/lib/ecore/efl_io_writer_fd.c new file mode 100644 index 0000000000..dac739b116 --- /dev/null +++ b/src/lib/ecore/efl_io_writer_fd.c @@ -0,0 +1,88 @@ +#define EFL_IO_WRITER_PROTECTED 1 +#define EFL_IO_WRITER_FD_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ecore_private.h" + +#define MY_CLASS EFL_IO_WRITER_FD_CLASS + +typedef struct _Efl_Io_Writer_Fd_Data +{ + int fd; + Eina_Bool can_write; +} Efl_Io_Writer_Fd_Data; + +EOLIAN static void +_efl_io_writer_fd_writer_fd_set(Eo *o EINA_UNUSED, Efl_Io_Writer_Fd_Data *pd, int fd) +{ + pd->fd = fd; +} + +EOLIAN static int +_efl_io_writer_fd_writer_fd_get(Eo *o EINA_UNUSED, Efl_Io_Writer_Fd_Data *pd) +{ + return pd->fd; +} + +EOLIAN static Eina_Error +_efl_io_writer_fd_efl_io_writer_write(Eo *o, Efl_Io_Writer_Fd_Data *pd EINA_UNUSED, Eina_Slice *ro_slice, Eina_Slice *remaining) +{ + int fd = efl_io_writer_fd_writer_fd_get(o); + ssize_t r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ro_slice, EINVAL); + if (fd < 0) goto error; + + do + { + r = write(fd, ro_slice->mem, ro_slice->len); + if (r < 0) + { + if (errno == EINTR) continue; + + if (remaining) *remaining = *ro_slice; + ro_slice->len = 0; + ro_slice->mem = NULL; + if (errno == EAGAIN) efl_io_writer_can_write_set(o, EINA_FALSE); + return errno; + } + } + while (r < 0); + + if (remaining) + { + remaining->len = ro_slice->len - r; + remaining->bytes = ro_slice->bytes + r; + } + ro_slice->len = r; + if (r == 0) efl_io_writer_can_write_set(o, EINA_FALSE); + return 0; + + error: + if (remaining) *remaining = *ro_slice; + ro_slice->len = 0; + ro_slice->mem = NULL; + return EINVAL; + +} + +EOLIAN static Eina_Bool +_efl_io_writer_fd_efl_io_writer_can_write_get(Eo *o EINA_UNUSED, Efl_Io_Writer_Fd_Data *pd) +{ + return pd->can_write; +} + +EOLIAN static void +_efl_io_writer_fd_efl_io_writer_can_write_set(Eo *o, Efl_Io_Writer_Fd_Data *pd, Eina_Bool can_write) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_writer_fd_writer_fd_get(o) < 0); + if (pd->can_write == can_write) return; + pd->can_write = can_write; + efl_event_callback_call(o, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, NULL); +} + +#include "efl_io_writer_fd.eo.c" diff --git a/src/lib/ecore/efl_io_writer_fd.eo b/src/lib/ecore/efl_io_writer_fd.eo new file mode 100644 index 0000000000..7d793a2c7a --- /dev/null +++ b/src/lib/ecore/efl_io_writer_fd.eo @@ -0,0 +1,22 @@ +mixin Efl.Io.Writer.Fd (Efl.Io.Writer) { + [[Write fd using write(2). + + @since 1.19 + ]] + + methods { + @property writer_fd { + get {} + set @protected {} + values { + fd: int; + } + } + } + + implements { + Efl.Io.Writer.write; + Efl.Io.Writer.can_write.get; + Efl.Io.Writer.can_write.set; + } +} diff --git a/src/lib/efl/Efl.h b/src/lib/efl/Efl.h index cfc8247ed7..38f7bab70b 100644 --- a/src/lib/efl/Efl.h +++ b/src/lib/efl/Efl.h @@ -129,6 +129,15 @@ EAPI extern const Efl_Event_Description _EFL_GFX_PATH_CHANGED; #include "interfaces/efl_input_interface.eo.h" #include "interfaces/efl_event.eo.h" +/* Input and Output */ +#include "interfaces/efl_io_closer.eo.h" +#include "interfaces/efl_io_reader.eo.h" +#include "interfaces/efl_io_writer.eo.h" +#include "interfaces/efl_io_sizer.eo.h" +#include "interfaces/efl_io_positioner.eo.h" + +#include "interfaces/efl_io_buffer.eo.h" + #else #ifndef EFL_NOLEGACY_API_SUPPORT diff --git a/src/lib/efl/interfaces/efl_io_buffer.c b/src/lib/efl/interfaces/efl_io_buffer.c new file mode 100644 index 0000000000..22a7db8254 --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_buffer.c @@ -0,0 +1,487 @@ +#define EFL_IO_READER_PROTECTED 1 +#define EFL_IO_WRITER_PROTECTED 1 + +#include "config.h" +#include "Efl.h" + +#define MY_CLASS EFL_IO_BUFFER_CLASS + +typedef struct _Efl_Io_Buffer_Data +{ + uint8_t *bytes; + size_t allocated; + size_t used; + size_t limit; + size_t position_read; + size_t position_write; + Eina_Bool closed; + Eina_Bool can_read; + Eina_Bool can_write; +} Efl_Io_Buffer_Data; + +static Eina_Bool +_efl_io_buffer_realloc(Eo *o, Efl_Io_Buffer_Data *pd, size_t size) +{ + void *tmp; + size_t limit = efl_io_buffer_limit_get(o); + + if ((limit > 0) && (size > limit)) + size = limit; + + if (pd->allocated == size) return EINA_FALSE; + + if (efl_io_sizer_size_get(o) > size) + { + if (efl_io_buffer_position_read_get(o) > size) + efl_io_buffer_position_read_set(o, size); + if (efl_io_buffer_position_write_get(o) > size) + efl_io_buffer_position_write_set(o, size); + + /* no efl_io_sizer_size_set() since it could recurse! */ + pd->used = size; + efl_event_callback_call(o, EFL_IO_SIZER_EVENT_SIZE_CHANGED, NULL); + } + + if (size == 0) + { + free(pd->bytes); + tmp = NULL; + } + else + { + tmp = realloc(pd->bytes, size); + EINA_SAFETY_ON_NULL_RETURN_VAL(tmp, EINA_FALSE); + } + + pd->bytes = tmp; + pd->allocated = size; + efl_event_callback_call(o, EFL_IO_BUFFER_EVENT_REALLOCATED, NULL); + return EINA_TRUE; +} + +static Eina_Bool +_efl_io_buffer_realloc_rounded(Eo *o, Efl_Io_Buffer_Data *pd, size_t size) +{ + if ((size > 0) && (size < 128)) + size = ((size / 32) + 1) * 32; + else if (size < 1024) + size = ((size / 128) + 1) * 128; + else if (size < 8192) + size = ((size / 1024) + 1) * 1024; + else + size = ((size / 4096) + 1) * 4096; + + return _efl_io_buffer_realloc(o, pd, size); +} + +EOLIAN static void +_efl_io_buffer_preallocate(Eo *o, Efl_Io_Buffer_Data *pd, size_t size) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o)); + if (pd->allocated < size) + _efl_io_buffer_realloc_rounded(o, pd, size); +} + +EOLIAN static void +_efl_io_buffer_limit_set(Eo *o, Efl_Io_Buffer_Data *pd, size_t limit) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o)); + + if (pd->limit == limit) return; + pd->limit = limit; + + if (pd->allocated > limit) + _efl_io_buffer_realloc(o, pd, limit); + + efl_io_reader_can_read_set(o, efl_io_buffer_position_read_get(o) < efl_io_sizer_size_get(o)); + efl_io_writer_can_write_set(o, (limit == 0) || + (efl_io_buffer_position_write_get(o) < limit)); +} + +EOLIAN static size_t +_efl_io_buffer_limit_get(Eo *o EINA_UNUSED, Efl_Io_Buffer_Data *pd) +{ + return pd->limit; +} + +EOLIAN static Eina_Bool +_efl_io_buffer_slice_get(Eo *o, Efl_Io_Buffer_Data *pd, Eina_Slice *slice) +{ + if (slice) + { + slice->mem = pd->bytes; + slice->len = efl_io_sizer_size_get(o); + } + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EINA_FALSE); + return EINA_TRUE; +} + +EOLIAN static Eina_Binbuf * +_efl_io_buffer_binbuf_steal(Eo *o, Efl_Io_Buffer_Data *pd) +{ + Eina_Binbuf *ret; + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), NULL); + + ret = eina_binbuf_manage_new(pd->bytes, efl_io_sizer_size_get(o), EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(ret, NULL); + + pd->bytes = NULL; + pd->allocated = 0; + efl_event_callback_call(o, EFL_IO_BUFFER_EVENT_REALLOCATED, NULL); + efl_io_sizer_resize(o, 0); + + return ret; +} + +EOLIAN static Efl_Object * +_efl_io_buffer_efl_object_finalize(Eo *o, Efl_Io_Buffer_Data *pd EINA_UNUSED) +{ + size_t limit; + + o = efl_finalize(efl_super(o, MY_CLASS)); + if (!o) return NULL; + + efl_io_reader_can_read_set(o, efl_io_buffer_position_read_get(o) < efl_io_sizer_size_get(o)); + limit = efl_io_buffer_limit_get(o); + efl_io_writer_can_write_set(o, (limit == 0) || + (efl_io_buffer_position_write_get(o) < limit)); + + return o; +} + +EOLIAN static void +_efl_io_buffer_efl_object_destructor(Eo *o, Efl_Io_Buffer_Data *pd) +{ + if (!efl_io_closer_closed_get(o)) + efl_io_closer_close(o); + + efl_destructor(efl_super(o, MY_CLASS)); + + if (pd->bytes) + { + free(pd->bytes); + pd->bytes = NULL; + pd->allocated = 0; + pd->used = 0; + pd->position_read = 0; + pd->position_write = 0; + } +} + +EOLIAN static Eina_Error +_efl_io_buffer_efl_io_reader_read(Eo *o, Efl_Io_Buffer_Data *pd, Eina_Rw_Slice *rw_slice) +{ + Eina_Slice ro_slice; + size_t used, read_pos, available; + + EINA_SAFETY_ON_NULL_RETURN_VAL(rw_slice, EINVAL); + EINA_SAFETY_ON_TRUE_GOTO(efl_io_closer_closed_get(o), error); + + used = efl_io_sizer_size_get(o); + read_pos = efl_io_buffer_position_read_get(o); + available = used - read_pos; + + if (rw_slice->len > available) + { + rw_slice->len = available; + if (rw_slice->len == 0) + return EAGAIN; + } + + ro_slice.len = rw_slice->len; + ro_slice.mem = pd->bytes + read_pos; + + *rw_slice = eina_rw_slice_copy(*rw_slice, ro_slice); + efl_io_buffer_position_read_set(o, read_pos + ro_slice.len); + + return 0; + + error: + rw_slice->len = 0; + return EINVAL; +} + +EOLIAN static Eina_Bool +_efl_io_buffer_efl_io_reader_can_read_get(Eo *o EINA_UNUSED, Efl_Io_Buffer_Data *pd) +{ + return pd->can_read; +} + +EOLIAN static void +_efl_io_buffer_efl_io_reader_can_read_set(Eo *o, Efl_Io_Buffer_Data *pd, Eina_Bool can_read) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o)); + if (pd->can_read == can_read) return; + pd->can_read = can_read; + efl_event_callback_call(o, EFL_IO_READER_EVENT_CAN_READ_CHANGED, NULL); +} + +EOLIAN static Eina_Bool +_efl_io_buffer_efl_io_reader_eos_get(Eo *o, Efl_Io_Buffer_Data *pd EINA_UNUSED) +{ + return efl_io_closer_closed_get(o) || + efl_io_buffer_position_read_get(o) >= efl_io_sizer_size_get(o); +} + +EOLIAN static void +_efl_io_buffer_efl_io_reader_eos_set(Eo *o, Efl_Io_Buffer_Data *pd EINA_UNUSED, Eina_Bool is_eos) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o)); + if (is_eos) + efl_event_callback_call(o, EFL_IO_READER_EVENT_EOS, NULL); +} + +EOLIAN static Eina_Error +_efl_io_buffer_efl_io_writer_write(Eo *o, Efl_Io_Buffer_Data *pd, Eina_Slice *slice, Eina_Slice *remaining) +{ + size_t available, todo, write_pos, limit; + int err = EINVAL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(slice, EINVAL); + EINA_SAFETY_ON_TRUE_GOTO(efl_io_closer_closed_get(o), error); + + write_pos = efl_io_buffer_position_write_get(o); + available = pd->allocated - write_pos; + limit = efl_io_buffer_limit_get(o); + + err = ENOSPC; + if (available >= slice->len) + todo = slice->len; + else if ((limit > 0) && (pd->allocated == limit)) goto error; + else + { + _efl_io_buffer_realloc_rounded(o, pd, write_pos + slice->len); + if (pd->allocated >= write_pos + slice->len) + todo = slice->len; + else + todo = pd->allocated - write_pos; + + if (todo == 0) goto error; + } + + memcpy(pd->bytes + write_pos, slice->mem, todo); + if (remaining) + { + remaining->len = slice->len - todo; + if (remaining->len) + remaining->mem = slice->bytes + todo; + else + remaining->mem = NULL; + } + slice->len = todo; + + if (pd->used < write_pos + todo) + { + pd->used = write_pos + todo; + efl_event_callback_call(o, EFL_IO_SIZER_EVENT_SIZE_CHANGED, NULL); + efl_io_reader_can_read_set(o, pd->position_read < pd->used); + } + efl_io_buffer_position_write_set(o, write_pos + todo); + + return 0; + + error: + if (remaining) *remaining = *slice; + slice->len = 0; + return err; +} + +EOLIAN static Eina_Bool +_efl_io_buffer_efl_io_writer_can_write_get(Eo *o EINA_UNUSED, Efl_Io_Buffer_Data *pd) +{ + return pd->can_write; +} + +EOLIAN static void +_efl_io_buffer_efl_io_writer_can_write_set(Eo *o, Efl_Io_Buffer_Data *pd, Eina_Bool can_write) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o)); + if (pd->can_write == can_write) return; + pd->can_write = can_write; + efl_event_callback_call(o, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, NULL); +} + +EOLIAN static Eina_Error +_efl_io_buffer_efl_io_closer_close(Eo *o, Efl_Io_Buffer_Data *pd) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EINVAL); + efl_io_sizer_resize(o, 0); + pd->closed = EINA_TRUE; + efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL); + return 0; +} + +EOLIAN static Eina_Bool +_efl_io_buffer_efl_io_closer_closed_get(Eo *o EINA_UNUSED, Efl_Io_Buffer_Data *pd) +{ + return pd->closed; +} + +EOLIAN static Eina_Error +_efl_io_buffer_efl_io_sizer_resize(Eo *o, Efl_Io_Buffer_Data *pd, uint64_t size) +{ + Eina_Error ret = 0; + Eina_Bool reallocated; + size_t old_size, pos_read, pos_write; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EINVAL); + + if (efl_io_sizer_size_get(o) == size) return 0; + + old_size = pd->used; + pd->used = size; + + efl_event_freeze(o); + reallocated = _efl_io_buffer_realloc_rounded(o, pd, size); + efl_event_thaw(o); + + if (size > pd->allocated) + { + pd->used = size = pd->allocated; + ret = ENOSPC; + } + + if (old_size < size) + memset(pd->bytes + old_size, 0, size - old_size); + + pos_read = efl_io_buffer_position_read_get(o); + if (pos_read > size) + efl_io_buffer_position_read_set(o, size); + else + efl_io_reader_can_read_set(o, pos_read < size); + + pos_write = efl_io_buffer_position_write_get(o); + if (pos_write > size) + efl_io_buffer_position_write_set(o, size); + else + { + size_t limit = efl_io_buffer_limit_get(o); + efl_io_writer_can_write_set(o, (limit == 0) || (pos_write < limit)); + } + + efl_event_callback_call(o, EFL_IO_SIZER_EVENT_SIZE_CHANGED, NULL); + if (reallocated) + efl_event_callback_call(o, EFL_IO_BUFFER_EVENT_REALLOCATED, NULL); + + return ret; +} + +EOLIAN static uint64_t +_efl_io_buffer_efl_io_sizer_size_get(Eo *o EINA_UNUSED, Efl_Io_Buffer_Data *pd) +{ + return pd->used; +} + +EOLIAN static Eina_Error +_efl_io_buffer_efl_io_positioner_seek(Eo *o, Efl_Io_Buffer_Data *pd EINA_UNUSED, int64_t offset, Efl_Io_Positioner_Whence whence) +{ + size_t size; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EINVAL); + + size = efl_io_sizer_size_get(o); + + if (whence == EFL_IO_POSITIONER_WHENCE_CURRENT) + { + whence = EFL_IO_POSITIONER_WHENCE_START; + offset += efl_io_positioner_position_get(o); + } + else if (whence == EFL_IO_POSITIONER_WHENCE_END) + { + whence = EFL_IO_POSITIONER_WHENCE_START; + offset += size; + } + + EINA_SAFETY_ON_TRUE_RETURN_VAL(whence != EFL_IO_POSITIONER_WHENCE_START, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(offset < 0, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL((size_t)offset > size, EINVAL); + + efl_io_buffer_position_read_set(o, offset); + efl_io_buffer_position_write_set(o, offset); + + return 0; +} + +EOLIAN static uint64_t +_efl_io_buffer_efl_io_positioner_position_get(Eo *o, Efl_Io_Buffer_Data *pd EINA_UNUSED) +{ + uint64_t r = efl_io_buffer_position_read_get(o); + uint64_t w = efl_io_buffer_position_write_get(o); + /* if using Efl.Io.Positioner.position, on set it will do both + * read/write to the same offset, however on Efl.Io.Reader.read it + * will only update position_read (and similarly for + * Efl.Io.Writer), thus on the next position.get we want the + * greatest position. + * + * This allows the buffer to be used solely as reader or writer + * without the need to know it have two internal offsets. + */ + if (r >= w) + return r; + return w; +} + +EOLIAN static Eina_Bool +_efl_io_buffer_position_read_set(Eo *o, Efl_Io_Buffer_Data *pd, uint64_t position) +{ + size_t size; + Eina_Bool changed; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EINA_FALSE); + + size = efl_io_sizer_size_get(o); + EINA_SAFETY_ON_TRUE_RETURN_VAL(position > size, EINA_FALSE); + + if (pd->position_read == position) return EINA_TRUE; + + changed = efl_io_positioner_position_get(o) != position; + + pd->position_read = position; + efl_event_callback_call(o, EFL_IO_BUFFER_EVENT_POSITION_READ_CHANGED, NULL); + if (changed) + efl_event_callback_call(o, EFL_IO_POSITIONER_EVENT_POSITION_CHANGED, NULL); + + efl_io_reader_can_read_set(o, position < size); + efl_io_reader_eos_set(o, position >= size); + return EINA_TRUE; +} + +EOLIAN static uint64_t +_efl_io_buffer_position_read_get(Eo *o EINA_UNUSED, Efl_Io_Buffer_Data *pd) +{ + return pd->position_read; +} + +EOLIAN static Eina_Bool +_efl_io_buffer_position_write_set(Eo *o, Efl_Io_Buffer_Data *pd, uint64_t position) +{ + size_t size; + size_t limit; + Eina_Bool changed; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EINA_FALSE); + + size = efl_io_sizer_size_get(o); + EINA_SAFETY_ON_TRUE_RETURN_VAL(position > size, EINA_FALSE); + + if (pd->position_write == position) return EINA_TRUE; + + changed = efl_io_positioner_position_get(o) != position; + + pd->position_write = position; + efl_event_callback_call(o, EFL_IO_BUFFER_EVENT_POSITION_WRITE_CHANGED, NULL); + if (changed) + efl_event_callback_call(o, EFL_IO_POSITIONER_EVENT_POSITION_CHANGED, NULL); + + limit = efl_io_buffer_limit_get(o); + efl_io_writer_can_write_set(o, (limit == 0) || (position < limit)); + return EINA_TRUE; +} + +EOLIAN static uint64_t +_efl_io_buffer_position_write_get(Eo *o EINA_UNUSED, Efl_Io_Buffer_Data *pd) +{ + return pd->position_write; +} + +#include "interfaces/efl_io_buffer.eo.c" diff --git a/src/lib/efl/interfaces/efl_io_buffer.eo b/src/lib/efl/interfaces/efl_io_buffer.eo new file mode 100644 index 0000000000..066b8ff050 --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_buffer.eo @@ -0,0 +1,129 @@ +class Efl.Io.Buffer (Efl.Object, Efl.Io.Reader, Efl.Io.Writer, Efl.Io.Closer, Efl.Io.Sizer, Efl.Io.Positioner) { + [[Generic In-memory buffer of data to be used as I/O. + + This class offers both input and output, which can be used at + the same time since @Efl.Io.Reader.read and @Efl.Io.Writer.write + use different offsets/position internally. + + One can get temporary direct access to internal buffer with + @.slice_get or steal the buffer with @.binbuf_steal. + + A fixed sized buffer can be implemented by setting @.limit + followed by @.preallocate + + See @Efl.Io.File. + + @since 1.19 + ]] + + methods { + preallocate { + [[Immediately pre-allocate a buffer of at least a given size.]] + params { + @in size: size; [[amount of bytes to pre-allocate.]] + } + } + + @property limit { + [[Limit how big the buffer can grow. + + This affects both @.preallocate and how buffer grows + when @Efl.Io.Writer.write is called. + + If you want a buffer of an exact size, always set the + limit before any further calls that can grow it. + ]] + get { } + set { + [[Constructor-only property to set buffer limit. 0 is unlimited]] + } + values { + size: size; [[Defines a maximum buffer size, or 0 to allow unlimited amount of bytes]] + } + } + + @property position_read { + [[The position used by @Efl.Io.Reader.read. + + Note that @Efl.Io.Positioner.seek or + @Efl.Io.Positioner.position.set will affect this property + and @.position_write. + + @Efl.Io.Positioner.position.get will return the greatest + of @.position_read and @.position_write. + ]] + get { } + set { + return: bool (false); + } + values { + position: uint64; + } + } + + @property position_write { + [[The position used by @Efl.Io.Writer.write. + + Note that @Efl.Io.Positioner.seek or + @Efl.Io.Positioner.position.set will affect this property + and @.position_read. + + @Efl.Io.Positioner.position.get will return the greatest + of @.position_read and @.position_write. + ]] + get { } + set { + return: bool (false); + } + values { + position: uint64; + } + } + + slice_get { // TODO: property and return of Eina.Slice (not pointer) + [[Get a temporary access to buffer's internal memory. + + The memory pointed by slice may be changed by other + methods of this class. The event "reallocated" will be + called in those situations. + ]] + params { + @out slice: Eina.Slice; [[slice of the current buffer, may be invalidated if @Efl.Io.Writer.write, @Efl.Io.Closer.close or @Efl.Io.Sizer.resize are called. It is the full slice, not a partial one starting at current position.]] + } + return: bool (false); + } + + binbuf_steal { + [[Steals the internal buffer memory and returns it as a binbuf. + + The returned memory must be freed with eina_binbuf_free(). + ]] + return: free(own(Eina.Binbuf*), eina_binbuf_free) @warn_unused; + } + } + + events { + position_read,changed; [[Notifies @.position_read changed]] + position_write,changed; [[Notifies @.position_write changed]] + reallocated; [[Notifies the internal buffer was reallocated, thus whatever was returned by @.slice_get becomes invalid]] + } + + implements { + Efl.Object.finalize; + Efl.Object.destructor; + Efl.Io.Reader.read; + Efl.Io.Reader.can_read.get; + Efl.Io.Reader.can_read.set; + Efl.Io.Reader.eos.get; + Efl.Io.Reader.eos.set; + Efl.Io.Writer.write; + Efl.Io.Writer.can_write.get; + Efl.Io.Writer.can_write.set; + Efl.Io.Closer.close; + Efl.Io.Closer.closed.get; + Efl.Io.Sizer.resize; + Efl.Io.Sizer.size.get; + Efl.Io.Positioner.seek; + Efl.Io.Positioner.position.get; + } +} diff --git a/src/lib/efl/interfaces/efl_io_closer.c b/src/lib/efl/interfaces/efl_io_closer.c new file mode 100644 index 0000000000..55b27f7b4d --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_closer.c @@ -0,0 +1,13 @@ +#include "config.h" +#include "Efl.h" + +EOLIAN static Eina_Bool +_efl_io_closer_closed_set(Eo *obj, void *pd EINA_UNUSED, Eina_Bool is_closed) +{ + if (is_closed) + return efl_io_closer_close(obj) == 0; + + return EINA_FALSE; +} + +#include "interfaces/efl_io_closer.eo.c" diff --git a/src/lib/efl/interfaces/efl_io_closer.eo b/src/lib/efl/interfaces/efl_io_closer.eo new file mode 100644 index 0000000000..6a27b02a9b --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_closer.eo @@ -0,0 +1,49 @@ +import eina_types; + +mixin Efl.Io.Closer { + [[Generic interface for objects that can close themselves. + + This interface allows external objects to transparently close an + input or output stream, cleaning up its resources. + + Calls to @.close() may or may not block, that's not up to this + interface to specify. + + @since 1.19 + ]] + + data: null; + + methods { + close @virtual_pure { + [[Closes the Input/Output object. + + This operation will be executed immediately and may or + may not block the caller thread for some time. The + details of blocking behavior is to be defined by the + implementation and may be subject to other parameters + such as non-blocking flags, maximum timeout or even + retry attempts. + + You can understand this method as close(2) libc function. + ]] + return: Eina.Error; [[0 on succeed, a mapping of errno otherwise]] + } + + @property closed { + [[If true will notify object was closed.]] + get @virtual_pure { } + set { + [[If true, calls close()]] + return: bool; [[$true if could close, $false if already closed or errors.]] + } + values { + is_closed: bool; + } + } + } + + events { + closed; [[Notifies closed, when property is marked as true]] + } +} diff --git a/src/lib/efl/interfaces/efl_io_positioner.c b/src/lib/efl/interfaces/efl_io_positioner.c new file mode 100644 index 0000000000..e8d1722f2a --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_positioner.c @@ -0,0 +1,10 @@ +#include "config.h" +#include "Efl.h" + +EOLIAN static Eina_Bool +_efl_io_positioner_position_set(Eo *o, void *pd EINA_UNUSED, uint64_t position) +{ + return efl_io_positioner_seek(o, position, EFL_IO_POSITIONER_WHENCE_START) == 0; +} + +#include "interfaces/efl_io_positioner.eo.c" diff --git a/src/lib/efl/interfaces/efl_io_positioner.eo b/src/lib/efl/interfaces/efl_io_positioner.eo new file mode 100644 index 0000000000..1c25b774d7 --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_positioner.eo @@ -0,0 +1,41 @@ +import eina_types; + +enum Efl.Io.Positioner.Whence { + start, [[seek from start of the stream/file]] + current, [[seek from current position]] + end, [[seek from the end of stream/file]] +} + +mixin Efl.Io.Positioner { + [[Generic interface for objects that can change or report position. + + @since 1.19 + ]] + + data: null; + + methods { + seek @virtual_pure { + params { + @in offset: int64; [[offset in byte relative to whence]] + @in whence: Efl.Io.Positioner.Whence; + } + return: Eina.Error; [[0 on succeed, a mapping of errno otherwise]] + } + + @property position { + get @virtual_pure { } + set { + [[Try to set position object, relative to start of file. See @.seek()]] + return: bool; [[$true if could reposition, $false if errors.]] + } + values { + position: uint64; + } + } + } + + events { + position,changed; [[Notifies position changed]] + } +} diff --git a/src/lib/efl/interfaces/efl_io_reader.c b/src/lib/efl/interfaces/efl_io_reader.c new file mode 100644 index 0000000000..d1cf467f0a --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_reader.c @@ -0,0 +1,5 @@ +#define EFL_IO_READER_PROTECTED 1 +#include "config.h" +#include "Efl.h" + +#include "interfaces/efl_io_reader.eo.c" diff --git a/src/lib/efl/interfaces/efl_io_reader.eo b/src/lib/efl/interfaces/efl_io_reader.eo new file mode 100644 index 0000000000..79e78a394f --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_reader.eo @@ -0,0 +1,80 @@ +import eina_types; + +interface Efl.Io.Reader { + [[Generic interface for objects that can read data into a provided memory. + + This interface allows external objects to transparently monitor + for new data and as it to be read into a provided memory slice. + + Calls to @.read may or may not block, that's not up to this + interface to specify. The user can check based on @.eos property + and signal if the stream reached an end, with event + "can_read,changed" or property @.can_read to known whenever a read + would have data to return. + + @since 1.19 + ]] + + methods { + read { + [[Reads data into a pre-allocated buffer. + + This operation will be executed immediately and may or + may not block the caller thread for some time. The + details of blocking behavior is to be defined by the + implementation and may be subject to other parameters + such as non-blocking flags, maximum timeout or even + retry attempts. + + You can understand this method as read(2) libc function. + ]] + params { + @inout rw_slice: Eina.Rw_Slice @nonull; [[provides a pre-allocated memory to be filled up to rw_slice.len. It will be populated and the length will be set to the actually used amount of bytes, which can be smaller than the request.]] + } + return: Eina.Error; [[0 on succeed, a mapping of errno otherwise]] + } + + @property can_read { + [[If $true will notify @.read can be called without blocking or failing.]] + get { } + set @protected { } + values { + can_read: bool; + } + } + + @property eos { + [[If $true will notify end of stream.]] + get { } + set @protected { } + values { + is_eos: bool; + } + } + } + + events { + can_read,changed; [[Notifies can_read property changed. + + If @.can_read is $true there is data to + @.read without blocking/error. If + @.can_read is $false, @.read would either + block or fail. + + Note that usually this event is dispatched + from inside @Efl.Io.Reader.read, thus + before it returns. + ]] + eos; [[Notifies end of stream, when property is marked as true. + + If this is used alongside with an @Efl.Io.Closer, then + it should be emitted before that call. + + It should be emitted only once for an object unless it + implements @Efl.Io.Positioner.seek. + + The property @.can_read should change to $false before + this event is dispatched. + ]] + } +} diff --git a/src/lib/efl/interfaces/efl_io_sizer.c b/src/lib/efl/interfaces/efl_io_sizer.c new file mode 100644 index 0000000000..43b5621254 --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_sizer.c @@ -0,0 +1,10 @@ +#include "config.h" +#include "Efl.h" + +EOLIAN static Eina_Bool +_efl_io_sizer_size_set(Eo *o, void *pd EINA_UNUSED, uint64_t size) +{ + return efl_io_sizer_resize(o, size) == 0; +} + +#include "interfaces/efl_io_sizer.eo.c" diff --git a/src/lib/efl/interfaces/efl_io_sizer.eo b/src/lib/efl/interfaces/efl_io_sizer.eo new file mode 100644 index 0000000000..2b104f1784 --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_sizer.eo @@ -0,0 +1,37 @@ +import eina_types; + +mixin Efl.Io.Sizer { + [[Generic interface for objects that can resize or report size of themselves. + + This interface allows external objects to transparently resize + or report its size. + + @since 1.19 + ]] + + data: null; + + methods { + resize @virtual_pure { + params { + @in size: uint64; + } + return: Eina.Error; [[0 on succeed, a mapping of errno otherwise]] + } + + @property size { + get @virtual_pure { } + set { + [[Try to resize the object, check with get if the value was accepted or not.]] + return: bool; [[$true if could resize, $false if errors.]] + } + values { + size: uint64; + } + } + } + + events { + size,changed; [[Notifies size changed]] + } +} diff --git a/src/lib/efl/interfaces/efl_io_writer.c b/src/lib/efl/interfaces/efl_io_writer.c new file mode 100644 index 0000000000..3170b361c6 --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_writer.c @@ -0,0 +1,5 @@ +#define EFL_IO_WRITER_PROTECTED 1 +#include "config.h" +#include "Efl.h" + +#include "interfaces/efl_io_writer.eo.c" diff --git a/src/lib/efl/interfaces/efl_io_writer.eo b/src/lib/efl/interfaces/efl_io_writer.eo new file mode 100644 index 0000000000..f3a777cb2c --- /dev/null +++ b/src/lib/efl/interfaces/efl_io_writer.eo @@ -0,0 +1,62 @@ +import eina_types; + +interface Efl.Io.Writer { + [[Generic interface for objects that can write data from a provided memory. + + This interface allows external objects to transparently write + data to this object and be notified if more data can be written + or it's topped capacity. + + Calls to @.write() may or may not block, that's not up to this + interface to specify. The user can check with event + "can_write,changed" or property @.can_write to known whenever a write + could push more data. + + @since 1.19 + ]] + + methods { + write { + [[Writes data from a pre-populated buffer. + + This operation will be executed immediately and may or + may not block the caller thread for some time. The + details of blocking behavior is to be defined by the + implementation and may be subject to other parameters + such as non-blocking flags, maximum timeout or even + retry attempts. + + You can understand this method as write(2) libc function. + ]] + params { + @inout slice: Eina.Slice @nonull; [[provides a pre-populated memory to be used up to slice.len. The returned slice will be adapted as length will be set to the actually used amount of bytes, which can be smaller than the request.]] + @out remaining: Eina.Slice @optional; [[convenience to output the remaining parts of slice that was not written. If the full slice was written, this will be a slice of zero-length.]] + } + return: Eina.Error; [[0 on succeed, a mapping of errno otherwise]] + } + + @property can_write { + [[If $true will notify @.write can be called without blocking or failing.]] + get { } + set @protected { } + values { + can_write: bool; + } + } + } + + events { + can_write,changed; [[Notifies can_write property changed. + + If @.can_write is $true there is data to + @.write without blocking/error. If + @.can_write is $false, @.write would + either block or fail. + + Note that usually this event is + dispatched from inside + @Efl.Io.Writer.write, thus before it + returns. + ]] + } +}