diff --git a/applications/bt/bt_service/bt.c b/applications/bt/bt_service/bt.c index b85139fd..2ea1599b 100755 --- a/applications/bt/bt_service/bt.c +++ b/applications/bt/bt_service/bt.c @@ -84,7 +84,7 @@ static void bt_on_data_received_callback(uint8_t* data, uint16_t size, void* con furi_assert(context); Bt* bt = context; - size_t bytes_processed = rpc_feed_bytes(bt->rpc_session, data, size, 1000); + size_t bytes_processed = rpc_session_feed(bt->rpc_session, data, size, 1000); if(bytes_processed != size) { FURI_LOG_E(BT_SERVICE_TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size); } @@ -129,8 +129,9 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) { furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); // Open RPC session FURI_LOG_I(BT_SERVICE_TAG, "Open RPC connection"); - bt->rpc_session = rpc_open_session(bt->rpc); - rpc_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback, bt); + bt->rpc_session = rpc_session_open(bt->rpc); + rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback); + rpc_session_set_context(bt->rpc_session, bt); furi_hal_bt_set_data_event_callbacks( bt_on_data_received_callback, bt_on_data_sent_callback, bt); // Update battery level @@ -142,7 +143,7 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) { } else if(event.type == BleEventTypeDisconnected) { FURI_LOG_I(BT_SERVICE_TAG, "Close RPC connection"); if(bt->rpc_session) { - rpc_close_session(bt->rpc_session); + rpc_session_close(bt->rpc_session); bt->rpc_session = NULL; } } else if(event.type == BleEventTypeStartAdvertising) { diff --git a/applications/cli/cli.c b/applications/cli/cli.c index d9fde866..7d60f7d1 100644 --- a/applications/cli/cli.c +++ b/applications/cli/cli.c @@ -263,7 +263,7 @@ static void cli_handle_autocomplete(Cli* cli) { cli->cursor_position = string_size(cli->line); } // Cleanup - string_clean(common); + string_clear(common); // Show prompt cli_prompt(cli); } diff --git a/applications/rpc/rpc.c b/applications/rpc/rpc.c index adb23e78..9f8913d2 100644 --- a/applications/rpc/rpc.c +++ b/applications/rpc/rpc.c @@ -1,22 +1,20 @@ -#include "cmsis_os.h" -#include "cmsis_os2.h" -#include "flipper.pb.h" -#include "furi-hal-delay.h" -#include "furi/check.h" -#include "furi/log.h" -#include -#include "pb.h" -#include "pb_decode.h" -#include "pb_encode.h" -#include "portmacro.h" -#include "status.pb.h" -#include "storage.pb.h" +#include "rpc_i.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include #include +#include #include -#include "rpc_i.h" #define RPC_TAG "RPC" @@ -51,10 +49,11 @@ static RpcSystemCallbacks rpc_systems[] = { struct RpcSession { RpcSendBytesCallback send_bytes_callback; - void* send_bytes_context; - osMutexId_t send_bytes_mutex; + RpcSessionClosedCallback closed_callback; + void* context; + osMutexId_t callbacks_mutex; Rpc* rpc; - bool terminate_session; + bool terminate; void** system_contexts; }; @@ -70,6 +69,20 @@ struct Rpc { static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg); +static void rpc_close_session_process(const PB_Main* msg_request, void* context) { + furi_assert(msg_request); + furi_assert(context); + + Rpc* rpc = context; + rpc_send_and_release_empty(rpc, msg_request->command_id, PB_CommandStatus_OK); + + osMutexAcquire(rpc->session.callbacks_mutex, osWaitForever); + if(rpc->session.closed_callback) { + rpc->session.closed_callback(rpc->session.context); + } + osMutexRelease(rpc->session.callbacks_mutex); +} + static size_t rpc_sprintf_msg_file( string_t str, const char* prefix, @@ -105,6 +118,31 @@ static size_t rpc_sprintf_msg_file( return cnt; } +void rpc_print_data(const char* prefix, uint8_t* buffer, size_t size) { + string_t str; + string_init(str); + string_reserve(str, 100 + size * 5); + + string_cat_printf(str, "\r\n%s DEC(%d): {", prefix, size); + for(int i = 0; i < size; ++i) { + string_cat_printf(str, "%d, ", buffer[i]); + } + string_cat_printf(str, "}\r\n"); + + printf("%s", string_get_cstr(str)); + string_clean(str); + string_reserve(str, 100 + size * 3); + + string_cat_printf(str, "%s HEX(%d): {", prefix, size); + for(int i = 0; i < size; ++i) { + string_cat_printf(str, "%02X", buffer[i]); + } + string_cat_printf(str, "}\r\n\r\n"); + + printf("%s", string_get_cstr(str)); + string_clear(str); +} + void rpc_print_message(const PB_Main* message) { string_t str; string_init(str); @@ -120,6 +158,9 @@ void rpc_print_message(const PB_Main* message) { /* not implemented yet */ string_cat_printf(str, "\tNOT_IMPLEMENTED (%d) {\r\n", message->which_content); break; + case PB_Main_stop_session_tag: + string_cat_printf(str, "\tstop_session {\r\n"); + break; case PB_Main_app_start_tag: { string_cat_printf(str, "\tapp_start {\r\n"); const char* name = message->content.app_start.name; @@ -242,7 +283,7 @@ static Rpc* rpc_alloc(void) { return rpc; } -RpcSession* rpc_open_session(Rpc* rpc) { +RpcSession* rpc_session_open(Rpc* rpc) { furi_assert(rpc); bool result = false; furi_check(osMutexAcquire(rpc->busy_mutex, osWaitForever) == osOK); @@ -256,41 +297,94 @@ RpcSession* rpc_open_session(Rpc* rpc) { if(result) { RpcSession* session = &rpc->session; - session->send_bytes_mutex = osMutexNew(NULL); + session->callbacks_mutex = osMutexNew(NULL); session->rpc = rpc; - session->terminate_session = false; + session->terminate = false; + xStreamBufferReset(rpc->stream); + session->system_contexts = furi_alloc(COUNT_OF(rpc_systems) * sizeof(void*)); for(int i = 0; i < COUNT_OF(rpc_systems); ++i) { session->system_contexts[i] = rpc_systems[i].alloc(rpc); } + + RpcHandler rpc_handler = { + .message_handler = rpc_close_session_process, + .decode_submessage = NULL, + .context = rpc, + }; + rpc_add_handler(rpc, PB_Main_stop_session_tag, &rpc_handler); + FURI_LOG_D(RPC_TAG, "Session started\r\n"); } return result ? &rpc->session : NULL; /* support 1 open session for now */ } -void rpc_close_session(RpcSession* session) { +void rpc_session_close(RpcSession* session) { furi_assert(session); furi_assert(session->rpc); furi_assert(session->rpc->busy); - rpc_set_send_bytes_callback(session, NULL, NULL); + rpc_session_set_send_bytes_callback(session, NULL); + rpc_session_set_close_callback(session, NULL); osEventFlagsSet(session->rpc->events, RPC_EVENT_DISCONNECT); } -void rpc_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback, void* context) { +static void rpc_free_session(RpcSession* session) { + furi_assert(session); + + for(int i = 0; i < COUNT_OF(rpc_systems); ++i) { + if(rpc_systems[i].free) { + rpc_systems[i].free(session->system_contexts[i]); + } + } + free(session->system_contexts); + osMutexDelete(session->callbacks_mutex); + RpcHandlerDict_clean(session->rpc->handlers); + + session->context = NULL; + session->closed_callback = NULL; + session->send_bytes_callback = NULL; +} + +void rpc_session_set_context(RpcSession* session, void* context) { furi_assert(session); furi_assert(session->rpc); furi_assert(session->rpc->busy); - osMutexAcquire(session->send_bytes_mutex, osWaitForever); - session->send_bytes_callback = callback; - session->send_bytes_context = context; - osMutexRelease(session->send_bytes_mutex); + osMutexAcquire(session->callbacks_mutex, osWaitForever); + session->context = context; + osMutexRelease(session->callbacks_mutex); } +void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallback callback) { + furi_assert(session); + furi_assert(session->rpc); + furi_assert(session->rpc->busy); + + osMutexAcquire(session->callbacks_mutex, osWaitForever); + session->closed_callback = callback; + osMutexRelease(session->callbacks_mutex); +} + +void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback) { + furi_assert(session); + furi_assert(session->rpc); + furi_assert(session->rpc->busy); + + osMutexAcquire(session->callbacks_mutex, osWaitForever); + session->send_bytes_callback = callback; + osMutexRelease(session->callbacks_mutex); +} + +/* Doesn't forbid using rpc_feed_bytes() after session close - it's safe. + * Because any bytes received in buffer will be flushed before next session. + * If bytes get into stream buffer before it's get epmtified and this + * command is gets processed - it's safe either. But case of it is quite + * odd: client sends close request and sends command after. + */ size_t - rpc_feed_bytes(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) { + rpc_session_feed(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout) { furi_assert(session); Rpc* rpc = session->rpc; furi_assert(rpc->busy); @@ -306,6 +400,8 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { uint32_t flags = 0; size_t bytes_received = 0; + furi_assert(istream->bytes_left); + while(1) { bytes_received += xStreamBufferReceive(rpc->stream, buf + bytes_received, count - bytes_received, 0); @@ -315,7 +411,9 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { flags = osEventFlagsWait(rpc->events, RPC_EVENTS_ALL, 0, osWaitForever); if(flags & RPC_EVENT_DISCONNECT) { if(xStreamBufferIsEmpty(rpc->stream)) { - rpc->session.terminate_session = true; + rpc->session.terminate = true; + istream->bytes_left = 0; + bytes_received = 0; break; } else { /* Save disconnect flag and continue reading buffer */ @@ -325,12 +423,16 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { } } +#if DEBUG_PRINT + rpc_print_data("INPUT", buf, bytes_received); +#endif + return (count == bytes_received); } -void rpc_encode_and_send(Rpc* rpc, PB_Main* main_message) { +void rpc_send_and_release(Rpc* rpc, PB_Main* message) { furi_assert(rpc); - furi_assert(main_message); + furi_assert(message); RpcSession* session = &rpc->session; pb_ostream_t ostream = PB_OSTREAM_SIZING; @@ -339,47 +441,26 @@ void rpc_encode_and_send(Rpc* rpc, PB_Main* main_message) { rpc_print_message(main_message); #endif - bool result = pb_encode_ex(&ostream, &PB_Main_msg, main_message, PB_ENCODE_DELIMITED); + bool result = pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED); furi_check(result && ostream.bytes_written); uint8_t* buffer = furi_alloc(ostream.bytes_written); ostream = pb_ostream_from_buffer(buffer, ostream.bytes_written); - pb_encode_ex(&ostream, &PB_Main_msg, main_message, PB_ENCODE_DELIMITED); + pb_encode_ex(&ostream, &PB_Main_msg, message, PB_ENCODE_DELIMITED); - { #if DEBUG_PRINT - string_t str; - string_init(str); - string_reserve(str, 100 + ostream.bytes_written * 5); + rpc_print_data("OUTPUT", buffer, ostream.bytes_written); +#endif - string_cat_printf(str, "\r\nREPONSE DEC(%d): {", ostream.bytes_written); - for(int i = 0; i < ostream.bytes_written; ++i) { - string_cat_printf(str, "%d, ", buffer[i]); - } - string_cat_printf(str, "}\r\n"); - - printf("%s", string_get_cstr(str)); - string_clean(str); - string_reserve(str, 100 + ostream.bytes_written * 3); - - string_cat_printf(str, "REPONSE HEX(%d): {", ostream.bytes_written); - for(int i = 0; i < ostream.bytes_written; ++i) { - string_cat_printf(str, "%02X", buffer[i]); - } - string_cat_printf(str, "}\r\n\r\n"); - - printf("%s", string_get_cstr(str)); -#endif // DEBUG_PRINT - - osMutexAcquire(session->send_bytes_mutex, osWaitForever); - if(session->send_bytes_callback) { - session->send_bytes_callback( - session->send_bytes_context, buffer, ostream.bytes_written); - } - osMutexRelease(session->send_bytes_mutex); + osMutexAcquire(session->callbacks_mutex, osWaitForever); + if(session->send_bytes_callback) { + session->send_bytes_callback(session->context, buffer, ostream.bytes_written); } + osMutexRelease(session->callbacks_mutex); + free(buffer); + pb_release(&PB_Main_msg, message); } static bool content_callback(pb_istream_t* stream, const pb_field_t* field, void** arg) { @@ -399,12 +480,17 @@ int32_t rpc_srv(void* p) { Rpc* rpc = rpc_alloc(); furi_record_create("rpc", rpc); + Cli* cli = furi_record_open("cli"); + + cli_add_command( + cli, "start_rpc_session", CliCommandFlagDefault, rpc_cli_command_start_session, rpc); + while(1) { pb_istream_t istream = { .callback = rpc_pb_stream_read, .state = rpc, .errmsg = NULL, - .bytes_left = 0x7FFFFFFF, + .bytes_left = 1024, /* max incoming message size */ }; if(pb_decode_ex(&istream, &PB_Main_msg, rpc->decoded_message, PB_DECODE_DELIMITED)) { @@ -417,35 +503,25 @@ int32_t rpc_srv(void* p) { if(handler && handler->message_handler) { handler->message_handler(rpc->decoded_message, handler->context); - } else if(!handler) { + } else if(!handler && !rpc->session.terminate) { FURI_LOG_E( - RPC_TAG, - "Unhandled message, tag: %d\r\n", - rpc->decoded_message->which_content); + RPC_TAG, "Unhandled message, tag: %d", rpc->decoded_message->which_content); } - pb_release(&PB_Main_msg, rpc->decoded_message); } else { - pb_release(&PB_Main_msg, rpc->decoded_message); - RpcSession* session = &rpc->session; - if(session->terminate_session) { - session->terminate_session = false; - osEventFlagsClear(rpc->events, RPC_EVENTS_ALL); - FURI_LOG_D(RPC_TAG, "Session terminated\r\n"); - for(int i = 0; i < COUNT_OF(rpc_systems); ++i) { - if(rpc_systems[i].free) { - rpc_systems[i].free(session->system_contexts[i]); - } - } - free(session->system_contexts); - osMutexDelete(session->send_bytes_mutex); - RpcHandlerDict_clean(rpc->handlers); - rpc->busy = false; - } else { - xStreamBufferReset(rpc->stream); - FURI_LOG_E( - RPC_TAG, "Decode failed, error: \'%.128s\'\r\n", PB_GET_ERROR(&istream)); + xStreamBufferReset(rpc->stream); + if(!rpc->session.terminate) { + FURI_LOG_E(RPC_TAG, "Decode failed, error: \'%.128s\'", PB_GET_ERROR(&istream)); } } + + pb_release(&PB_Main_msg, rpc->decoded_message); + + if(rpc->session.terminate) { + FURI_LOG_D(RPC_TAG, "Session terminated"); + osEventFlagsClear(rpc->events, RPC_EVENTS_ALL); + rpc_free_session(&rpc->session); + rpc->busy = false; + } } return 0; } @@ -456,13 +532,13 @@ void rpc_add_handler(Rpc* rpc, pb_size_t message_tag, RpcHandler* handler) { RpcHandlerDict_set_at(rpc->handlers, message_tag, *handler); } -void rpc_encode_and_send_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status) { +void rpc_send_and_release_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status) { PB_Main message = { .command_id = command_id, .command_status = status, .has_next = false, .which_content = PB_Main_empty_tag, }; - rpc_encode_and_send(rpc, &message); + rpc_send_and_release(rpc, &message); pb_release(&PB_Main_msg, &message); } diff --git a/applications/rpc/rpc.h b/applications/rpc/rpc.h index 91557606..29e37773 100644 --- a/applications/rpc/rpc.h +++ b/applications/rpc/rpc.h @@ -1,16 +1,79 @@ #pragma once #include #include +#include #include "cmsis_os.h" +/** Rpc interface. Used for opening session only. */ typedef struct Rpc Rpc; +/** Rpc session interface */ typedef struct RpcSession RpcSession; +/** Callback to send to client any data (e.g. response to command) */ typedef void (*RpcSendBytesCallback)(void* context, uint8_t* bytes, size_t bytes_len); +/** Callback to notify transport layer that close_session command + * is received. Any other actions lays on transport layer. + * No destruction or session close preformed. */ +typedef void (*RpcSessionClosedCallback)(void* context); -RpcSession* rpc_open_session(Rpc* rpc); -void rpc_close_session(RpcSession* session); -/* WARN: can't call RPC API within RpcSendBytesCallback */ -void rpc_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback, void* context); -size_t - rpc_feed_bytes(RpcSession* session, uint8_t* encoded_bytes, size_t size, TickType_t timeout); +/** Open RPC session + * + * USAGE: + * 1) rpc_session_open(); + * 2) rpc_session_set_context(); + * 3) rpc_session_set_send_bytes_callback(); + * 4) rpc_session_set_close_callback(); + * 5) while(1) { + * rpc_session_feed(); + * } + * 6) rpc_session_close(); + * + * + * @param rpc instance + * @return pointer to RpcSession descriptor, or + * NULL if RPC is busy and can't open session now + */ +RpcSession* rpc_session_open(Rpc* rpc); + +/** Close RPC session + * It is guaranteed that no callbacks will be called + * as soon as session is closed. So no need in setting + * callbacks to NULL after session close. + * + * @param session pointer to RpcSession descriptor + */ +void rpc_session_close(RpcSession* session); + +/** Set session context for callbacks to pass + * + * @param session pointer to RpcSession descriptor + * @param context context to pass to callbacks + */ +void rpc_session_set_context(RpcSession* session, void* context); + +/** Set callback to send bytes to client + * WARN: It's forbidden to call RPC API within RpcSendBytesCallback + * + * @param session pointer to RpcSession descriptor + * @param callback callback to send bytes to client (can be NULL) + */ +void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback); + +/** Set callback to be called when RPC command to close session is received + * WARN: It's forbidden to call RPC API within RpcSessionClosedCallback + * + * @param session pointer to RpcSession descriptor + * @param callback callback to inform about RPC close session command (can be NULL) + */ +void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallback callback); + +/** Give bytes to RPC service to decode them and perform command + * + * @param session pointer to RpcSession descriptor + * @param buffer buffer to provide to RPC service + * @param size size of buffer + * @param timeout max timeout to wait till all buffer will be consumed + * + * @return actually consumed bytes + */ +size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, TickType_t timeout); diff --git a/applications/rpc/rpc_app.c b/applications/rpc/rpc_app.c index db4e5567..d2c0cdc0 100644 --- a/applications/rpc/rpc_app.c +++ b/applications/rpc/rpc_app.c @@ -34,7 +34,7 @@ void rpc_system_app_start_process(const PB_Main* request, void* context) { furi_record_close("loader"); - rpc_encode_and_send_empty(rpc, request->command_id, result); + rpc_send_and_release_empty(rpc, request->command_id, result); } void rpc_system_app_lock_status_process(const PB_Main* request, void* context) { @@ -56,7 +56,8 @@ void rpc_system_app_lock_status_process(const PB_Main* request, void* context) { furi_record_close("loader"); - rpc_encode_and_send(rpc, &response); + rpc_send_and_release(rpc, &response); + pb_release(&PB_Main_msg, &response); } void* rpc_system_app_alloc(Rpc* rpc) { diff --git a/applications/rpc/rpc_cli.c b/applications/rpc/rpc_cli.c new file mode 100644 index 00000000..853e9dd3 --- /dev/null +++ b/applications/rpc/rpc_cli.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +typedef struct { + Cli* cli; + bool session_close_request; +} CliRpc; + +#define CLI_READ_BUFFER_SIZE 100 + +static void rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t bytes_len) { + furi_assert(context); + furi_assert(bytes); + furi_assert(bytes_len); + CliRpc* cli_rpc = context; + + cli_write(cli_rpc->cli, bytes, bytes_len); +} + +static void rpc_session_close_callback(void* context) { + furi_assert(context); + CliRpc* cli_rpc = context; + + cli_rpc->session_close_request = true; +} + +void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) { + Rpc* rpc = context; + + RpcSession* rpc_session = rpc_session_open(rpc); + if(rpc_session == NULL) { + printf("Another session is in progress\r\n"); + return; + } + + CliRpc cli_rpc = {.cli = cli, .session_close_request = false}; + rpc_session_set_context(rpc_session, &cli_rpc); + rpc_session_set_send_bytes_callback(rpc_session, rpc_send_bytes_callback); + rpc_session_set_close_callback(rpc_session, rpc_session_close_callback); + + uint8_t* buffer = furi_alloc(CLI_READ_BUFFER_SIZE); + size_t size_received = 0; + + while(1) { + size_received = furi_hal_vcp_rx_with_timeout(buffer, CLI_READ_BUFFER_SIZE, 50); + if(!furi_hal_vcp_is_connected() || cli_rpc.session_close_request) { + break; + } + + if(size_received) { + rpc_session_feed(rpc_session, buffer, size_received, 3000); + } + } + + rpc_session_close(rpc_session); + free(buffer); +} diff --git a/applications/rpc/rpc_i.h b/applications/rpc/rpc_i.h index 5bcb9d9b..1bcf7c86 100644 --- a/applications/rpc/rpc_i.h +++ b/applications/rpc/rpc_i.h @@ -1,9 +1,10 @@ #pragma once #include "rpc.h" -#include "pb.h" -#include "pb_decode.h" -#include "pb_encode.h" -#include "flipper.pb.h" +#include +#include +#include +#include +#include typedef void* (*RpcSystemAlloc)(Rpc*); typedef void (*RpcSystemFree)(void*); @@ -15,8 +16,8 @@ typedef struct { void* context; } RpcHandler; -void rpc_encode_and_send(Rpc* rpc, PB_Main* main_message); -void rpc_encode_and_send_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status); +void rpc_send_and_release(Rpc* rpc, PB_Main* main_message); +void rpc_send_and_release_empty(Rpc* rpc, uint32_t command_id, PB_CommandStatus status); void rpc_add_handler(Rpc* rpc, pb_size_t message_tag, RpcHandler* handler); void* rpc_system_status_alloc(Rpc* rpc); @@ -25,3 +26,4 @@ void rpc_system_storage_free(void* ctx); void* rpc_system_app_alloc(Rpc* rpc); void rpc_print_message(const PB_Main* message); +void rpc_cli_command_start_session(Cli* cli, string_t args, void* context); diff --git a/applications/rpc/rpc_status.c b/applications/rpc/rpc_status.c index 524675d9..e8f4e273 100644 --- a/applications/rpc/rpc_status.c +++ b/applications/rpc/rpc_status.c @@ -9,7 +9,8 @@ void rpc_system_status_ping_process(const PB_Main* msg_request, void* context) { msg_response.command_id = msg_request->command_id; msg_response.which_content = PB_Main_ping_response_tag; - rpc_encode_and_send(context, &msg_response); + rpc_send_and_release(context, &msg_response); + pb_release(&PB_Main_msg, &msg_response); } void* rpc_system_status_alloc(Rpc* rpc) { diff --git a/applications/rpc/rpc_storage.c b/applications/rpc/rpc_storage.c index 96842cba..f8e0a97e 100644 --- a/applications/rpc/rpc_storage.c +++ b/applications/rpc/rpc_storage.c @@ -35,7 +35,7 @@ static void rpc_system_storage_reset_state(RpcStorageSystem* rpc_storage, bool s if(rpc_storage->state != RpcStorageStateIdle) { if(send_error) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, rpc_storage->current_command_id, PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED); @@ -96,6 +96,31 @@ static PB_CommandStatus rpc_system_storage_get_file_error(File* file) { return rpc_system_storage_get_error(storage_file_get_error(file)); } +static void rpc_system_storage_list_root(const PB_Main* request, void* context) { + RpcStorageSystem* rpc_storage = context; + const char* hard_coded_dirs[] = {"any", "int", "ext"}; + + PB_Main response = { + .has_next = false, + .command_id = request->command_id, + .command_status = PB_CommandStatus_OK, + .which_content = PB_Main_storage_list_response_tag, + }; + furi_assert(COUNT_OF(hard_coded_dirs) < COUNT_OF(response.content.storage_list_response.file)); + + for(int i = 0; i < COUNT_OF(hard_coded_dirs); ++i) { + ++response.content.storage_list_response.file_count; + response.content.storage_list_response.file[i].data = NULL; + response.content.storage_list_response.file[i].size = 0; + response.content.storage_list_response.file[i].type = PB_Storage_File_FileType_DIR; + char* str = furi_alloc(strlen(hard_coded_dirs[i]) + 1); + strcpy(str, hard_coded_dirs[i]); + response.content.storage_list_response.file[i].name = str; + } + + rpc_send_and_release(rpc_storage->rpc, &response); +} + static void rpc_system_storage_list_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); @@ -104,6 +129,11 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex RpcStorageSystem* rpc_storage = context; rpc_system_storage_reset_state(rpc_storage, true); + if(!strcmp(request->content.storage_list_request.path, "/")) { + rpc_system_storage_list_root(request, context); + return; + } + Storage* fs_api = furi_record_open("storage"); File* dir = storage_file_alloc(fs_api); @@ -132,8 +162,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex if(i == COUNT_OF(list->file)) { list->file_count = i; response.has_next = true; - rpc_encode_and_send(rpc_storage->rpc, &response); - pb_release(&PB_Main_msg, &response); + rpc_send_and_release(rpc_storage->rpc, &response); i = 0; } list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? PB_Storage_File_FileType_DIR : @@ -150,8 +179,7 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex } response.has_next = false; - rpc_encode_and_send(rpc_storage->rpc, &response); - pb_release(&PB_Main_msg, &response); + rpc_send_and_release(rpc_storage->rpc, &response); storage_dir_close(dir); storage_file_free(dir); @@ -168,9 +196,6 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex /* use same message memory to send reponse */ PB_Main* response = furi_alloc(sizeof(PB_Main)); - response->command_id = request->command_id; - response->which_content = PB_Main_storage_read_response_tag; - response->command_status = PB_CommandStatus_OK; const char* path = request->content.storage_read_request.path; Storage* fs_api = furi_record_open("storage"); File* file = storage_file_alloc(fs_api); @@ -178,10 +203,13 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) { size_t size_left = storage_file_size(file); - response->content.storage_read_response.has_file = true; - response->content.storage_read_response.file.data = - furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MIN(size_left, MAX_DATA_SIZE))); do { + response->command_id = request->command_id; + response->which_content = PB_Main_storage_read_response_tag; + response->command_status = PB_CommandStatus_OK; + response->content.storage_read_response.has_file = true; + response->content.storage_read_response.file.data = + furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MIN(size_left, MAX_DATA_SIZE))); uint8_t* buffer = response->content.storage_read_response.file.data->bytes; uint16_t* read_size_msg = &response->content.storage_read_response.file.data->size; @@ -192,21 +220,19 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex if(result) { response->has_next = (size_left > 0); - rpc_encode_and_send(rpc_storage->rpc, response); - // no pb_release(...); + rpc_send_and_release(rpc_storage->rpc, response); } } while((size_left != 0) && result); if(!result) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); } } else { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); } - pb_release(&PB_Main_msg, response); free(response); storage_file_close(file); storage_file_free(file); @@ -245,14 +271,14 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte result = (written_size == buffer_size); if(result && !request->has_next) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, rpc_storage->current_command_id, PB_CommandStatus_OK); rpc_system_storage_reset_state(rpc_storage, false); } } if(!result) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, rpc_storage->current_command_id, rpc_system_storage_get_file_error(file)); @@ -260,23 +286,57 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte } } +static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path) { + FileInfo fileinfo; + bool is_dir_is_empty = false; + FS_Error error = storage_common_stat(fs_api, path, &fileinfo); + if((error == FSE_OK) && (fileinfo.flags & FSF_DIRECTORY)) { + File* dir = storage_file_alloc(fs_api); + if(storage_dir_open(dir, path)) { + char* name = furi_alloc(MAX_NAME_LENGTH); + is_dir_is_empty = !storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH); + free(name); + } + storage_dir_close(dir); + storage_file_free(dir); + } + + return is_dir_is_empty; +} + static void rpc_system_storage_delete_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_storage_delete_request_tag); furi_assert(context); RpcStorageSystem* rpc_storage = context; - PB_CommandStatus status; + PB_CommandStatus status = PB_CommandStatus_ERROR; rpc_system_storage_reset_state(rpc_storage, true); Storage* fs_api = furi_record_open("storage"); - char* path = request->content.storage_mkdir_request.path; - if(path) { - FS_Error error = storage_common_remove(fs_api, path); - status = rpc_system_storage_get_error(error); - } else { + + char* path = request->content.storage_delete_request.path; + if(!path) { status = PB_CommandStatus_ERROR_INVALID_PARAMETERS; + } else { + FS_Error error_remove = storage_common_remove(fs_api, path); + // FSE_DENIED is for empty directory, but not only for this + // that's why we have to check it + if((error_remove == FSE_DENIED) && !rpc_system_storage_is_dir_is_empty(fs_api, path)) { + if(request->content.storage_delete_request.recursive) { + bool deleted = storage_simply_remove_recursive(fs_api, path); + status = deleted ? PB_CommandStatus_OK : PB_CommandStatus_ERROR; + } else { + status = PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY; + } + } else if(error_remove == FSE_NOT_EXIST) { + status = PB_CommandStatus_OK; + } else { + status = rpc_system_storage_get_error(error_remove); + } } - rpc_encode_and_send_empty(rpc_storage->rpc, request->command_id, status); + + furi_record_close("storage"); + rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status); } static void rpc_system_storage_mkdir_process(const PB_Main* request, void* context) { @@ -295,7 +355,7 @@ static void rpc_system_storage_mkdir_process(const PB_Main* request, void* conte } else { status = PB_CommandStatus_ERROR_INVALID_PARAMETERS; } - rpc_encode_and_send_empty(rpc_storage->rpc, request->command_id, status); + rpc_send_and_release_empty(rpc_storage->rpc, request->command_id, status); } static void rpc_system_storage_md5sum_process(const PB_Main* request, void* context) { @@ -307,7 +367,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont const char* filename = request->content.storage_md5sum_request.path; if(!filename) { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, request->command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS); return; } @@ -349,9 +409,9 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont free(hash); free(data); storage_file_close(file); - rpc_encode_and_send(rpc_storage->rpc, &response); + rpc_send_and_release(rpc_storage->rpc, &response); } else { - rpc_encode_and_send_empty( + rpc_send_and_release_empty( rpc_storage->rpc, request->command_id, rpc_system_storage_get_file_error(file)); } diff --git a/applications/storage/storage-external-api.c b/applications/storage/storage-external-api.c index aa11161e..d74cbb77 100644 --- a/applications/storage/storage-external-api.c +++ b/applications/storage/storage-external-api.c @@ -1,7 +1,11 @@ +#include +#include #include "storage.h" #include "storage-i.h" #include "storage-message.h" +#define MAX_NAME_LENGTH 256 + #define S_API_PROLOGUE \ osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \ furi_check(semaphore != NULL); @@ -382,6 +386,67 @@ void storage_file_free(File* file) { free(file); } +bool storage_simply_remove_recursive(Storage* storage, const char* path) { + furi_assert(storage); + furi_assert(path); + FileInfo fileinfo; + bool result = false; + string_t fullname; + string_t cur_dir; + + if(storage_simply_remove(storage, path)) { + return true; + } + + char* name = furi_alloc(MAX_NAME_LENGTH + 1); + File* dir = storage_file_alloc(storage); + string_init_set_str(cur_dir, path); + bool go_deeper = false; + + while(1) { + if(!storage_dir_open(dir, string_get_cstr(cur_dir))) { + storage_dir_close(dir); + break; + } + + while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { + if(fileinfo.flags & FSF_DIRECTORY) { + string_cat_printf(cur_dir, "/%s", name); + go_deeper = true; + break; + } + + string_init_printf(fullname, "%s/%s", string_get_cstr(cur_dir), name); + FS_Error error = storage_common_remove(storage, string_get_cstr(fullname)); + furi_assert(error == FSE_OK); + string_clear(fullname); + } + storage_dir_close(dir); + + if(go_deeper) { + go_deeper = false; + continue; + } + + FS_Error error = storage_common_remove(storage, string_get_cstr(cur_dir)); + furi_assert(error == FSE_OK); + + if(string_cmp(cur_dir, path)) { + size_t last_char = string_search_rchar(cur_dir, '/'); + furi_assert(last_char != STRING_FAILURE); + string_left(cur_dir, last_char); + } else { + result = true; + break; + } + } + + storage_file_free(dir); + string_clear(cur_dir); + free(name); + return result; +} + bool storage_simply_remove(Storage* storage, const char* path) { FS_Error result; result = storage_common_remove(storage, path); @@ -392,4 +457,4 @@ bool storage_simply_mkdir(Storage* storage, const char* path) { FS_Error result; result = storage_common_mkdir(storage, path); return result == FSE_OK || result == FSE_EXIST; -} \ No newline at end of file +} diff --git a/applications/storage/storage.h b/applications/storage/storage.h index d38153bf..aabe8ddc 100644 --- a/applications/storage/storage.h +++ b/applications/storage/storage.h @@ -240,6 +240,14 @@ FS_Error storage_sd_status(Storage* api); */ bool storage_simply_remove(Storage* storage, const char* path); +/** + * Removes a file/directory from the repository, the directory can be not empty + * @param storage pointer to the api + * @param path + * @return true on success or if file/dir is not exist + */ +bool storage_simply_remove_recursive(Storage* storage, const char* path); + /** * Creates a directory * @param storage @@ -250,4 +258,4 @@ bool storage_simply_mkdir(Storage* storage, const char* path); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/applications/tests/furi_record_test.c b/applications/tests/furi_record_test.c index 7f8a0507..e7eebe4b 100644 --- a/applications/tests/furi_record_test.c +++ b/applications/tests/furi_record_test.c @@ -11,7 +11,10 @@ void test_furi_create_open() { // 2. Open it void* record = furi_record_open("test/holding"); mu_assert_pointers_eq(record, &test_data); + + // 3. Close it furi_record_close("test/holding"); + // 4. Clean up furi_record_destroy("test/holding"); } diff --git a/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c b/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c index 908e196d..80f036b0 100644 --- a/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c +++ b/applications/tests/irda_decoder_encoder/irda_decoder_encoder_test.c @@ -325,7 +325,6 @@ MU_TEST_SUITE(test_irda_decoder_encoder) { int run_minunit_test_irda_decoder_encoder() { MU_RUN_SUITE(test_irda_decoder_encoder); - MU_REPORT(); return MU_EXIT_CODE; } diff --git a/applications/tests/minunit.h b/applications/tests/minunit.h index 466cf9c1..b12a87ca 100644 --- a/applications/tests/minunit.h +++ b/applications/tests/minunit.h @@ -79,6 +79,9 @@ extern "C" { __attribute__((unused)) static void (*minunit_setup)(void) = NULL; __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; +void minunit_print_progress(void); +void minunit_print_fail(const char* error); + /* Definitions */ #define MU_TEST(method_name) static void method_name(void) #define MU_TEST_SUITE(suite_name) static void suite_name(void) @@ -108,8 +111,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_run++; \ if(minunit_status) { \ minunit_fail++; \ - printf("F"); \ - printf("\n%s\n", minunit_last_message); \ + minunit_print_fail(minunit_last_message); \ } fflush(stdout); \ if(minunit_teardown)(*minunit_teardown)();) @@ -142,7 +144,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; #test); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_fail(message) \ MU__SAFE_BLOCK(minunit_assert++; snprintf( \ @@ -169,7 +171,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; message); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -187,7 +189,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_r); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_not_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -204,7 +206,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_greater_than(val, result) \ MU__SAFE_BLOCK( \ @@ -222,7 +224,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_less_than(val, result) \ MU__SAFE_BLOCK( \ @@ -240,7 +242,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_between(expected_lower, expected_upper, result) \ MU__SAFE_BLOCK( \ @@ -261,7 +263,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_m); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_int_in(expected, array_length, result) \ MU__SAFE_BLOCK( \ @@ -288,7 +290,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_r); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_double_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -309,7 +311,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_r); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_double_greater_than(val, result) \ MU__SAFE_BLOCK( \ @@ -327,7 +329,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_double_less_than(val, result) \ MU__SAFE_BLOCK( \ @@ -345,7 +347,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_e); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_double_between(expected_lower, expected_upper, result) \ MU__SAFE_BLOCK( \ @@ -366,7 +368,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_m); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) #define mu_assert_string_eq(expected, result) \ MU__SAFE_BLOCK( \ @@ -386,39 +388,39 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; minunit_tmp_r); \ minunit_status = 1; \ return; \ - } else { printf("."); }) + } else { minunit_print_progress(); }) -#define mu_assert_null(result) \ - MU__SAFE_BLOCK( \ - minunit_assert++; if(result == NULL) { printf("."); } else { \ - snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: Expected result was not NULL", \ - __func__, \ - __FILE__, \ - __LINE__); \ - minunit_status = 1; \ - return; \ +#define mu_assert_null(result) \ + MU__SAFE_BLOCK( \ + minunit_assert++; if(result == NULL) { minunit_print_progress(); } else { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\n\t%s:%d: Expected result was not NULL", \ + __func__, \ + __FILE__, \ + __LINE__); \ + minunit_status = 1; \ + return; \ }) -#define mu_assert_not_null(result) \ - MU__SAFE_BLOCK( \ - minunit_assert++; if(result != NULL) { printf("."); } else { \ - snprintf( \ - minunit_last_message, \ - MINUNIT_MESSAGE_LEN, \ - "%s failed:\n\t%s:%d: Expected result was not NULL", \ - __func__, \ - __FILE__, \ - __LINE__); \ - minunit_status = 1; \ - return; \ +#define mu_assert_not_null(result) \ + MU__SAFE_BLOCK( \ + minunit_assert++; if(result != NULL) { minunit_print_progress(); } else { \ + snprintf( \ + minunit_last_message, \ + MINUNIT_MESSAGE_LEN, \ + "%s failed:\n\t%s:%d: Expected result was not NULL", \ + __func__, \ + __FILE__, \ + __LINE__); \ + minunit_status = 1; \ + return; \ }) #define mu_assert_pointers_eq(pointer1, pointer2) \ MU__SAFE_BLOCK( \ - minunit_assert++; if(pointer1 == pointer2) { printf("."); } else { \ + minunit_assert++; if(pointer1 == pointer2) { minunit_print_progress(); } else { \ snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ @@ -432,7 +434,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL; #define mu_assert_pointers_not_eq(pointer1, pointer2) \ MU__SAFE_BLOCK( \ - minunit_assert++; if(pointer1 != pointer2) { printf("."); } else { \ + minunit_assert++; if(pointer1 != pointer2) { minunit_print_progress(); } else { \ snprintf( \ minunit_last_message, \ MINUNIT_MESSAGE_LEN, \ @@ -603,4 +605,4 @@ __attribute__((unused)) static double mu_timer_cpu(void) { } #endif -#endif /* MINUNIT_MINUNIT_H */ \ No newline at end of file +#endif /* MINUNIT_MINUNIT_H */ diff --git a/applications/tests/minunit_test.c b/applications/tests/minunit_test.c index 8c94c845..1e71fd07 100644 --- a/applications/tests/minunit_test.c +++ b/applications/tests/minunit_test.c @@ -62,7 +62,6 @@ MU_TEST_SUITE(test_suite) { int run_minunit() { MU_RUN_SUITE(test_suite); - MU_REPORT(); return MU_EXIT_CODE; } diff --git a/applications/tests/rpc/rpc_test.c b/applications/tests/rpc/rpc_test.c index 18f1c7a8..3f59cebf 100644 --- a/applications/tests/rpc/rpc_test.c +++ b/applications/tests/rpc/rpc_test.c @@ -56,25 +56,40 @@ static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected); static void test_rpc_decode_and_compare(MsgList_t expected_msg_list); static void test_rpc_free_msg_list(MsgList_t msg_list); -static void test_rpc_storage_setup(void) { +static void test_rpc_setup(void) { furi_assert(!rpc); furi_assert(!session); furi_assert(!output_stream); rpc = furi_record_open("rpc"); for(int i = 0; !session && (i < 10000); ++i) { - session = rpc_open_session(rpc); + session = rpc_session_open(rpc); delay(1); } furi_assert(session); + output_stream = xStreamBufferCreate(1000, 1); + mu_assert(session, "failed to start session"); + rpc_session_set_send_bytes_callback(session, output_bytes_callback); + rpc_session_set_context(session, output_stream); +} + +static void test_rpc_teardown(void) { + rpc_session_close(session); + furi_record_close("rpc"); + vStreamBufferDelete(output_stream); + ++command_id; + output_stream = NULL; + rpc = NULL; + session = NULL; +} + +static void test_rpc_storage_setup(void) { + test_rpc_setup(); + Storage* fs_api = furi_record_open("storage"); clean_directory(fs_api, TEST_DIR_NAME); furi_record_close("storage"); - - output_stream = xStreamBufferCreate(1000, 1); - mu_assert(session, "failed to start session"); - rpc_set_send_bytes_callback(session, output_bytes_callback, output_stream); } static void test_rpc_storage_teardown(void) { @@ -82,13 +97,7 @@ static void test_rpc_storage_teardown(void) { clean_directory(fs_api, TEST_DIR_NAME); furi_record_close("storage"); - rpc_close_session(session); - furi_record_close("rpc"); - vStreamBufferDelete(output_stream); - ++command_id; - output_stream = NULL; - rpc = NULL; - session = NULL; + test_rpc_teardown(); } static void clean_directory(Storage* fs_api, const char* clean_dir) { @@ -197,10 +206,12 @@ static void test_rpc_create_simple_message( const char* str, uint32_t command_id) { furi_assert(message); - furi_assert(str); - char* str_copy = furi_alloc(strlen(str) + 1); - strcpy(str_copy, str); + char* str_copy = NULL; + if(str) { + str_copy = furi_alloc(strlen(str) + 1); + strcpy(str_copy, str); + } message->command_id = command_id; message->command_status = PB_CommandStatus_OK; message->cb_content.funcs.encode = NULL; @@ -292,7 +303,7 @@ static void test_rpc_encode_and_feed_one(PB_Main* request) { size_t bytes_left = ostream.bytes_written; uint8_t* buffer_ptr = buffer; do { - size_t bytes_sent = rpc_feed_bytes(session, buffer_ptr, bytes_left, 1000); + size_t bytes_sent = rpc_session_feed(session, buffer_ptr, bytes_left, 1000); mu_check(bytes_sent > 0); bytes_left -= bytes_sent; @@ -402,6 +413,38 @@ static bool test_rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_ return (count == bytes_received); } +static void + test_rpc_storage_list_create_expected_list_root(MsgList_t msg_list, uint32_t command_id) { + PB_Main* message = MsgList_push_new(msg_list); + message->has_next = false; + message->cb_content.funcs.encode = NULL; + message->command_id = command_id; + message->which_content = PB_Main_storage_list_response_tag; + + message->content.storage_list_response.file_count = 3; + message->content.storage_list_response.file[0].data = NULL; + message->content.storage_list_response.file[1].data = NULL; + message->content.storage_list_response.file[2].data = NULL; + + message->content.storage_list_response.file[0].size = 0; + message->content.storage_list_response.file[1].size = 0; + message->content.storage_list_response.file[2].size = 0; + + message->content.storage_list_response.file[0].type = PB_Storage_File_FileType_DIR; + message->content.storage_list_response.file[1].type = PB_Storage_File_FileType_DIR; + message->content.storage_list_response.file[2].type = PB_Storage_File_FileType_DIR; + + char* str = furi_alloc(4); + strcpy(str, "any"); + message->content.storage_list_response.file[0].name = str; + str = furi_alloc(4); + strcpy(str, "int"); + message->content.storage_list_response.file[1].name = str; + str = furi_alloc(4); + strcpy(str, "ext"); + message->content.storage_list_response.file[2].name = str; +} + static void test_rpc_storage_list_create_expected_list( MsgList_t msg_list, const char* path, @@ -505,7 +548,11 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) { MsgList_init(expected_msg_list); test_rpc_create_simple_message(&request, PB_Main_storage_list_request_tag, path, command_id); - test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id); + if(!strcmp(path, "/")) { + test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id); + } else { + test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id); + } test_rpc_encode_and_feed_one(&request); test_rpc_decode_and_compare(expected_msg_list); @@ -514,6 +561,7 @@ static void test_rpc_storage_list_run(const char* path, uint32_t command_id) { } MU_TEST(test_storage_list) { + test_rpc_storage_list_run("/", ++command_id); test_rpc_storage_list_run("/ext/nfc", ++command_id); test_rpc_storage_list_run("/int", ++command_id); @@ -597,12 +645,23 @@ static void test_storage_read_run(const char* path, uint32_t command_id) { test_rpc_free_msg_list(expected_msg_list); } +static bool test_is_exists(const char* path) { + Storage* fs_api = furi_record_open("storage"); + FileInfo fileinfo; + FS_Error result = storage_common_stat(fs_api, path, &fileinfo); + + furi_check((result == FSE_OK) || (result == FSE_NOT_EXIST)); + + return result == FSE_OK; +} + static void test_create_dir(const char* path) { Storage* fs_api = furi_record_open("storage"); FS_Error error = storage_common_mkdir(fs_api, path); (void)error; furi_assert((error == FSE_OK) || (error == FSE_EXIST)); furi_record_close("storage"); + furi_check(test_is_exists(path)); } static void test_create_file(const char* path, size_t size) { @@ -625,6 +684,7 @@ static void test_create_file(const char* path, size_t size) { storage_file_free(file); furi_record_close("storage"); + furi_check(test_is_exists(path)); } MU_TEST(test_storage_read) { @@ -829,12 +889,17 @@ MU_TEST(test_storage_interrupt_continuous_another_system) { test_rpc_free_msg_list(expected_msg_list); } -static void test_storage_delete_run(const char* path, size_t command_id, PB_CommandStatus status) { +static void test_storage_delete_run( + const char* path, + size_t command_id, + PB_CommandStatus status, + bool recursive) { PB_Main request; MsgList_t expected_msg_list; MsgList_init(expected_msg_list); test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id); + request.content.storage_delete_request.recursive = recursive; test_rpc_add_empty_to_list(expected_msg_list, status, command_id); test_rpc_encode_and_feed_one(&request); @@ -844,16 +909,69 @@ static void test_storage_delete_run(const char* path, size_t command_id, PB_Comm test_rpc_free_msg_list(expected_msg_list); } -MU_TEST(test_storage_delete) { - test_create_file(TEST_DIR "empty.txt", 0); - test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK); - test_storage_delete_run( - TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_ERROR_STORAGE_NOT_EXIST); +#define TEST_DIR_RMRF_NAME TEST_DIR "rmrf_test" +#define TEST_DIR_RMRF TEST_DIR_RMRF_NAME "/" +MU_TEST(test_storage_delete_recursive) { + test_create_dir(TEST_DIR_RMRF_NAME); + + test_create_dir(TEST_DIR_RMRF "dir1"); + test_create_file(TEST_DIR_RMRF "dir1/file1", 1); + + test_create_dir(TEST_DIR_RMRF "dir1/dir1"); + test_create_dir(TEST_DIR_RMRF "dir1/dir2"); + test_create_file(TEST_DIR_RMRF "dir1/dir2/file1", 1); + test_create_file(TEST_DIR_RMRF "dir1/dir2/file2", 1); + test_create_dir(TEST_DIR_RMRF "dir1/dir3"); + test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1"); + test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1"); + test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1/dir1"); + test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1/dir1/dir1"); + + test_create_dir(TEST_DIR_RMRF "dir2"); + test_create_dir(TEST_DIR_RMRF "dir2/dir1"); + test_create_dir(TEST_DIR_RMRF "dir2/dir2"); + test_create_file(TEST_DIR_RMRF "dir2/dir2/file1", 1); + + test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1"); + test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1/dir1"); + test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1/dir1/dir1"); + test_create_file(TEST_DIR_RMRF "dir2/dir2/dir1/dir1/dir1/file1", 1); - test_create_dir(TEST_DIR "dir1"); - test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK); test_storage_delete_run( - TEST_DIR "dir1", ++command_id, PB_CommandStatus_ERROR_STORAGE_NOT_EXIST); + TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY, false); + mu_check(test_is_exists(TEST_DIR_RMRF_NAME)); + test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, true); + mu_check(!test_is_exists(TEST_DIR_RMRF_NAME)); + test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR_RMRF_NAME)); + + test_create_dir(TEST_DIR_RMRF_NAME); + test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, true); + mu_check(!test_is_exists(TEST_DIR_RMRF_NAME)); + + test_create_dir(TEST_DIR "file1"); + test_storage_delete_run(TEST_DIR "file1", ++command_id, PB_CommandStatus_OK, true); + mu_check(!test_is_exists(TEST_DIR "file1")); +} + +MU_TEST(test_storage_delete) { + test_storage_delete_run(NULL, ++command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS, false); + + furi_check(!test_is_exists(TEST_DIR "empty.txt")); + test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR "empty.txt")); + + test_create_file(TEST_DIR "empty.txt", 0); + test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR "empty.txt")); + + furi_check(!test_is_exists(TEST_DIR "dir1")); + test_create_dir(TEST_DIR "dir1"); + test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR "dir1")); + + test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK, false); + mu_check(!test_is_exists(TEST_DIR "dir1")); } static void test_storage_mkdir_run(const char* path, size_t command_id, PB_CommandStatus status) { @@ -872,18 +990,17 @@ static void test_storage_mkdir_run(const char* path, size_t command_id, PB_Comma } MU_TEST(test_storage_mkdir) { + furi_check(!test_is_exists(TEST_DIR "dir1")); test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK); + mu_check(test_is_exists(TEST_DIR "dir1")); + test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST); + mu_check(test_is_exists(TEST_DIR "dir1")); + + furi_check(!test_is_exists(TEST_DIR "dir2")); test_create_dir(TEST_DIR "dir2"); test_storage_mkdir_run(TEST_DIR "dir2", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST); - - Storage* fs_api = furi_record_open("storage"); - FS_Error error = storage_common_remove(fs_api, TEST_DIR "dir1"); - (void)error; - furi_assert(error == FSE_OK); - furi_record_close("storage"); - - test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK); + mu_check(test_is_exists(TEST_DIR "dir2")); } static void test_storage_calculate_md5sum(const char* path, char* md5sum) { @@ -1013,7 +1130,7 @@ MU_TEST(test_ping) { // 4) test for fill buffer till end (great varint) and close connection MU_TEST_SUITE(test_rpc_status) { - MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown); + MU_SUITE_CONFIGURE(&test_rpc_setup, &test_rpc_teardown); MU_RUN_TEST(test_ping); } @@ -1026,6 +1143,7 @@ MU_TEST_SUITE(test_rpc_storage) { MU_RUN_TEST(test_storage_write_read); MU_RUN_TEST(test_storage_write); MU_RUN_TEST(test_storage_delete); + MU_RUN_TEST(test_storage_delete_recursive); MU_RUN_TEST(test_storage_mkdir); MU_RUN_TEST(test_storage_md5sum); MU_RUN_TEST(test_storage_interrupt_continuous_same_system); @@ -1112,20 +1230,19 @@ MU_TEST(test_app_start_and_lock_status) { "skynet_destroy_world_app", NULL, PB_CommandStatus_ERROR_INVALID_PARAMETERS, ++command_id); test_app_get_status_lock_run(false, ++command_id); - test_app_start_run("Delay Test App", "0", PB_CommandStatus_OK, ++command_id); + test_app_start_run("Delay Test", "0", PB_CommandStatus_OK, ++command_id); delay(100); test_app_get_status_lock_run(false, ++command_id); - test_app_start_run("Delay Test App", "200", PB_CommandStatus_OK, ++command_id); + test_app_start_run("Delay Test", "200", PB_CommandStatus_OK, ++command_id); test_app_get_status_lock_run(true, ++command_id); delay(100); test_app_get_status_lock_run(true, ++command_id); - test_app_start_run( - "Delay Test App", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id); + test_app_start_run("Delay Test", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id); delay(200); test_app_get_status_lock_run(false, ++command_id); - test_app_start_run("Delay Test App", "500", PB_CommandStatus_OK, ++command_id); + test_app_start_run("Delay Test", "500", PB_CommandStatus_OK, ++command_id); delay(100); test_app_get_status_lock_run(true, ++command_id); test_app_start_run("Infrared", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id); @@ -1140,16 +1257,22 @@ MU_TEST(test_app_start_and_lock_status) { } MU_TEST_SUITE(test_rpc_app) { - MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown); + MU_SUITE_CONFIGURE(&test_rpc_setup, &test_rpc_teardown); MU_RUN_TEST(test_app_start_and_lock_status); } int run_minunit_test_rpc() { - MU_RUN_SUITE(test_rpc_storage); + Storage* storage = furi_record_open("storage"); + furi_record_close("storage"); + if(storage_sd_status(storage) != FSE_OK) { + FURI_LOG_E("UNIT_TESTS", "SD card not mounted - skip storage tests"); + } else { + MU_RUN_SUITE(test_rpc_storage); + } + MU_RUN_SUITE(test_rpc_status); MU_RUN_SUITE(test_rpc_app); - MU_REPORT(); return MU_EXIT_CODE; } diff --git a/applications/tests/test_index.c b/applications/tests/test_index.c index 7158b304..d55a1e45 100644 --- a/applications/tests/test_index.c +++ b/applications/tests/test_index.c @@ -7,10 +7,27 @@ #include #include +#define TESTS_TAG "UNIT_TESTS" + int run_minunit(); int run_minunit_test_irda_decoder_encoder(); int run_minunit_test_rpc(); +void minunit_print_progress(void) { + static char progress[] = {'\\', '|', '/', '-'}; + static uint8_t progress_counter = 0; + static TickType_t last_tick = 0; + TickType_t current_tick = xTaskGetTickCount(); + if(current_tick - last_tick > 20) { + last_tick = current_tick; + printf("[%c]\033[3D", progress[++progress_counter % COUNT_OF(progress)]); + } +} + +void minunit_print_fail(const char* str) { + printf("%s\n", str); +} + void unit_tests_cli(Cli* cli, string_t args, void* context) { uint32_t test_result = 0; minunit_run = 0; @@ -25,21 +42,30 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { furi_record_close("notification"); if(loader_is_locked(loader)) { - FURI_LOG_E("UNIT_TESTS", "RPC: stop all applications to run tests"); + FURI_LOG_E(TESTS_TAG, "RPC: stop all applications to run tests"); notification_message(notification, &sequence_blink_magenta_100); } else { notification_message_block(notification, &sequence_set_only_blue_255); + uint32_t heap_before = memmgr_get_free_heap(); + test_result |= run_minunit(); test_result |= run_minunit_test_irda_decoder_encoder(); test_result |= run_minunit_test_rpc(); if(test_result == 0) { + delay(200); /* wait for tested services and apps to deallocate */ + uint32_t heap_after = memmgr_get_free_heap(); notification_message(notification, &sequence_success); - FURI_LOG_I("UNIT_TESTS", "PASSED"); + if(heap_after != heap_before) { + FURI_LOG_E(TESTS_TAG, "Leaked: %d", heap_before - heap_after); + } else { + FURI_LOG_I(TESTS_TAG, "No leaks"); + } + FURI_LOG_I(TESTS_TAG, "PASSED"); } else { notification_message(notification, &sequence_error); - FURI_LOG_E("UNIT_TESTS", "FAILED"); + FURI_LOG_E(TESTS_TAG, "FAILED"); } } } diff --git a/assets/compiled/flipper.pb.c b/assets/compiled/flipper.pb.c index d81c78ec..684ffe8c 100644 --- a/assets/compiled/flipper.pb.c +++ b/assets/compiled/flipper.pb.c @@ -9,6 +9,9 @@ PB_BIND(PB_Empty, PB_Empty, AUTO) +PB_BIND(PB_StopSession, PB_StopSession, AUTO) + + PB_BIND(PB_Main, PB_Main, AUTO) diff --git a/assets/compiled/flipper.pb.h b/assets/compiled/flipper.pb.h index d37a38a2..bb2a3535 100644 --- a/assets/compiled/flipper.pb.h +++ b/assets/compiled/flipper.pb.h @@ -30,7 +30,8 @@ typedef enum _PB_CommandStatus { PB_CommandStatus_ERROR_STORAGE_INTERNAL = 11, /* *< Internal error */ PB_CommandStatus_ERROR_STORAGE_NOT_IMPLEMENTED = 12, /* *< Functon not implemented */ PB_CommandStatus_ERROR_STORAGE_ALREADY_OPEN = 13, /* *< File/Dir already opened */ - PB_CommandStatus_ERROR_APP_CANT_START = 16, /* *< Can't start app - or internal error */ + PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY = 18, /* *< Directory, you're going to remove is not empty */ + PB_CommandStatus_ERROR_APP_CANT_START = 16, /* *< Can't start app - internal error */ PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED = 17 /* *< Another app is running */ } PB_CommandStatus; @@ -42,6 +43,10 @@ typedef struct _PB_Empty { char dummy_field; } PB_Empty; +typedef struct _PB_StopSession { + char dummy_field; +} PB_StopSession; + typedef struct _PB_Main { uint32_t command_id; PB_CommandStatus command_status; @@ -64,14 +69,15 @@ typedef struct _PB_Main { PB_App_Start app_start; PB_App_LockStatusRequest app_lock_status_request; PB_App_LockStatusResponse app_lock_status_response; + PB_StopSession stop_session; } content; } PB_Main; /* Helper constants for enums */ #define _PB_CommandStatus_MIN PB_CommandStatus_OK -#define _PB_CommandStatus_MAX PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED -#define _PB_CommandStatus_ARRAYSIZE ((PB_CommandStatus)(PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED+1)) +#define _PB_CommandStatus_MAX PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY +#define _PB_CommandStatus_ARRAYSIZE ((PB_CommandStatus)(PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY+1)) #ifdef __cplusplus @@ -80,8 +86,10 @@ extern "C" { /* Initializer values for message structs */ #define PB_Empty_init_default {0} +#define PB_StopSession_init_default {0} #define PB_Main_init_default {0, _PB_CommandStatus_MIN, 0, {{NULL}, NULL}, 0, {PB_Empty_init_default}} #define PB_Empty_init_zero {0} +#define PB_StopSession_init_zero {0} #define PB_Main_init_zero {0, _PB_CommandStatus_MIN, 0, {{NULL}, NULL}, 0, {PB_Empty_init_zero}} /* Field tags (for use in manual encoding/decoding) */ @@ -103,6 +111,7 @@ extern "C" { #define PB_Main_app_start_tag 16 #define PB_Main_app_lock_status_request_tag 17 #define PB_Main_app_lock_status_response_tag 18 +#define PB_Main_stop_session_tag 19 /* Struct field encoding specification for nanopb */ #define PB_Empty_FIELDLIST(X, a) \ @@ -110,6 +119,11 @@ extern "C" { #define PB_Empty_CALLBACK NULL #define PB_Empty_DEFAULT NULL +#define PB_StopSession_FIELDLIST(X, a) \ + +#define PB_StopSession_CALLBACK NULL +#define PB_StopSession_DEFAULT NULL + #define PB_Main_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, command_id, 1) \ X(a, STATIC, SINGULAR, UENUM, command_status, 2) \ @@ -128,7 +142,8 @@ X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_md5sum_request,content.stora X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_md5sum_response,content.storage_md5sum_response), 15) \ X(a, STATIC, ONEOF, MSG_W_CB, (content,app_start,content.app_start), 16) \ X(a, STATIC, ONEOF, MSG_W_CB, (content,app_lock_status_request,content.app_lock_status_request), 17) \ -X(a, STATIC, ONEOF, MSG_W_CB, (content,app_lock_status_response,content.app_lock_status_response), 18) +X(a, STATIC, ONEOF, MSG_W_CB, (content,app_lock_status_response,content.app_lock_status_response), 18) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,stop_session,content.stop_session), 19) #define PB_Main_CALLBACK NULL #define PB_Main_DEFAULT NULL #define PB_Main_content_empty_MSGTYPE PB_Empty @@ -146,16 +161,20 @@ X(a, STATIC, ONEOF, MSG_W_CB, (content,app_lock_status_response,content.app #define PB_Main_content_app_start_MSGTYPE PB_App_Start #define PB_Main_content_app_lock_status_request_MSGTYPE PB_App_LockStatusRequest #define PB_Main_content_app_lock_status_response_MSGTYPE PB_App_LockStatusResponse +#define PB_Main_content_stop_session_MSGTYPE PB_StopSession extern const pb_msgdesc_t PB_Empty_msg; +extern const pb_msgdesc_t PB_StopSession_msg; extern const pb_msgdesc_t PB_Main_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define PB_Empty_fields &PB_Empty_msg +#define PB_StopSession_fields &PB_StopSession_msg #define PB_Main_fields &PB_Main_msg /* Maximum encoded size of messages (where known) */ #define PB_Empty_size 0 +#define PB_StopSession_size 0 #if defined(PB_Storage_ListRequest_size) && defined(PB_Storage_ListResponse_size) && defined(PB_Storage_ReadRequest_size) && defined(PB_Storage_ReadResponse_size) && defined(PB_Storage_WriteRequest_size) && defined(PB_Storage_DeleteRequest_size) && defined(PB_Storage_MkdirRequest_size) && defined(PB_Storage_Md5sumRequest_size) && defined(PB_App_Start_size) #define PB_Main_size (10 + sizeof(union PB_Main_content_size_union)) union PB_Main_content_size_union {char f7[(6 + PB_Storage_ListRequest_size)]; char f8[(6 + PB_Storage_ListResponse_size)]; char f9[(6 + PB_Storage_ReadRequest_size)]; char f10[(6 + PB_Storage_ReadResponse_size)]; char f11[(6 + PB_Storage_WriteRequest_size)]; char f12[(6 + PB_Storage_DeleteRequest_size)]; char f13[(6 + PB_Storage_MkdirRequest_size)]; char f14[(6 + PB_Storage_Md5sumRequest_size)]; char f16[(7 + PB_App_Start_size)]; char f0[36];}; diff --git a/assets/compiled/storage.pb.h b/assets/compiled/storage.pb.h index cdb47372..ea6291b9 100644 --- a/assets/compiled/storage.pb.h +++ b/assets/compiled/storage.pb.h @@ -16,10 +16,6 @@ typedef enum _PB_Storage_File_FileType { } PB_Storage_File_FileType; /* Struct definitions */ -typedef struct _PB_Storage_DeleteRequest { - char *path; -} PB_Storage_DeleteRequest; - typedef struct _PB_Storage_ListRequest { char *path; } PB_Storage_ListRequest; @@ -36,6 +32,11 @@ typedef struct _PB_Storage_ReadRequest { char *path; } PB_Storage_ReadRequest; +typedef struct _PB_Storage_DeleteRequest { + char *path; + bool recursive; +} PB_Storage_DeleteRequest; + typedef struct _PB_Storage_File { PB_Storage_File_FileType type; char *name; @@ -81,7 +82,7 @@ extern "C" { #define PB_Storage_ReadRequest_init_default {NULL} #define PB_Storage_ReadResponse_init_default {false, PB_Storage_File_init_default} #define PB_Storage_WriteRequest_init_default {NULL, false, PB_Storage_File_init_default} -#define PB_Storage_DeleteRequest_init_default {NULL} +#define PB_Storage_DeleteRequest_init_default {NULL, 0} #define PB_Storage_MkdirRequest_init_default {NULL} #define PB_Storage_Md5sumRequest_init_default {NULL} #define PB_Storage_Md5sumResponse_init_default {""} @@ -91,17 +92,18 @@ extern "C" { #define PB_Storage_ReadRequest_init_zero {NULL} #define PB_Storage_ReadResponse_init_zero {false, PB_Storage_File_init_zero} #define PB_Storage_WriteRequest_init_zero {NULL, false, PB_Storage_File_init_zero} -#define PB_Storage_DeleteRequest_init_zero {NULL} +#define PB_Storage_DeleteRequest_init_zero {NULL, 0} #define PB_Storage_MkdirRequest_init_zero {NULL} #define PB_Storage_Md5sumRequest_init_zero {NULL} #define PB_Storage_Md5sumResponse_init_zero {""} /* Field tags (for use in manual encoding/decoding) */ -#define PB_Storage_DeleteRequest_path_tag 1 #define PB_Storage_ListRequest_path_tag 1 #define PB_Storage_Md5sumRequest_path_tag 1 #define PB_Storage_MkdirRequest_path_tag 1 #define PB_Storage_ReadRequest_path_tag 1 +#define PB_Storage_DeleteRequest_path_tag 1 +#define PB_Storage_DeleteRequest_recursive_tag 2 #define PB_Storage_File_type_tag 1 #define PB_Storage_File_name_tag 2 #define PB_Storage_File_size_tag 3 @@ -151,7 +153,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, file, 2) #define PB_Storage_WriteRequest_file_MSGTYPE PB_Storage_File #define PB_Storage_DeleteRequest_FIELDLIST(X, a) \ -X(a, POINTER, SINGULAR, STRING, path, 1) +X(a, POINTER, SINGULAR, STRING, path, 1) \ +X(a, STATIC, SINGULAR, BOOL, recursive, 2) #define PB_Storage_DeleteRequest_CALLBACK NULL #define PB_Storage_DeleteRequest_DEFAULT NULL diff --git a/assets/protobuf b/assets/protobuf index 8e6db414..021ba48a 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 8e6db414beed5aff0902f2cca2f4146a0dffb7a1 +Subproject commit 021ba48abb64d25c7094da13b752fe37d4bf6007 diff --git a/core/furi/record.c b/core/furi/record.c index 7bc736df..85d196c0 100644 --- a/core/furi/record.c +++ b/core/furi/record.c @@ -85,6 +85,7 @@ bool furi_record_destroy(const char* name) { furi_assert(record_data); if(record_data->holders_count == 0) { FuriRecordDataDict_erase(furi_record->records, name_str); + furi_check(osOK == osEventFlagsDelete(record_data->flags)); ret = true; } diff --git a/firmware/targets/f6/furi-hal/furi-hal-vcp.c b/firmware/targets/f6/furi-hal/furi-hal-vcp.c index b975495d..b2f17842 100644 --- a/firmware/targets/f6/furi-hal/furi-hal-vcp.c +++ b/firmware/targets/f6/furi-hal/furi-hal-vcp.c @@ -157,3 +157,8 @@ static void vcp_on_cdc_rx() { static void vcp_on_cdc_tx_complete() { osSemaphoreRelease(furi_hal_vcp->tx_semaphore); } + +bool furi_hal_vcp_is_connected(void) { + return furi_hal_vcp->connected; +} + diff --git a/firmware/targets/f7/furi-hal/furi-hal-vcp.c b/firmware/targets/f7/furi-hal/furi-hal-vcp.c index b975495d..b2f17842 100644 --- a/firmware/targets/f7/furi-hal/furi-hal-vcp.c +++ b/firmware/targets/f7/furi-hal/furi-hal-vcp.c @@ -157,3 +157,8 @@ static void vcp_on_cdc_rx() { static void vcp_on_cdc_tx_complete() { osSemaphoreRelease(furi_hal_vcp->tx_semaphore); } + +bool furi_hal_vcp_is_connected(void) { + return furi_hal_vcp->connected; +} + diff --git a/firmware/targets/furi-hal-include/furi-hal-vcp.h b/firmware/targets/furi-hal-include/furi-hal-vcp.h index 996232cf..27c04fb1 100644 --- a/firmware/targets/furi-hal-include/furi-hal-vcp.h +++ b/firmware/targets/furi-hal-include/furi-hal-vcp.h @@ -52,6 +52,12 @@ size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeo */ void furi_hal_vcp_tx(const uint8_t* buffer, size_t size); +/** Check whether VCP is connected + * + * @return true if connected + */ +bool furi_hal_vcp_is_connected(void); + #ifdef __cplusplus } #endif