diff options
author | Georgii Surkov <37121527+gsurkov@users.noreply.github.com> | 2022-07-22 19:00:25 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-22 19:00:25 +0300 |
commit | 16e598b2c08bf3156370f15fef5b5dd002780f68 (patch) | |
tree | f61ee9800f997973789c7c0c1e461f8203b4a69e /lib | |
parent | ec57dd310a6ad9657c08d143bd82a66da532dc78 (diff) |
[FL-2655, FL-2650] Buffered file streams (#1424)
* Initial buffered file stream implementation
* Fix logical errors
* Fix more logical errors
* Minimally working implementation
* Adapt infrared unit tests for buffered streams
* Increase read buffer size from 512 to 1K
* Correct naming and formatting
* More code improvements
* Allow passing access and open modes for buffered streams
* Implement tests for buffered streams
* Better file and method names
* Add comments and correct formatting
* Use buffered streams in Infrared
* Fix compilation error
Diffstat (limited to 'lib')
-rw-r--r-- | lib/flipper_format/flipper_format.c | 19 | ||||
-rw-r--r-- | lib/flipper_format/flipper_format.h | 23 | ||||
-rw-r--r-- | lib/toolbox/stream/buffered_file_stream.c | 155 | ||||
-rw-r--r-- | lib/toolbox/stream/buffered_file_stream.h | 47 | ||||
-rw-r--r-- | lib/toolbox/stream/stream_cache.c | 71 | ||||
-rw-r--r-- | lib/toolbox/stream/stream_cache.h | 77 |
6 files changed, 392 insertions, 0 deletions
diff --git a/lib/flipper_format/flipper_format.c b/lib/flipper_format/flipper_format.c index 846129cd..62005127 100644 --- a/lib/flipper_format/flipper_format.c +++ b/lib/flipper_format/flipper_format.c @@ -2,6 +2,7 @@ #include <toolbox/stream/stream.h> #include <toolbox/stream/string_stream.h> #include <toolbox/stream/file_stream.h> +#include <toolbox/stream/buffered_file_stream.h> #include "flipper_format.h" #include "flipper_format_i.h" #include "flipper_format_stream.h" @@ -36,11 +37,24 @@ FlipperFormat* flipper_format_file_alloc(Storage* storage) { return flipper_format; } +FlipperFormat* flipper_format_buffered_file_alloc(Storage* storage) { + FlipperFormat* flipper_format = malloc(sizeof(FlipperFormat)); + flipper_format->stream = buffered_file_stream_alloc(storage); + flipper_format->strict_mode = false; + return flipper_format; +} + bool flipper_format_file_open_existing(FlipperFormat* flipper_format, const char* path) { furi_assert(flipper_format); return file_stream_open(flipper_format->stream, path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING); } +bool flipper_format_buffered_file_open_existing(FlipperFormat* flipper_format, const char* path) { + furi_assert(flipper_format); + return buffered_file_stream_open( + flipper_format->stream, path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING); +} + bool flipper_format_file_open_append(FlipperFormat* flipper_format, const char* path) { furi_assert(flipper_format); @@ -87,6 +101,11 @@ bool flipper_format_file_close(FlipperFormat* flipper_format) { return file_stream_close(flipper_format->stream); } +bool flipper_format_buffered_file_close(FlipperFormat* flipper_format) { + furi_assert(flipper_format); + return buffered_file_stream_close(flipper_format->stream); +} + void flipper_format_free(FlipperFormat* flipper_format) { furi_assert(flipper_format); stream_free(flipper_format->stream); diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index e32a5219..3f7b71af 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -116,6 +116,12 @@ FlipperFormat* flipper_format_string_alloc(); FlipperFormat* flipper_format_file_alloc(Storage* storage); /** + * Allocate FlipperFormat as file, buffered read-only mode. + * @return FlipperFormat* pointer to a FlipperFormat instance + */ +FlipperFormat* flipper_format_buffered_file_alloc(Storage* storage); + +/** * Open existing file. * Use only if FlipperFormat allocated as a file. * @param flipper_format Pointer to a FlipperFormat instance @@ -125,6 +131,15 @@ FlipperFormat* flipper_format_file_alloc(Storage* storage); bool flipper_format_file_open_existing(FlipperFormat* flipper_format, const char* path); /** + * Open existing file, read-only with buffered read operations. + * Use only if FlipperFormat allocated as a file. + * @param flipper_format Pointer to a FlipperFormat instance + * @param path File path + * @return True on success + */ +bool flipper_format_buffered_file_open_existing(FlipperFormat* flipper_format, const char* path); + +/** * Open existing file for writing and add values to the end of file. * Use only if FlipperFormat allocated as a file. * @param flipper_format Pointer to a FlipperFormat instance @@ -160,6 +175,14 @@ bool flipper_format_file_open_new(FlipperFormat* flipper_format, const char* pat bool flipper_format_file_close(FlipperFormat* flipper_format); /** + * Closes the file, use only if FlipperFormat allocated as a buffered file. + * @param flipper_format + * @return true + * @return false + */ +bool flipper_format_buffered_file_close(FlipperFormat* flipper_format); + +/** * Free FlipperFormat. * @param flipper_format Pointer to a FlipperFormat instance */ diff --git a/lib/toolbox/stream/buffered_file_stream.c b/lib/toolbox/stream/buffered_file_stream.c new file mode 100644 index 00000000..5db276d3 --- /dev/null +++ b/lib/toolbox/stream/buffered_file_stream.c @@ -0,0 +1,155 @@ +#include "buffered_file_stream.h" + +#include "stream_i.h" +#include "file_stream.h" +#include "stream_cache.h" + +typedef struct { + Stream stream_base; + Stream* file_stream; + StreamCache* cache; +} BufferedFileStream; + +static void buffered_file_stream_free(BufferedFileStream* stream); +static bool buffered_file_stream_eof(BufferedFileStream* stream); +static void buffered_file_stream_clean(BufferedFileStream* stream); +static bool + buffered_file_stream_seek(BufferedFileStream* stream, int32_t offset, StreamOffset offset_type); +static size_t buffered_file_stream_tell(BufferedFileStream* stream); +static size_t buffered_file_stream_size(BufferedFileStream* stream); +static size_t + buffered_file_stream_write(BufferedFileStream* stream, const uint8_t* data, size_t size); +static size_t buffered_file_stream_read(BufferedFileStream* stream, uint8_t* data, size_t size); +static bool buffered_file_stream_delete_and_insert( + BufferedFileStream* stream, + size_t delete_size, + StreamWriteCB write_callback, + const void* ctx); + +const StreamVTable buffered_file_stream_vtable = { + .free = (StreamFreeFn)buffered_file_stream_free, + .eof = (StreamEOFFn)buffered_file_stream_eof, + .clean = (StreamCleanFn)buffered_file_stream_clean, + .seek = (StreamSeekFn)buffered_file_stream_seek, + .tell = (StreamTellFn)buffered_file_stream_tell, + .size = (StreamSizeFn)buffered_file_stream_size, + .write = (StreamWriteFn)buffered_file_stream_write, + .read = (StreamReadFn)buffered_file_stream_read, + .delete_and_insert = (StreamDeleteAndInsertFn)buffered_file_stream_delete_and_insert, +}; + +Stream* buffered_file_stream_alloc(Storage* storage) { + BufferedFileStream* stream = malloc(sizeof(BufferedFileStream)); + + stream->file_stream = file_stream_alloc(storage); + stream->cache = stream_cache_alloc(); + + stream->stream_base.vtable = &buffered_file_stream_vtable; + return (Stream*)stream; +} + +bool buffered_file_stream_open( + Stream* _stream, + const char* path, + FS_AccessMode access_mode, + FS_OpenMode open_mode) { + furi_assert(_stream); + BufferedFileStream* stream = (BufferedFileStream*)_stream; + stream_cache_drop(stream->cache); + furi_check(stream->stream_base.vtable == &buffered_file_stream_vtable); + return file_stream_open(stream->file_stream, path, access_mode, open_mode); +} + +bool buffered_file_stream_close(Stream* _stream) { + furi_assert(_stream); + BufferedFileStream* stream = (BufferedFileStream*)_stream; + furi_check(stream->stream_base.vtable == &buffered_file_stream_vtable); + return file_stream_close(stream->file_stream); +} + +FS_Error buffered_file_stream_get_error(Stream* _stream) { + furi_assert(_stream); + BufferedFileStream* stream = (BufferedFileStream*)_stream; + furi_check(stream->stream_base.vtable == &buffered_file_stream_vtable); + return file_stream_get_error(stream->file_stream); +} + +static void buffered_file_stream_free(BufferedFileStream* stream) { + furi_assert(stream); + stream_free(stream->file_stream); + stream_cache_free(stream->cache); + free(stream); +} + +static bool buffered_file_stream_eof(BufferedFileStream* stream) { + return stream_cache_at_end(stream->cache) && stream_eof(stream->file_stream); +} + +static void buffered_file_stream_clean(BufferedFileStream* stream) { + stream_cache_drop(stream->cache); + stream_clean(stream->file_stream); +} + +static bool buffered_file_stream_seek( + BufferedFileStream* stream, + int32_t offset, + StreamOffset offset_type) { + bool success = false; + int32_t new_offset = offset; + + if(offset_type == StreamOffsetFromCurrent) { + new_offset -= stream_cache_seek(stream->cache, offset); + if(new_offset < 0) { + new_offset -= (int32_t)stream_cache_size(stream->cache); + } + } + + if((new_offset != 0) || (offset_type != StreamOffsetFromCurrent)) { + stream_cache_drop(stream->cache); + success = stream_seek(stream->file_stream, new_offset, offset_type); + } else { + success = true; + } + + return success; +} + +static size_t buffered_file_stream_tell(BufferedFileStream* stream) { + return stream_tell(stream->file_stream) + stream_cache_pos(stream->cache) - + stream_cache_size(stream->cache); +} + +static size_t buffered_file_stream_size(BufferedFileStream* stream) { + return stream_cache_size(stream->cache) + stream_size(stream->file_stream); +} + +static size_t + buffered_file_stream_write(BufferedFileStream* stream, const uint8_t* data, size_t size) { + stream_cache_drop(stream->cache); + return stream_write(stream->file_stream, data, size); +} + +static size_t buffered_file_stream_read(BufferedFileStream* stream, uint8_t* data, size_t size) { + size_t need_to_read = size; + + while(need_to_read) { + need_to_read -= + stream_cache_read(stream->cache, data + (size - need_to_read), need_to_read); + if(need_to_read) { + if(!stream_cache_fill(stream->cache, stream->file_stream)) { + break; + } + } + } + + return size - need_to_read; +} + +static bool buffered_file_stream_delete_and_insert( + BufferedFileStream* stream, + size_t delete_size, + StreamWriteCB write_callback, + const void* ctx) { + stream_cache_drop(stream->cache); + return stream_delete_and_insert(stream->file_stream, delete_size, write_callback, ctx); +} diff --git a/lib/toolbox/stream/buffered_file_stream.h b/lib/toolbox/stream/buffered_file_stream.h new file mode 100644 index 00000000..e6ad7209 --- /dev/null +++ b/lib/toolbox/stream/buffered_file_stream.h @@ -0,0 +1,47 @@ +#pragma once +#include <stdlib.h> +#include <storage/storage.h> +#include "stream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Allocate a file stream with buffered read operations + * @return Stream* + */ +Stream* buffered_file_stream_alloc(Storage* storage); + +/** + * Opens an existing file or creates a new one. + * @param stream pointer to file stream object. + * @param path path to file + * @param access_mode access mode from FS_AccessMode + * @param open_mode open mode from FS_OpenMode + * @return success flag. You need to close the file even if the open operation failed. + */ +bool buffered_file_stream_open( + Stream* stream, + const char* path, + FS_AccessMode access_mode, + FS_OpenMode open_mode); + +/** + * Closes the file. + * @param stream + * @return true + * @return false + */ +bool buffered_file_stream_close(Stream* stream); + +/** + * Retrieves the error id from the file object + * @param stream pointer to stream object. + * @return FS_Error error id + */ +FS_Error buffered_file_stream_get_error(Stream* stream); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/stream/stream_cache.c b/lib/toolbox/stream/stream_cache.c new file mode 100644 index 00000000..164ac466 --- /dev/null +++ b/lib/toolbox/stream/stream_cache.c @@ -0,0 +1,71 @@ +#include "stream_cache.h" + +#define STREAM_CACHE_MAX_SIZE 1024U + +struct StreamCache { + uint8_t data[STREAM_CACHE_MAX_SIZE]; + size_t data_size; + size_t position; +}; + +StreamCache* stream_cache_alloc() { + StreamCache* cache = malloc(sizeof(StreamCache)); + cache->data_size = 0; + cache->position = 0; + return cache; +} +void stream_cache_free(StreamCache* cache) { + furi_assert(cache); + cache->data_size = 0; + cache->position = 0; + free(cache); +} + +void stream_cache_drop(StreamCache* cache) { + cache->data_size = 0; + cache->position = 0; +} + +bool stream_cache_at_end(StreamCache* cache) { + furi_assert(cache->data_size >= cache->position); + return cache->data_size == cache->position; +} + +size_t stream_cache_size(StreamCache* cache) { + return cache->data_size; +} + +size_t stream_cache_pos(StreamCache* cache) { + return cache->position; +} + +size_t stream_cache_fill(StreamCache* cache, Stream* stream) { + const size_t size_read = stream_read(stream, cache->data, STREAM_CACHE_MAX_SIZE); + cache->data_size = size_read; + cache->position = 0; + return size_read; +} + +size_t stream_cache_read(StreamCache* cache, uint8_t* data, size_t size) { + furi_assert(cache->data_size >= cache->position); + const size_t size_read = MIN(size, cache->data_size - cache->position); + if(size_read > 0) { + memcpy(data, cache->data + cache->position, size_read); + cache->position += size_read; + } + return size_read; +} + +int32_t stream_cache_seek(StreamCache* cache, int32_t offset) { + furi_assert(cache->data_size >= cache->position); + int32_t actual_offset = 0; + + if(offset > 0) { + actual_offset = MIN(cache->data_size - cache->position, (size_t)offset); + } else if(offset < 0) { + actual_offset = -MIN(cache->position, (size_t)abs(offset)); + } + + cache->position += actual_offset; + return actual_offset; +} diff --git a/lib/toolbox/stream/stream_cache.h b/lib/toolbox/stream/stream_cache.h new file mode 100644 index 00000000..20c18d80 --- /dev/null +++ b/lib/toolbox/stream/stream_cache.h @@ -0,0 +1,77 @@ +#pragma once + +#include "stream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct StreamCache StreamCache; + +/** + * Allocate stream cache. + * @return StreamCache* pointer to a StreamCache instance + */ +StreamCache* stream_cache_alloc(); + +/** + * Free stream cache. + * @param cache Pointer to a StreamCache instance + */ +void stream_cache_free(StreamCache* cache); + +/** + * Drop the cache contents and set it to initial state. + * @param cache Pointer to a StreamCache instance + */ +void stream_cache_drop(StreamCache* cache); + +/** + * Determine if the internal cursor is at end the end of cached data. + * @param cache Pointer to a StreamCache instance + * @return True if cursor is at end, otherwise false. + */ +bool stream_cache_at_end(StreamCache* cache); + +/** + * Get the current size of cached data. + * @param cache Pointer to a StreamCache instance + * @return Size of cached data. + */ +size_t stream_cache_size(StreamCache* cache); + +/** + * Get the internal cursor position. + * @param cache Pointer to a StreamCache instance + * @return Cursor position inside the cache. + */ +size_t stream_cache_pos(StreamCache* cache); + +/** + * Load the cache with new data from a stream. + * @param cache Pointer to a StreamCache instance + * @param stream Pointer to a Stream instance + * @return Size of newly cached data. + */ +size_t stream_cache_fill(StreamCache* cache, Stream* stream); + +/** + * Read cached data and advance the internal cursor. + * @param cache Pointer to a StreamCache instance. + * @param data Pointer to a data buffer. Must be initialized. + * @param size Maximum size in bytes to read from the cache. + * @return Actual size that was read. + */ +size_t stream_cache_read(StreamCache* cache, uint8_t* data, size_t size); + +/** + * Move the internal cursor relatively to its current position. + * @param cache Pointer to a StreamCache instance. + * @param offset Cursor offset. + * @return Actual cursor offset. Equal to offset parameter on hit. + */ +int32_t stream_cache_seek(StreamCache* cache, int32_t offset); + +#ifdef __cplusplus +} +#endif |