Просмотр исходного кода

RPC: Implement storage_stat_request (#800)

* RPC: Update protobuf sources
* RPC: Implement storage_stat_request
* RPC: Test storage_stat_request
* FuriRecord: fix use after free in destroy method.
* Furi: refactor PubSub and it's usage. Fix allocation in RPC.
* FuriCore: fix memory leak in pubsub
* FuriCore: update unsubscribe method signature in pubsub, make subscription structure lighter.
* FuriCore: remove dead code

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Anna Prosvetova 4 лет назад
Родитель
Сommit
e9e76e144c
37 измененных файлов с 353 добавлено и 217 удалено
  1. 2 2
      applications/bt/bt_service/bt.c
  2. 1 1
      applications/gui/gui.c
  3. 1 1
      applications/gui/gui_i.h
  4. 0 1
      applications/gui/icon_animation.c
  5. 7 7
      applications/input/input.c
  6. 1 1
      applications/input/input.h
  7. 1 3
      applications/input/input_i.h
  8. 2 2
      applications/loader/loader.c
  9. 1 1
      applications/notification/notification-app.c
  10. 1 1
      applications/notification/notification-app.h
  11. 13 9
      applications/power/power_service/power.c
  12. 1 1
      applications/power/power_service/power.h
  13. 3 2
      applications/power/power_service/power_api.c
  14. 1 1
      applications/power/power_service/power_i.h
  15. 5 3
      applications/rpc/rpc_gui.c
  16. 3 0
      applications/rpc/rpc_i.h
  17. 39 3
      applications/rpc/rpc_storage.c
  18. 2 0
      applications/storage/storage-glue.h
  19. 1 0
      applications/subghz/subghz_cli.c
  20. 11 20
      applications/tests/furi_pubsub_test.c
  21. 72 4
      applications/tests/rpc/rpc_test.c
  22. 1 0
      applications/tests/test_index.c
  23. 11 3
      assets/compiled/flipper.pb.h
  24. 6 0
      assets/compiled/storage.pb.c
  25. 32 0
      assets/compiled/storage.pb.h
  26. 1 1
      assets/protobuf
  27. 4 0
      core/furi.h
  28. 74 67
      core/furi/pubsub.c
  29. 45 76
      core/furi/pubsub.h
  30. 1 1
      core/furi/record.c
  31. 3 0
      core/furi/stdglue.c
  32. 1 1
      firmware/targets/f6/furi-hal/furi-hal-spi.c
  33. 1 1
      firmware/targets/f6/furi-hal/furi-hal-subghz.c
  34. 1 1
      firmware/targets/f7/furi-hal/furi-hal-spi.c
  35. 1 1
      firmware/targets/f7/furi-hal/furi-hal-subghz.c
  36. 2 2
      lib/common-api/task-control-block.h
  37. 1 0
      lib/irda/worker/irda_worker.c

+ 2 - 2
applications/bt/bt_service/bt.c

@@ -69,8 +69,8 @@ Bt* bt_alloc() {
 
     // Power
     bt->power = furi_record_open("power");
-    PubSub* power_pubsub = power_get_pubsub(bt->power);
-    subscribe_pubsub(power_pubsub, bt_battery_level_changed_callback, bt);
+    FuriPubSub* power_pubsub = power_get_pubsub(bt->power);
+    furi_pubsub_subscribe(power_pubsub, bt_battery_level_changed_callback, bt);
 
     // RPC
     bt->rpc = furi_record_open("rpc");

+ 1 - 1
applications/gui/gui.c

@@ -410,7 +410,7 @@ Gui* gui_alloc() {
     gui->input_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL);
     gui->input_events = furi_record_open("input_events");
     furi_check(gui->input_events);
-    subscribe_pubsub(gui->input_events, gui_input_events_callback, gui);
+    furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui);
     // Cli
     gui->cli = furi_record_open("cli");
     cli_add_command(

+ 1 - 1
applications/gui/gui_i.h

@@ -50,7 +50,7 @@ struct Gui {
 
     // Input
     osMessageQueueId_t input_queue;
-    PubSub* input_events;
+    FuriPubSub* input_events;
     uint8_t ongoing_input;
     ViewPort* ongoing_input_view_port;
 

+ 0 - 1
applications/gui/icon_animation.c

@@ -2,7 +2,6 @@
 #include "icon_i.h"
 
 #include <furi.h>
-#include <timers.h>
 
 IconAnimation* icon_animation_alloc(const Icon* icon) {
     furi_assert(icon);

+ 7 - 7
applications/input/input.c

@@ -28,11 +28,11 @@ void input_press_timer_callback(void* arg) {
     input_pin->press_counter++;
     if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) {
         event.type = InputTypeLong;
-        notify_pubsub(&input->event_pubsub, &event);
+        furi_pubsub_publish(input->event_pubsub, &event);
     } else if(input_pin->press_counter > INPUT_LONG_PRESS_COUNTS) {
         input_pin->press_counter--;
         event.type = InputTypeRepeat;
-        notify_pubsub(&input->event_pubsub, &event);
+        furi_pubsub_publish(input->event_pubsub, &event);
     }
 }
 
@@ -89,7 +89,7 @@ void input_cli_send(Cli* cli, string_t args, void* context) {
         return;
     }
     // Publish input event
-    notify_pubsub(&input->event_pubsub, &event);
+    furi_pubsub_publish(input->event_pubsub, &event);
 }
 
 const char* input_get_key_name(InputKey key) {
@@ -120,8 +120,8 @@ const char* input_get_type_name(InputType type) {
 int32_t input_srv() {
     input = furi_alloc(sizeof(Input));
     input->thread = osThreadGetId();
-    init_pubsub(&input->event_pubsub);
-    furi_record_create("input_events", &input->event_pubsub);
+    input->event_pubsub = furi_pubsub_alloc();
+    furi_record_create("input_events", input->event_pubsub);
 
     input->cli = furi_record_open("cli");
     if(input->cli) {
@@ -168,14 +168,14 @@ int32_t input_srv() {
                     input_timer_stop(input->pin_states[i].press_timer);
                     if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) {
                         event.type = InputTypeShort;
-                        notify_pubsub(&input->event_pubsub, &event);
+                        furi_pubsub_publish(input->event_pubsub, &event);
                     }
                     input->pin_states[i].press_counter = 0;
                 }
 
                 // Send Press/Release event
                 event.type = input->pin_states[i].state ? InputTypePress : InputTypeRelease;
-                notify_pubsub(&input->event_pubsub, &event);
+                furi_pubsub_publish(input->event_pubsub, &event);
             }
         }
 

+ 1 - 1
applications/input/input.h

@@ -18,7 +18,7 @@ typedef enum {
     InputTypeRepeat, /**< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */
 } InputType;
 
-/** Input Event, dispatches with PubSub */
+/** Input Event, dispatches with FuriPubSub */
 typedef struct {
     uint32_t sequence;
     InputKey key;

+ 1 - 3
applications/input/input_i.h

@@ -6,8 +6,6 @@
 #pragma once
 
 #include "input.h"
-#include <FreeRTOS.h>
-#include <timers.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -35,7 +33,7 @@ typedef struct {
 /** Input state */
 typedef struct {
     osThreadId_t thread;
-    PubSub event_pubsub;
+    FuriPubSub* event_pubsub;
     InputPinState* pin_states;
     Cli* cli;
     volatile uint32_t counter;

+ 2 - 2
applications/loader/loader.c

@@ -140,7 +140,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con
     Loader* instance = context;
 
     if(thread_state == FuriThreadStateRunning) {
-        instance->free_heap_size = xPortGetFreeHeapSize();
+        instance->free_heap_size = memmgr_get_free_heap();
     } else if(thread_state == FuriThreadStateStopped) {
         /*
          * Current Leak Sanitizer assumes that memory is allocated and freed
@@ -153,7 +153,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con
          * both values should be taken into account.
          */
         delay(20);
-        int heap_diff = instance->free_heap_size - xPortGetFreeHeapSize();
+        int heap_diff = instance->free_heap_size - memmgr_get_free_heap();
         FURI_LOG_I(
             LOADER_LOG_TAG,
             "Application thread stopped. Heap allocation balance: %d. Thread allocation balance: %d.",

+ 1 - 1
applications/notification/notification-app.c

@@ -427,7 +427,7 @@ static NotificationApp* notification_app_alloc() {
 
     // display backlight control
     app->event_record = furi_record_open("input_events");
-    subscribe_pubsub(app->event_record, input_event_callback, app);
+    furi_pubsub_subscribe(app->event_record, input_event_callback, app);
     notification_message(app, &sequence_display_on);
 
     return app;

+ 1 - 1
applications/notification/notification-app.h

@@ -44,7 +44,7 @@ typedef struct {
 
 struct NotificationApp {
     osMessageQueueId_t queue;
-    PubSub* event_record;
+    FuriPubSub* event_record;
     osTimerId_t display_timer;
 
     NotificationLedLayer display;

+ 13 - 9
applications/power/power_service/power.c

@@ -31,7 +31,7 @@ Power* power_alloc() {
     power->gui = furi_record_open("gui");
 
     // Pubsub
-    init_pubsub(&power->event_pubsub);
+    power->event_pubsub = furi_pubsub_alloc();
 
     // State initialization
     power->state = PowerStateNotCharging;
@@ -60,10 +60,6 @@ Power* power_alloc() {
 void power_free(Power* power) {
     furi_assert(power);
 
-    // Records
-    furi_record_close("notification");
-    furi_record_close("gui");
-
     // Gui
     view_dispatcher_remove_view(power->view_dispatcher, PowerViewOff);
     power_off_free(power->power_off);
@@ -73,6 +69,14 @@ void power_free(Power* power) {
 
     // State
     osMutexDelete(power->info_mtx);
+
+    // FuriPubSub
+    furi_pubsub_free(power->event_pubsub);
+
+    // Records
+    furi_record_close("notification");
+    furi_record_close("gui");
+
     free(power);
 }
 
@@ -83,14 +87,14 @@ static void power_check_charging_state(Power* power) {
                 notification_internal_message(power->notification, &sequence_charged);
                 power->state = PowerStateCharged;
                 power->event.type = PowerEventTypeFullyCharged;
-                notify_pubsub(&power->event_pubsub, &power->event);
+                furi_pubsub_publish(power->event_pubsub, &power->event);
             }
         } else {
             if(power->state != PowerStateCharging) {
                 notification_internal_message(power->notification, &sequence_charging);
                 power->state = PowerStateCharging;
                 power->event.type = PowerEventTypeStartCharging;
-                notify_pubsub(&power->event_pubsub, &power->event);
+                furi_pubsub_publish(power->event_pubsub, &power->event);
             }
         }
     } else {
@@ -98,7 +102,7 @@ static void power_check_charging_state(Power* power) {
             notification_internal_message(power->notification, &sequence_not_charging);
             power->state = PowerStateNotCharging;
             power->event.type = PowerEventTypeStopCharging;
-            notify_pubsub(&power->event_pubsub, &power->event);
+            furi_pubsub_publish(power->event_pubsub, &power->event);
         }
     }
 }
@@ -156,7 +160,7 @@ static void power_check_battery_level_change(Power* power) {
         power->battery_level = power->info.charge;
         power->event.type = PowerEventTypeBatteryLevelChanged;
         power->event.data.battery_level = power->battery_level;
-        notify_pubsub(&power->event_pubsub, &power->event);
+        furi_pubsub_publish(power->event_pubsub, &power->event);
     }
 }
 

+ 1 - 1
applications/power/power_service/power.h

@@ -62,4 +62,4 @@ void power_get_info(Power* power, PowerInfo* info);
 /** Get power event pubsub handler
  * @param power - Power instance
  */
-PubSub* power_get_pubsub(Power* power);
+FuriPubSub* power_get_pubsub(Power* power);

+ 3 - 2
applications/power/power_service/power_api.c

@@ -1,4 +1,5 @@
 #include "power_i.h"
+
 #include <furi.h>
 #include "furi-hal-power.h"
 #include "furi-hal-bootloader.h"
@@ -30,7 +31,7 @@ void power_get_info(Power* power, PowerInfo* info) {
     osMutexRelease(power->info_mtx);
 }
 
-PubSub* power_get_pubsub(Power* power) {
+FuriPubSub* power_get_pubsub(Power* power) {
     furi_assert(power);
-    return &power->event_pubsub;
+    return power->event_pubsub;
 }

+ 1 - 1
applications/power/power_service/power_i.h

@@ -25,7 +25,7 @@ struct Power {
     ViewPort* battery_view_port;
     Gui* gui;
     NotificationApp* notification;
-    PubSub event_pubsub;
+    FuriPubSub* event_pubsub;
     PowerEvent event;
 
     PowerState state;

+ 5 - 3
applications/rpc/rpc_gui.c

@@ -113,9 +113,9 @@ void rpc_system_gui_send_input_event_request_process(const PB_Main* request, voi
         return;
     }
 
-    PubSub* input_events = furi_record_open("input_events");
+    FuriPubSub* input_events = furi_record_open("input_events");
     furi_check(input_events);
-    notify_pubsub(input_events, &event);
+    furi_pubsub_publish(input_events, &event);
     furi_record_close("input_events");
     rpc_send_and_release_empty(rpc_gui->rpc, request->command_id, PB_CommandStatus_OK);
 }
@@ -142,11 +142,13 @@ void* rpc_system_gui_alloc(Rpc* rpc) {
     rpc_handler.message_handler = rpc_system_gui_send_input_event_request_process;
     rpc_add_handler(rpc, PB_Main_gui_send_input_event_request_tag, &rpc_handler);
 
-    return NULL;
+    return rpc_gui;
 }
 
 void rpc_system_gui_free(void* ctx) {
+    furi_assert(ctx);
     RpcGuiSystem* rpc_gui = ctx;
+    furi_assert(rpc_gui->gui);
     gui_set_framebuffer_callback(rpc_gui->gui, NULL, NULL);
     furi_record_close("gui");
     free(rpc_gui);

+ 3 - 0
applications/rpc/rpc_i.h

@@ -1,5 +1,6 @@
 #pragma once
 #include "rpc.h"
+#include "storage/filesystem-api-defines.h"
 #include <pb.h>
 #include <pb_decode.h>
 #include <pb_encode.h>
@@ -29,3 +30,5 @@ void rpc_system_gui_free(void* ctx);
 
 void rpc_print_message(const PB_Main* message);
 void rpc_cli_command_start_session(Cli* cli, string_t args, void* context);
+
+PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error);

+ 39 - 3
applications/rpc/rpc_storage.c

@@ -51,7 +51,7 @@ static void rpc_system_storage_reset_state(RpcStorageSystem* rpc_storage, bool s
     }
 }
 
-static PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error) {
+PB_CommandStatus rpc_system_storage_get_error(FS_Error fs_error) {
     PB_CommandStatus pb_error;
     switch(fs_error) {
     case FSE_OK:
@@ -96,6 +96,40 @@ 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_stat_process(const PB_Main* request, void* context) {
+    furi_assert(request);
+    furi_assert(context);
+    furi_assert(request->which_content == PB_Main_storage_stat_request_tag);
+
+    RpcStorageSystem* rpc_storage = context;
+    rpc_system_storage_reset_state(rpc_storage, true);
+
+    PB_Main* response = furi_alloc(sizeof(PB_Main));
+    response->command_id = request->command_id;
+
+    Storage* fs_api = furi_record_open("storage");
+
+    const char* path = request->content.storage_stat_request.path;
+    FileInfo fileinfo;
+    FS_Error error = storage_common_stat(fs_api, path, &fileinfo);
+
+    response->command_status = rpc_system_storage_get_error(error);
+    response->which_content = PB_Main_empty_tag;
+
+    if(error == FSE_OK) {
+        response->which_content = PB_Main_storage_stat_response_tag;
+        response->content.storage_stat_response.has_file = true;
+        response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ?
+                                                                PB_Storage_File_FileType_DIR :
+                                                                PB_Storage_File_FileType_FILE;
+        response->content.storage_stat_response.file.size = fileinfo.size;
+    }
+
+    rpc_send_and_release(rpc_storage->rpc, response);
+    free(response);
+    furi_record_close("storage");
+}
+
 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"};
@@ -140,11 +174,10 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex
     PB_Main response = {
         .command_id = request->command_id,
         .has_next = false,
-        .which_content = PB_Main_storage_list_request_tag,
+        .which_content = PB_Main_storage_list_response_tag,
         .command_status = PB_CommandStatus_OK,
     };
     PB_Storage_ListResponse* list = &response.content.storage_list_response;
-    response.which_content = PB_Main_storage_list_response_tag;
 
     bool finish = false;
     int i = 0;
@@ -434,6 +467,9 @@ void* rpc_system_storage_alloc(Rpc* rpc) {
         .context = rpc_storage,
     };
 
+    rpc_handler.message_handler = rpc_system_storage_stat_process;
+    rpc_add_handler(rpc, PB_Main_storage_stat_request_tag, &rpc_handler);
+
     rpc_handler.message_handler = rpc_system_storage_list_process;
     rpc_add_handler(rpc, PB_Main_storage_list_request_tag, &rpc_handler);
 

+ 2 - 0
applications/storage/storage-glue.h

@@ -1,8 +1,10 @@
 #pragma once
+
 #include <furi.h>
 #include "filesystem-api-internal.h"
 #include <m-string.h>
 #include <m-array.h>
+#include <m-list.h>
 
 #ifdef __cplusplus
 extern "C" {

+ 1 - 0
applications/subghz/subghz_cli.c

@@ -3,6 +3,7 @@
 #include <furi.h>
 #include <furi-hal.h>
 #include <stream_buffer.h>
+
 #include <lib/toolbox/args.h>
 #include <lib/subghz/subghz_parser.h>
 #include <lib/subghz/subghz_keystore.h>

+ 11 - 20
applications/tests/furi_pubsub_test.c

@@ -16,39 +16,30 @@ void test_pubsub_handler(const void* arg, void* ctx) {
 }
 
 void test_furi_pubsub() {
-    bool result;
-    PubSub test_pubsub;
-    PubSubItem* test_pubsub_item;
+    FuriPubSub* test_pubsub = NULL;
+    FuriPubSubSubscription* test_pubsub_subscription = NULL;
 
     // init pubsub case
-    result = init_pubsub(&test_pubsub);
-    mu_assert(result, "init pubsub failed");
+    test_pubsub = furi_pubsub_alloc();
+    mu_assert_pointers_not_eq(test_pubsub, NULL);
 
     // subscribe pubsub case
-    test_pubsub_item = subscribe_pubsub(&test_pubsub, test_pubsub_handler, (void*)&context_value);
-    mu_assert_pointers_not_eq(test_pubsub_item, NULL);
+    test_pubsub_subscription =
+        furi_pubsub_subscribe(test_pubsub, test_pubsub_handler, (void*)&context_value);
+    mu_assert_pointers_not_eq(test_pubsub_subscription, NULL);
 
     /// notify pubsub case
-    result = notify_pubsub(&test_pubsub, (void*)&notify_value_0);
-    mu_assert(result, "notify pubsub failed");
+    furi_pubsub_publish(test_pubsub, (void*)&notify_value_0);
     mu_assert_int_eq(pubsub_value, notify_value_0);
     mu_assert_int_eq(pubsub_context_value, context_value);
 
     // unsubscribe pubsub case
-    result = unsubscribe_pubsub(test_pubsub_item);
-    mu_assert(result, "unsubscribe pubsub failed");
-
-    result = unsubscribe_pubsub(test_pubsub_item);
-    mu_assert(!result, "unsubscribe pubsub not failed");
+    furi_pubsub_unsubscribe(test_pubsub, test_pubsub_subscription);
 
     /// notify unsubscribed pubsub case
-    result = notify_pubsub(&test_pubsub, (void*)&notify_value_1);
-    mu_assert(result, "notify pubsub failed");
+    furi_pubsub_publish(test_pubsub, (void*)&notify_value_1);
     mu_assert_int_not_eq(pubsub_value, notify_value_1);
 
     // delete pubsub case
-    result = delete_pubsub(&test_pubsub);
-    mu_assert(result, "unsubscribe pubsub failed");
-
-    // TODO test case that the pubsub_delete will remove pubsub from heap
+    furi_pubsub_free(test_pubsub);
 }

+ 72 - 4
applications/tests/rpc/rpc_test.c

@@ -218,6 +218,9 @@ static void test_rpc_create_simple_message(
     message->which_content = tag;
     message->has_next = false;
     switch(tag) {
+    case PB_Main_storage_stat_request_tag:
+        message->content.storage_stat_request.path = str_copy;
+        break;
     case PB_Main_storage_list_request_tag:
         message->content.storage_list_request.path = str_copy;
         break;
@@ -369,6 +372,19 @@ static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected) {
         mu_check(result_locked == expected_locked);
         break;
     }
+    case PB_Main_storage_stat_response_tag: {
+        bool result_has_msg_file = result->content.storage_stat_response.has_file;
+        bool expected_has_msg_file = expected->content.storage_stat_response.has_file;
+        mu_check(result_has_msg_file == expected_has_msg_file);
+
+        if(result_has_msg_file) {
+            PB_Storage_File* result_msg_file = &result->content.storage_stat_response.file;
+            PB_Storage_File* expected_msg_file = &expected->content.storage_stat_response.file;
+            test_rpc_compare_file(result_msg_file, expected_msg_file);
+        } else {
+            mu_check(0);
+        }
+    } break;
     case PB_Main_storage_read_response_tag: {
         bool result_has_msg_file = result->content.storage_read_response.has_file;
         bool expected_has_msg_file = expected->content.storage_read_response.has_file;
@@ -455,11 +471,10 @@ static void test_rpc_storage_list_create_expected_list(
     PB_Main response = {
         .command_id = command_id,
         .has_next = false,
-        .which_content = PB_Main_storage_list_request_tag,
+        .which_content = PB_Main_storage_list_response_tag,
         /* other fields (e.g. msg_files ptrs) explicitly initialized by 0 */
     };
     PB_Storage_ListResponse* list = &response.content.storage_list_response;
-    response.which_content = PB_Main_storage_list_response_tag;
 
     bool finish = false;
     int i = 0;
@@ -649,9 +664,8 @@ 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));
-
+    furi_record_close("storage");
     return result == FSE_OK;
 }
 
@@ -687,6 +701,59 @@ static void test_create_file(const char* path, size_t size) {
     furi_check(test_is_exists(path));
 }
 
+static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) {
+    PB_Main request;
+    MsgList_t expected_msg_list;
+    MsgList_init(expected_msg_list);
+
+    test_rpc_create_simple_message(&request, PB_Main_storage_stat_request_tag, path, command_id);
+
+    Storage* fs_api = furi_record_open("storage");
+    FileInfo fileinfo;
+    FS_Error error = storage_common_stat(fs_api, path, &fileinfo);
+    furi_record_close("storage");
+
+    PB_Main* response = MsgList_push_new(expected_msg_list);
+    response->command_id = command_id;
+    response->command_status = rpc_system_storage_get_error(error);
+    response->has_next = false;
+    response->which_content = PB_Main_empty_tag;
+
+    if(error == FSE_OK) {
+        response->which_content = PB_Main_storage_stat_response_tag;
+        response->content.storage_stat_response.has_file = true;
+        response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ?
+                                                                PB_Storage_File_FileType_DIR :
+                                                                PB_Storage_File_FileType_FILE;
+        response->content.storage_stat_response.file.size = fileinfo.size;
+    }
+
+    test_rpc_encode_and_feed_one(&request);
+    test_rpc_decode_and_compare(expected_msg_list);
+
+    pb_release(&PB_Main_msg, &request);
+    test_rpc_free_msg_list(expected_msg_list);
+}
+
+#define TEST_DIR_STAT_NAME TEST_DIR "stat_dir"
+#define TEST_DIR_STAT TEST_DIR_STAT_NAME "/"
+MU_TEST(test_storage_stat) {
+    test_create_dir(TEST_DIR_STAT_NAME);
+    test_create_file(TEST_DIR_STAT "empty.txt", 0);
+    test_create_file(TEST_DIR_STAT "l33t.txt", 1337);
+
+    test_rpc_storage_stat_run("/", ++command_id);
+    test_rpc_storage_stat_run("/int", ++command_id);
+    test_rpc_storage_stat_run("/ext", ++command_id);
+
+    test_rpc_storage_stat_run(TEST_DIR_STAT "empty.txt", ++command_id);
+    test_rpc_storage_stat_run(TEST_DIR_STAT "l33t.txt", ++command_id);
+    test_rpc_storage_stat_run(TEST_DIR_STAT "missing", ++command_id);
+    test_rpc_storage_stat_run(TEST_DIR_STAT_NAME, ++command_id);
+
+    test_rpc_storage_stat_run(TEST_DIR_STAT, ++command_id);
+}
+
 MU_TEST(test_storage_read) {
     test_create_file(TEST_DIR "empty.txt", 0);
     test_create_file(TEST_DIR "file1.txt", 1);
@@ -1138,6 +1205,7 @@ MU_TEST_SUITE(test_rpc_status) {
 MU_TEST_SUITE(test_rpc_storage) {
     MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown);
 
+    MU_RUN_TEST(test_storage_stat);
     MU_RUN_TEST(test_storage_list);
     MU_RUN_TEST(test_storage_read);
     MU_RUN_TEST(test_storage_write_read);

+ 1 - 0
applications/tests/test_index.c

@@ -1,4 +1,5 @@
 #include "m-string.h"
+
 #include <stdio.h>
 #include <furi.h>
 #include <furi-hal.h>

+ 11 - 3
assets/compiled/flipper.pb.h

@@ -78,6 +78,8 @@ typedef struct _PB_Main {
         PB_Gui_StopScreenStreamRequest gui_stop_screen_stream_request;
         PB_Gui_ScreenStreamFrame gui_screen_stream_frame;
         PB_Gui_SendInputEventRequest gui_send_input_event_request;
+        PB_Storage_StatRequest storage_stat_request;
+        PB_Storage_StatResponse storage_stat_response;
     } content; 
 } PB_Main;
 
@@ -124,6 +126,8 @@ extern "C" {
 #define PB_Main_gui_stop_screen_stream_request_tag 21
 #define PB_Main_gui_screen_stream_frame_tag      22
 #define PB_Main_gui_send_input_event_request_tag 23
+#define PB_Main_storage_stat_request_tag         24
+#define PB_Main_storage_stat_response_tag        25
 
 /* Struct field encoding specification for nanopb */
 #define PB_Empty_FIELDLIST(X, a) \
@@ -159,7 +163,9 @@ X(a, STATIC,   ONEOF,    MSG_W_CB, (content,stop_session,content.stop_session),
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,gui_start_screen_stream_request,content.gui_start_screen_stream_request),  20) \
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,gui_stop_screen_stream_request,content.gui_stop_screen_stream_request),  21) \
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,gui_screen_stream_frame,content.gui_screen_stream_frame),  22) \
-X(a, STATIC,   ONEOF,    MSG_W_CB, (content,gui_send_input_event_request,content.gui_send_input_event_request),  23)
+X(a, STATIC,   ONEOF,    MSG_W_CB, (content,gui_send_input_event_request,content.gui_send_input_event_request),  23) \
+X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_stat_request,content.storage_stat_request),  24) \
+X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_stat_response,content.storage_stat_response),  25)
 #define PB_Main_CALLBACK NULL
 #define PB_Main_DEFAULT NULL
 #define PB_Main_content_empty_MSGTYPE PB_Empty
@@ -182,6 +188,8 @@ X(a, STATIC,   ONEOF,    MSG_W_CB, (content,gui_send_input_event_request,content
 #define PB_Main_content_gui_stop_screen_stream_request_MSGTYPE PB_Gui_StopScreenStreamRequest
 #define PB_Main_content_gui_screen_stream_frame_MSGTYPE PB_Gui_ScreenStreamFrame
 #define PB_Main_content_gui_send_input_event_request_MSGTYPE PB_Gui_SendInputEventRequest
+#define PB_Main_content_storage_stat_request_MSGTYPE PB_Storage_StatRequest
+#define PB_Main_content_storage_stat_response_MSGTYPE PB_Storage_StatResponse
 
 extern const pb_msgdesc_t PB_Empty_msg;
 extern const pb_msgdesc_t PB_StopSession_msg;
@@ -195,9 +203,9 @@ extern const pb_msgdesc_t 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_StartRequest_size) && defined(PB_Gui_ScreenStreamFrame_size)
+#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_StartRequest_size) && defined(PB_Gui_ScreenStreamFrame_size) && defined(PB_Storage_StatRequest_size) && defined(PB_Storage_StatResponse_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_StartRequest_size)]; char f22[(7 + PB_Gui_ScreenStreamFrame_size)]; char f0[36];};
+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_StartRequest_size)]; char f22[(7 + PB_Gui_ScreenStreamFrame_size)]; char f24[(7 + PB_Storage_StatRequest_size)]; char f25[(7 + PB_Storage_StatResponse_size)]; char f0[36];};
 #endif
 
 #ifdef __cplusplus

+ 6 - 0
assets/compiled/storage.pb.c

@@ -9,6 +9,12 @@
 PB_BIND(PB_Storage_File, PB_Storage_File, AUTO)
 
 
+PB_BIND(PB_Storage_StatRequest, PB_Storage_StatRequest, AUTO)
+
+
+PB_BIND(PB_Storage_StatResponse, PB_Storage_StatResponse, AUTO)
+
+
 PB_BIND(PB_Storage_ListRequest, PB_Storage_ListRequest, AUTO)
 
 

+ 32 - 0
assets/compiled/storage.pb.h

@@ -32,6 +32,10 @@ typedef struct _PB_Storage_ReadRequest {
     char *path; 
 } PB_Storage_ReadRequest;
 
+typedef struct _PB_Storage_StatRequest { 
+    char *path; 
+} PB_Storage_StatRequest;
+
 typedef struct _PB_Storage_DeleteRequest { 
     char *path; 
     bool recursive; 
@@ -58,6 +62,11 @@ typedef struct _PB_Storage_ReadResponse {
     PB_Storage_File file; 
 } PB_Storage_ReadResponse;
 
+typedef struct _PB_Storage_StatResponse { 
+    bool has_file;
+    PB_Storage_File file; 
+} PB_Storage_StatResponse;
+
 typedef struct _PB_Storage_WriteRequest { 
     char *path; 
     bool has_file;
@@ -77,6 +86,8 @@ extern "C" {
 
 /* Initializer values for message structs */
 #define PB_Storage_File_init_default             {_PB_Storage_File_FileType_MIN, NULL, 0, NULL}
+#define PB_Storage_StatRequest_init_default      {NULL}
+#define PB_Storage_StatResponse_init_default     {false, PB_Storage_File_init_default}
 #define PB_Storage_ListRequest_init_default      {NULL}
 #define PB_Storage_ListResponse_init_default     {0, {PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default, PB_Storage_File_init_default}}
 #define PB_Storage_ReadRequest_init_default      {NULL}
@@ -87,6 +98,8 @@ extern "C" {
 #define PB_Storage_Md5sumRequest_init_default    {NULL}
 #define PB_Storage_Md5sumResponse_init_default   {""}
 #define PB_Storage_File_init_zero                {_PB_Storage_File_FileType_MIN, NULL, 0, NULL}
+#define PB_Storage_StatRequest_init_zero         {NULL}
+#define PB_Storage_StatResponse_init_zero        {false, PB_Storage_File_init_zero}
 #define PB_Storage_ListRequest_init_zero         {NULL}
 #define PB_Storage_ListResponse_init_zero        {0, {PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero, PB_Storage_File_init_zero}}
 #define PB_Storage_ReadRequest_init_zero         {NULL}
@@ -102,6 +115,7 @@ extern "C" {
 #define PB_Storage_Md5sumRequest_path_tag        1
 #define PB_Storage_MkdirRequest_path_tag         1
 #define PB_Storage_ReadRequest_path_tag          1
+#define PB_Storage_StatRequest_path_tag          1
 #define PB_Storage_DeleteRequest_path_tag        1
 #define PB_Storage_DeleteRequest_recursive_tag   2
 #define PB_Storage_File_type_tag                 1
@@ -111,6 +125,7 @@ extern "C" {
 #define PB_Storage_Md5sumResponse_md5sum_tag     1
 #define PB_Storage_ListResponse_file_tag         1
 #define PB_Storage_ReadResponse_file_tag         1
+#define PB_Storage_StatResponse_file_tag         1
 #define PB_Storage_WriteRequest_path_tag         1
 #define PB_Storage_WriteRequest_file_tag         2
 
@@ -123,6 +138,17 @@ X(a, POINTER,  SINGULAR, BYTES,    data,              4)
 #define PB_Storage_File_CALLBACK NULL
 #define PB_Storage_File_DEFAULT NULL
 
+#define PB_Storage_StatRequest_FIELDLIST(X, a) \
+X(a, POINTER,  SINGULAR, STRING,   path,              1)
+#define PB_Storage_StatRequest_CALLBACK NULL
+#define PB_Storage_StatRequest_DEFAULT NULL
+
+#define PB_Storage_StatResponse_FIELDLIST(X, a) \
+X(a, STATIC,   OPTIONAL, MESSAGE,  file,              1)
+#define PB_Storage_StatResponse_CALLBACK NULL
+#define PB_Storage_StatResponse_DEFAULT NULL
+#define PB_Storage_StatResponse_file_MSGTYPE PB_Storage_File
+
 #define PB_Storage_ListRequest_FIELDLIST(X, a) \
 X(a, POINTER,  SINGULAR, STRING,   path,              1)
 #define PB_Storage_ListRequest_CALLBACK NULL
@@ -174,6 +200,8 @@ X(a, STATIC,   SINGULAR, STRING,   md5sum,            1)
 #define PB_Storage_Md5sumResponse_DEFAULT NULL
 
 extern const pb_msgdesc_t PB_Storage_File_msg;
+extern const pb_msgdesc_t PB_Storage_StatRequest_msg;
+extern const pb_msgdesc_t PB_Storage_StatResponse_msg;
 extern const pb_msgdesc_t PB_Storage_ListRequest_msg;
 extern const pb_msgdesc_t PB_Storage_ListResponse_msg;
 extern const pb_msgdesc_t PB_Storage_ReadRequest_msg;
@@ -186,6 +214,8 @@ extern const pb_msgdesc_t PB_Storage_Md5sumResponse_msg;
 
 /* Defines for backwards compatibility with code written before nanopb-0.4.0 */
 #define PB_Storage_File_fields &PB_Storage_File_msg
+#define PB_Storage_StatRequest_fields &PB_Storage_StatRequest_msg
+#define PB_Storage_StatResponse_fields &PB_Storage_StatResponse_msg
 #define PB_Storage_ListRequest_fields &PB_Storage_ListRequest_msg
 #define PB_Storage_ListResponse_fields &PB_Storage_ListResponse_msg
 #define PB_Storage_ReadRequest_fields &PB_Storage_ReadRequest_msg
@@ -198,6 +228,8 @@ extern const pb_msgdesc_t PB_Storage_Md5sumResponse_msg;
 
 /* Maximum encoded size of messages (where known) */
 /* PB_Storage_File_size depends on runtime parameters */
+/* PB_Storage_StatRequest_size depends on runtime parameters */
+/* PB_Storage_StatResponse_size depends on runtime parameters */
 /* PB_Storage_ListRequest_size depends on runtime parameters */
 /* PB_Storage_ListResponse_size depends on runtime parameters */
 /* PB_Storage_ReadRequest_size depends on runtime parameters */

+ 1 - 1
assets/protobuf

@@ -1 +1 @@
-Subproject commit 76f43b8c6510306d40c006b696d9d1b14a252dc1
+Subproject commit 0e6d374ab1a12f95a3cd04444376a261e7252db4

+ 4 - 0
core/furi.h

@@ -1,5 +1,9 @@
 #pragma once
 
+#include <FreeRTOS.h>
+#include <timers.h>
+#include <task.h>
+
 #include <cmsis_os2.h>
 
 #include <furi/common_defines.h>

+ 74 - 67
core/furi/pubsub.c

@@ -1,88 +1,95 @@
 #include "pubsub.h"
-#include <furi.h>
+#include "memmgr.h"
+#include "check.h"
+
+#include <m-list.h>
+#include <cmsis_os2.h>
+
+struct FuriPubSubSubscription {
+    FuriPubSubCallback callback;
+    void* callback_context;
+};
+
+LIST_DEF(FuriPubSubSubscriptionList, FuriPubSubSubscription, M_POD_OPLIST);
+
+struct FuriPubSub {
+    FuriPubSubSubscriptionList_t items;
+    osMutexId_t mutex;
+};
+
+FuriPubSub* furi_pubsub_alloc() {
+    FuriPubSub* pubsub = furi_alloc(sizeof(FuriPubSub));
 
-bool init_pubsub(PubSub* pubsub) {
-    // mutex without name,
-    // no attributes (unfortunatly robust mutex is not supported by FreeRTOS),
-    // with dynamic memory allocation
     pubsub->mutex = osMutexNew(NULL);
-    if(pubsub->mutex == NULL) return false;
+    furi_assert(pubsub->mutex);
 
-    // construct list
-    list_pubsub_cb_init(pubsub->items);
+    FuriPubSubSubscriptionList_init(pubsub->items);
 
-    return true;
+    return pubsub;
 }
 
-bool delete_pubsub(PubSub* pubsub) {
-    if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) {
-        bool result = osMutexDelete(pubsub->mutex) == osOK;
-        list_pubsub_cb_clear(pubsub->items);
-        return result;
-    } else {
-        return false;
-    }
-}
+void furi_pubsub_free(FuriPubSub* pubsub) {
+    furi_assert(pubsub);
 
-PubSubItem* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) {
-    if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) {
-        // put uninitialized item to the list
-        PubSubItem* item = list_pubsub_cb_push_raw(pubsub->items);
+    furi_check(FuriPubSubSubscriptionList_size(pubsub->items) == 0);
 
-        // initialize item
-        item->cb = cb;
-        item->ctx = ctx;
-        item->self = pubsub;
+    FuriPubSubSubscriptionList_clear(pubsub->items);
 
-        // TODO unsubscribe pubsub on app exit
-        //flapp_on_exit(unsubscribe_pubsub, item);
+    furi_check(osMutexDelete(pubsub->mutex) == osOK);
 
-        osMutexRelease(pubsub->mutex);
+    free(pubsub);
+}
 
-        return item;
-    } else {
-        return NULL;
-    }
+FuriPubSubSubscription*
+    furi_pubsub_subscribe(FuriPubSub* pubsub, FuriPubSubCallback callback, void* callback_context) {
+    furi_check(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK);
+    // put uninitialized item to the list
+    FuriPubSubSubscription* item = FuriPubSubSubscriptionList_push_raw(pubsub->items);
+
+    // initialize item
+    item->callback = callback;
+    item->callback_context = callback_context;
+
+    furi_check(osMutexRelease(pubsub->mutex) == osOK);
+
+    return item;
 }
 
-bool unsubscribe_pubsub(PubSubItem* pubsub_id) {
-    if(osMutexAcquire(pubsub_id->self->mutex, osWaitForever) == osOK) {
-        bool result = false;
-
-        // iterate over items
-        list_pubsub_cb_it_t it;
-        for(list_pubsub_cb_it(it, pubsub_id->self->items); !list_pubsub_cb_end_p(it);
-            list_pubsub_cb_next(it)) {
-            const PubSubItem* item = list_pubsub_cb_cref(it);
-
-            // if the iterator is equal to our element
-            if(item == pubsub_id) {
-                list_pubsub_cb_remove(pubsub_id->self->items, it);
-                result = true;
-                break;
-            }
-        }
+void furi_pubsub_unsubscribe(FuriPubSub* pubsub, FuriPubSubSubscription* pubsub_subscription) {
+    furi_assert(pubsub);
+    furi_assert(pubsub_subscription);
+
+    furi_check(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK);
+    bool result = false;
+
+    // iterate over items
+    FuriPubSubSubscriptionList_it_t it;
+    for(FuriPubSubSubscriptionList_it(it, pubsub->items); !FuriPubSubSubscriptionList_end_p(it);
+        FuriPubSubSubscriptionList_next(it)) {
+        const FuriPubSubSubscription* item = FuriPubSubSubscriptionList_cref(it);
 
-        osMutexRelease(pubsub_id->self->mutex);
-        return result;
-    } else {
-        return false;
+        // if the iterator is equal to our element
+        if(item == pubsub_subscription) {
+            FuriPubSubSubscriptionList_remove(pubsub->items, it);
+            result = true;
+            break;
+        }
     }
+
+    furi_check(osMutexRelease(pubsub->mutex) == osOK);
+    furi_check(result);
 }
 
-bool notify_pubsub(PubSub* pubsub, void* arg) {
-    if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) {
-        // iterate over subscribers
-        list_pubsub_cb_it_t it;
-        for(list_pubsub_cb_it(it, pubsub->items); !list_pubsub_cb_end_p(it);
-            list_pubsub_cb_next(it)) {
-            const PubSubItem* item = list_pubsub_cb_cref(it);
-            item->cb(arg, item->ctx);
-        }
+void furi_pubsub_publish(FuriPubSub* pubsub, void* message) {
+    furi_check(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK);
 
-        osMutexRelease(pubsub->mutex);
-        return true;
-    } else {
-        return false;
+    // iterate over subscribers
+    FuriPubSubSubscriptionList_it_t it;
+    for(FuriPubSubSubscriptionList_it(it, pubsub->items); !FuriPubSubSubscriptionList_end_p(it);
+        FuriPubSubSubscriptionList_next(it)) {
+        const FuriPubSubSubscription* item = FuriPubSubSubscriptionList_cref(it);
+        item->callback(message, item->callback_context);
     }
+
+    furi_check(osMutexRelease(pubsub->mutex) == osOK);
 }

+ 45 - 76
core/furi/pubsub.h

@@ -1,95 +1,64 @@
 #pragma once
 
-#include "cmsis_os.h"
-#include "m-list.h"
-
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-/**
-== PubSub ==
-
- * PubSub allows users to subscribe on notifies and notify subscribers.
- * Notifier side can pass `void*` arg to subscriber callback,
- * and also subscriber can set `void*` context pointer that pass into
- * callback (you can see callback signature below).
- */
-
-typedef void (*PubSubCallback)(const void*, void*);
-typedef struct PubSubType PubSub;
-
-typedef struct {
-    PubSubCallback cb;
-    void* ctx;
-    PubSub* self;
-} PubSubItem;
+/** FuriPubSub Callback type */
+typedef void (*FuriPubSubCallback)(const void* message, void* context);
 
-LIST_DEF(list_pubsub_cb, PubSubItem, M_POD_OPLIST);
+/** FuriPubSub type */
+typedef struct FuriPubSub FuriPubSub;
 
-struct PubSubType {
-    list_pubsub_cb_t items;
-    osMutexId_t mutex;
-};
+/** FuriPubSubSubscription type */
+typedef struct FuriPubSubSubscription FuriPubSubSubscription;
 
-/**
- * To create PubSub you should create PubSub instance and call `init_pubsub`.
+/** Allocate FuriPubSub
+ *
+ * Reentrable, Not threadsafe, one owner
+ *
+ * @return     pointer to FuriPubSub instance
  */
-bool init_pubsub(PubSub* pubsub);
+FuriPubSub* furi_pubsub_alloc();
 
-/**
- * Since we use dynamic memory - we must explicity delete pubsub
+/** Free FuriPubSub
+ * 
+ * @param      pubsub  FuriPubSub instance
  */
-bool delete_pubsub(PubSub* pubsub);
-
-/**
- * Use `subscribe_pubsub` to register your callback.
+void furi_pubsub_free(FuriPubSub* pubsub);
+
+/** Subscribe to FuriPubSub
+ * 
+ * Threadsafe, Reentrable
+ * 
+ * @param      pubsub            pointer to FuriPubSub instance
+ * @param[in]  callback          The callback
+ * @param      callback_context  The callback context
+ *
+ * @return     pointer to FuriPubSubSubscription instance
  */
-PubSubItem* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx);
-
-/**
- * Use `unsubscribe_pubsub` to unregister callback.
+FuriPubSubSubscription*
+    furi_pubsub_subscribe(FuriPubSub* pubsub, FuriPubSubCallback callback, void* callback_context);
+
+/** Unsubscribe from FuriPubSub
+ * 
+ * No use of `pubsub_subscription` allowed after call of this method
+ * Threadsafe, Reentrable.
+ *
+ * @param      pubsub               pointer to FuriPubSub instance
+ * @param      pubsub_subscription  pointer to FuriPubSubSubscription instance
  */
-bool unsubscribe_pubsub(PubSubItem* pubsub_id);
-
-/**
- * Use `notify_pubsub` to notify subscribers.
+void furi_pubsub_unsubscribe(FuriPubSub* pubsub, FuriPubSubSubscription* pubsub_subscription);
+
+/** Publish message to FuriPubSub
+ *
+ * Threadsafe, Reentrable.
+ * 
+ * @param      pubsub   pointer to FuriPubSub instance
+ * @param      message  message pointer to publish
  */
-bool notify_pubsub(PubSub* pubsub, void* arg);
+void furi_pubsub_publish(FuriPubSub* pubsub, void* message);
 
 #ifdef __cplusplus
 }
 #endif
-
-/*
-
-```C
-// MANIFEST
-// name="test"
-// stack=128
-
-void example_pubsub_handler(void* arg, void* ctx) {
-    printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx);
-}
-
-void pubsub_test() {
-    const char* app_name = "test app";
-
-    PubSub example_pubsub;
-    init_pubsub(&example_pubsub);
-
-    if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) {
-        printf("critical error\n");
-        flapp_exit(NULL);
-    }
-
-    uint32_t counter = 0;
-    while(1) {
-        notify_pubsub(&example_pubsub, (void*)&counter);
-        counter++;
-
-        osDelay(100);
-    }
-}
-```
-*/

+ 1 - 1
core/furi/record.c

@@ -84,8 +84,8 @@ bool furi_record_destroy(const char* name) {
     FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str);
     furi_assert(record_data);
     if(record_data->holders_count == 0) {
-        FuriRecordDataDict_erase(furi_record->records, name_str);
         furi_check(osOK == osEventFlagsDelete(record_data->flags));
+        FuriRecordDataDict_erase(furi_record->records, name_str);
         ret = true;
     }
 

+ 3 - 0
core/furi/stdglue.c

@@ -2,6 +2,9 @@
 #include "check.h"
 #include "memmgr.h"
 
+#include <FreeRTOS.h>
+#include <task.h>
+
 #include <furi-hal.h>
 #include <m-dict.h>
 

+ 1 - 1
firmware/targets/f6/furi-hal/furi-hal-spi.c

@@ -133,7 +133,7 @@ const FuriHalSpiDevice* furi_hal_spi_device_get(FuriHalSpiDeviceId device_id) {
     furi_assert(device_id < FuriHalSpiDeviceIdMax);
 
     const FuriHalSpiDevice* device = &furi_hal_spi_devices[device_id];
-    assert(device);
+    furi_assert(device);
     furi_hal_spi_bus_lock(device->bus);
     furi_hal_spi_device_configure(device);
 

+ 1 - 1
firmware/targets/f6/furi-hal/furi-hal-subghz.c

@@ -676,7 +676,7 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) {
             }
 
             uint32_t duration = level_duration_get_duration(ld);
-            assert(duration > 0);
+            furi_assert(duration > 0);
             *buffer = duration;
             buffer++;
             samples--;

+ 1 - 1
firmware/targets/f7/furi-hal/furi-hal-spi.c

@@ -133,7 +133,7 @@ const FuriHalSpiDevice* furi_hal_spi_device_get(FuriHalSpiDeviceId device_id) {
     furi_assert(device_id < FuriHalSpiDeviceIdMax);
 
     const FuriHalSpiDevice* device = &furi_hal_spi_devices[device_id];
-    assert(device);
+    furi_assert(device);
     furi_hal_spi_bus_lock(device->bus);
     furi_hal_spi_device_configure(device);
 

+ 1 - 1
firmware/targets/f7/furi-hal/furi-hal-subghz.c

@@ -676,7 +676,7 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) {
             }
 
             uint32_t duration = level_duration_get_duration(ld);
-            assert(duration > 0);
+            furi_assert(duration > 0);
             *buffer = duration;
             buffer++;
             samples--;

+ 2 - 2
lib/common-api/task-control-block.h

@@ -1,7 +1,7 @@
 #pragma once
 
-#include "FreeRTOS.h"
-
+#include <FreeRTOS.h>
+#include <task.h>
 
 typedef struct 			/* The old naming convention is used to prevent breaking kernel aware debuggers. */
 {

+ 1 - 0
lib/irda/worker/irda_worker.c

@@ -7,6 +7,7 @@
 #include <limits.h>
 #include <stdint.h>
 #include <furi.h>
+
 #include <notification/notification-messages.h>
 #include <stream_buffer.h>