diff --git a/CMakeLists.txt b/CMakeLists.txt index 39bcb99..4341c0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,21 @@ add_executable(${PROJECT_NAME} set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) +# C Interface shared library +add_library(${PROJECT_NAME}_c SHARED + src/notifly_c.cpp +) + +set_target_properties(${PROJECT_NAME}_c PROPERTIES + LINKER_LANGUAGE CXX + OUTPUT_NAME "notifly_c" + VERSION 1.0.0 + SOVERSION 1 +) + +target_compile_features(${PROJECT_NAME}_c PUBLIC cxx_std_20) +target_include_directories(${PROJECT_NAME}_c PUBLIC include) + include(FetchContent) # @@ -53,6 +68,28 @@ set_target_properties(${UNIT_TEST} MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug> ) +# C Interface test +set(C_TEST notifly_c_test) +add_executable(${C_TEST} test/test_c_interface.c) +target_link_libraries(${C_TEST} PRIVATE ${PROJECT_NAME}_c) +target_include_directories(${C_TEST} PRIVATE include) +set_target_properties(${C_TEST} + PROPERTIES + OUTPUT_NAME ${C_TEST} + LINKER_LANGUAGE C + ) + +# C Interface example +set(C_EXAMPLE notifly_c_example) +add_executable(${C_EXAMPLE} example/c_example.c) +target_link_libraries(${C_EXAMPLE} PRIVATE ${PROJECT_NAME}_c) +target_include_directories(${C_EXAMPLE} PRIVATE include) +set_target_properties(${C_EXAMPLE} + PROPERTIES + OUTPUT_NAME ${C_EXAMPLE} + LINKER_LANGUAGE C + ) + # Function to copy a file only if it does not exist or is different function(move_file source_file destination_dir) # Check if the source file exists diff --git a/README.md b/README.md index be11c97..97631f9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,37 @@ This project was originally forked from https://github.com/Geenz/CPP-Notificatio A C++ API inspired by Cocoa's NSNotificationCenter API. -## Usage +## C Interface + +Notifly now includes a **C interface** that provides access to the notification center functionality from C programs through a shared library (DLL/SO). This allows you to use Notifly from C projects while maintaining the performance and features of the C++ implementation. + +**Key features of the C interface:** +- Shared library (`libnotifly_c.so`/`notifly_c.dll`) for easy integration +- Handle-based API for type safety +- Function pointer callbacks +- Synchronous and asynchronous notification posting +- Full compatibility with the C++ API functionality + +See [docs/C_INTERFACE.md](docs/C_INTERFACE.md) for complete documentation and examples. + +**Quick C example:** +```c +#include "notifly_c.h" + +void my_callback(int notification_id, void* data, void* user_data) { + printf("Received notification %d\n", notification_id); +} + +int main() { + notifly_handle notifly = notifly_default(); + int observer_id = notifly_add_observer(notifly, 1001, my_callback, NULL); + notifly_post_notification(notifly, 1001, NULL); + notifly_remove_observer(notifly, observer_id); + return 0; +} +``` + +## C++ API Usage Using `notifly` is simple. In order to use the default center, simply use the static method `notifly::default_notifly()` like so: diff --git a/docs/C_INTERFACE.md b/docs/C_INTERFACE.md new file mode 100644 index 0000000..250bb98 --- /dev/null +++ b/docs/C_INTERFACE.md @@ -0,0 +1,186 @@ +# Notifly C Interface Documentation + +This document describes the C interface for the Notifly notification center library. + +## Overview + +The C interface provides access to Notifly functionality from C programs through a shared library (`libnotifly_c.so` on Linux, `notifly_c.dll` on Windows). The interface wraps the C++ API with a C-compatible API using: + +- Opaque handle-based design for type safety +- Function pointer callbacks instead of C++ std::function +- Simple void pointer data payloads +- Standard C error codes + +## API Reference + +### Types + +```c +typedef struct notifly_instance* notifly_handle; +typedef void (*notifly_callback)(int notification_id, void* data, void* user_data); + +typedef enum { + NOTIFLY_SUCCESS = 0, + NOTIFLY_OBSERVER_NOT_FOUND = -1, + NOTIFLY_NOTIFICATION_NOT_FOUND = -2, + NOTIFLY_PAYLOAD_TYPE_NOT_MATCH = -3, + NOTIFLY_NO_MORE_OBSERVER_IDS = -4, + NOTIFLY_INVALID_HANDLE = -5 +} notifly_result_t; +``` + +### Instance Management + +```c +// Create a new notifly instance +notifly_handle notifly_create(void); + +// Destroy a notifly instance (only for instances created with notifly_create) +void notifly_destroy(notifly_handle handle); + +// Get the default global notifly instance +notifly_handle notifly_default(void); +``` + +### Observer Management + +```c +// Add an observer for a specific notification +int notifly_add_observer(notifly_handle handle, int notification_id, + notifly_callback callback, void* user_data); + +// Remove a specific observer by ID +int notifly_remove_observer(notifly_handle handle, int observer_id); + +// Remove all observers for a notification +int notifly_remove_all_observers(notifly_handle handle, int notification_id); +``` + +### Notification Posting + +```c +// Post a notification synchronously +int notifly_post_notification(notifly_handle handle, int notification_id, void* data); + +// Post a notification asynchronously +int notifly_post_notification_async(notifly_handle handle, int notification_id, void* data); +``` + +### Utility Functions + +```c +// Convert error code to human-readable string +const char* notifly_result_to_string(int result); +``` + +## Usage Example + +```c +#include "notifly_c.h" +#include + +// Callback function +void my_callback(int notification_id, void* data, void* user_data) { + printf("Received notification %d\n", notification_id); + if (data) { + int* value = (int*)data; + printf("Data: %d\n", *value); + } +} + +int main() { + // Get default instance + notifly_handle notifly = notifly_default(); + + // Add observer + int observer_id = notifly_add_observer(notifly, 1001, my_callback, NULL); + if (observer_id <= 0) { + printf("Failed to add observer: %s\n", notifly_result_to_string(observer_id)); + return 1; + } + + // Post notification + int data = 42; + int result = notifly_post_notification(notifly, 1001, &data); + if (result <= 0) { + printf("Failed to post notification: %s\n", notifly_result_to_string(result)); + return 1; + } + + printf("Notification sent to %d observers\n", result); + + // Cleanup + notifly_remove_observer(notifly, observer_id); + + return 0; +} +``` + +## Building + +The shared library is built using CMake: + +```bash +mkdir build && cd build +cmake .. +make +``` + +This creates: +- `libnotifly_c.so` (Linux) / `notifly_c.dll` (Windows) - the shared library +- `notifly_c_test` - C interface unit tests +- `notifly_c_example` - C interface usage example + +## Linking + +To use the C interface in your project: + +### CMake +```cmake +find_library(NOTIFLY_C_LIBRARY notifly_c) +target_link_libraries(your_target ${NOTIFLY_C_LIBRARY}) +target_include_directories(your_target PRIVATE /path/to/notifly/include) +``` + +### Direct compilation +```bash +gcc -o my_program my_program.c -lnotifly_c -I/path/to/notifly/include +``` + +## Memory Management + +- **Handles**: The default handle (`notifly_default()`) should never be destroyed. Only destroy handles created with `notifly_create()`. +- **Data**: The library does not take ownership of data passed to `notifly_post_notification()`. Ensure data remains valid during synchronous calls. +- **User data**: User data passed to `notifly_add_observer()` must remain valid until the observer is removed. + +## Thread Safety + +The C interface inherits the thread safety characteristics of the underlying C++ implementation: +- Multiple threads can safely add/remove observers and post notifications +- Callbacks may be invoked from different threads when using async notifications +- No additional locking is required in user code + +## Error Handling + +All functions return integer results: +- Positive values: Success (usually count of affected observers) +- Zero: Success with no side effects +- Negative values: Error codes (see `notifly_result_t` enum) + +Use `notifly_result_to_string()` to get human-readable error descriptions. + +## Limitations + +1. **Type Safety**: Unlike the C++ API, the C interface uses void pointers for data, sacrificing compile-time type checking for simplicity. +2. **Templates**: The C++ template-based type validation is not available in C. +3. **Complex Data**: Only simple data structures should be passed through the void pointer interface. + +## Migration from C++ API + +| C++ API | C API | +|---------|-------| +| `notifly::default_notifly()` | `notifly_default()` | +| `add_observer(id, callback)` | `notifly_add_observer(handle, id, callback, user_data)` | +| `remove_observer(id)` | `notifly_remove_observer(handle, id)` | +| `post_notification(id, args...)` | `notifly_post_notification(handle, id, &data)` | +| `post_notification_async(id, args...)` | `notifly_post_notification_async(handle, id, &data)` | \ No newline at end of file diff --git a/example/c_example.c b/example/c_example.c new file mode 100644 index 0000000..7433b5c --- /dev/null +++ b/example/c_example.c @@ -0,0 +1,171 @@ +/* + * c_example.c + * Simple example demonstrating the notifly C interface + */ + +#include "notifly_c.h" +#include +#include +#include +#include // for sleep + +// Message IDs +#define MSG_STARTUP 1001 +#define MSG_DATA_RECEIVED 1002 +#define MSG_SHUTDOWN 1003 + +// Example data structure +typedef struct { + int sensor_id; + float temperature; + char location[50]; +} sensor_data_t; + +// Application context +typedef struct { + const char* app_name; + int message_count; +} app_context_t; + +// Callback for startup notifications +void on_startup(int notification_id, void* data, void* user_data) { + app_context_t* ctx = (app_context_t*)user_data; + printf("[%s] System startup notification received\n", ctx->app_name); + ctx->message_count++; +} + +// Callback for sensor data notifications +void on_sensor_data(int notification_id, void* data, void* user_data) { + app_context_t* ctx = (app_context_t*)user_data; + sensor_data_t* sensor = (sensor_data_t*)data; + + if (sensor) { + printf("[%s] Sensor data received:\n", ctx->app_name); + printf(" Sensor ID: %d\n", sensor->sensor_id); + printf(" Temperature: %.1f°C\n", sensor->temperature); + printf(" Location: %s\n", sensor->location); + } + + ctx->message_count++; +} + +// Callback for shutdown notifications +void on_shutdown(int notification_id, void* data, void* user_data) { + app_context_t* ctx = (app_context_t*)user_data; + printf("[%s] Shutdown notification received\n", ctx->app_name); + ctx->message_count++; +} + +// Generic callback for logging all notifications +void on_any_message(int notification_id, void* data, void* user_data) { + const char* observer_name = (const char*)user_data; + printf("[%s] Notification %d received\n", observer_name, notification_id); +} + +int main() { + printf("=== Notifly C Interface Example ===\n\n"); + + // Create application context + app_context_t app_ctx = { + .app_name = "SensorApp", + .message_count = 0 + }; + + // Get the default notification center + notifly_handle notifly = notifly_default(); + if (!notifly) { + printf("ERROR: Failed to get default notification center\n"); + return 1; + } + + printf("1. Setting up observers...\n"); + + // Add observers for different message types + int startup_observer = notifly_add_observer(notifly, MSG_STARTUP, on_startup, &app_ctx); + int data_observer = notifly_add_observer(notifly, MSG_DATA_RECEIVED, on_sensor_data, &app_ctx); + int shutdown_observer = notifly_add_observer(notifly, MSG_SHUTDOWN, on_shutdown, &app_ctx); + + // Add a logger that observes all message types + int logger1 = notifly_add_observer(notifly, MSG_STARTUP, on_any_message, (void*)"Logger"); + int logger2 = notifly_add_observer(notifly, MSG_DATA_RECEIVED, on_any_message, (void*)"Logger"); + int logger3 = notifly_add_observer(notifly, MSG_SHUTDOWN, on_any_message, (void*)"Logger"); + + if (startup_observer <= 0 || data_observer <= 0 || shutdown_observer <= 0 || + logger1 <= 0 || logger2 <= 0 || logger3 <= 0) { + printf("ERROR: Failed to add observers\n"); + return 1; + } + + printf(" Added %d observers successfully\n", 6); + printf("\n2. Sending notifications...\n\n"); + + // Send startup notification + int result = notifly_post_notification(notifly, MSG_STARTUP, NULL); + printf(" Startup notification sent to %d observers\n\n", result); + + // Send sensor data notifications + sensor_data_t sensors[] = { + {101, 23.5, "Living Room"}, + {102, 19.8, "Bedroom"}, + {103, 25.1, "Kitchen"} + }; + + for (int i = 0; i < 3; i++) { + result = notifly_post_notification(notifly, MSG_DATA_RECEIVED, &sensors[i]); + printf(" Sensor data notification sent to %d observers\n\n", result); + } + + // Send some async notifications + printf("3. Sending async notifications...\n\n"); + + sensor_data_t outdoor_sensor = {201, 15.3, "Outdoor"}; + result = notifly_post_notification_async(notifly, MSG_DATA_RECEIVED, &outdoor_sensor); + printf(" Async sensor data notification sent to %d observers\n", result); + + // Wait a bit for async notifications to complete + usleep(100000); // 100ms + printf("\n"); + + // Send shutdown notification + result = notifly_post_notification(notifly, MSG_SHUTDOWN, NULL); + printf(" Shutdown notification sent to %d observers\n\n", result); + + printf("4. Summary:\n"); + printf(" Application processed %d notifications\n", app_ctx.message_count); + + // Clean up observers + printf("\n5. Cleaning up...\n"); + + notifly_remove_observer(notifly, startup_observer); + notifly_remove_observer(notifly, data_observer); + notifly_remove_observer(notifly, shutdown_observer); + + // Remove all logger observers for MSG_DATA_RECEIVED + int removed = notifly_remove_all_observers(notifly, MSG_DATA_RECEIVED); + printf(" Removed %d remaining observers for data notifications\n", removed); + + // Remove remaining logger observers + notifly_remove_observer(notifly, logger1); + notifly_remove_observer(notifly, logger3); + + printf(" Cleanup complete\n\n"); + + // Test creating our own instance + printf("6. Testing custom instance...\n"); + + notifly_handle custom_notifly = notifly_create(); + if (!custom_notifly) { + printf("ERROR: Failed to create custom instance\n"); + return 1; + } + + int custom_observer = notifly_add_observer(custom_notifly, 999, on_any_message, (void*)"Custom"); + result = notifly_post_notification(custom_notifly, 999, NULL); + printf(" Custom instance notification sent to %d observers\n", result); + + notifly_destroy(custom_notifly); + printf(" Custom instance destroyed\n"); + + printf("\n=== Example completed successfully! ===\n"); + return 0; +} \ No newline at end of file diff --git a/include/notifly_c.h b/include/notifly_c.h new file mode 100644 index 0000000..8a9e9e7 --- /dev/null +++ b/include/notifly_c.h @@ -0,0 +1,75 @@ +/* + * notifly_c.h + * notifly C interface + * + * Copyright (c) 2024 Salvatore Rivieccio. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef NOTIFLY_C_H +#define NOTIFLY_C_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Handle for notifly instance (opaque pointer) */ +typedef struct notifly_instance* notifly_handle; + +/* Callback function type for observers */ +typedef void (*notifly_callback)(int notification_id, void* data, void* user_data); + +/* Result codes (matching notifly_result enum from C++ API) */ +typedef enum { + NOTIFLY_SUCCESS = 0, + NOTIFLY_OBSERVER_NOT_FOUND = -1, + NOTIFLY_NOTIFICATION_NOT_FOUND = -2, + NOTIFLY_PAYLOAD_TYPE_NOT_MATCH = -3, + NOTIFLY_NO_MORE_OBSERVER_IDS = -4, + NOTIFLY_INVALID_HANDLE = -5 +} notifly_result_t; + +/* Library version */ +#define NOTIFLY_C_VERSION_MAJOR 1 +#define NOTIFLY_C_VERSION_MINOR 0 +#define NOTIFLY_C_VERSION_PATCH 0 + +/* Instance management */ +notifly_handle notifly_create(void); +void notifly_destroy(notifly_handle handle); +notifly_handle notifly_default(void); + +/* Observer management */ +int notifly_add_observer(notifly_handle handle, int notification_id, notifly_callback callback, void* user_data); +int notifly_remove_observer(notifly_handle handle, int observer_id); +int notifly_remove_all_observers(notifly_handle handle, int notification_id); + +/* Notification posting */ +int notifly_post_notification(notifly_handle handle, int notification_id, void* data); +int notifly_post_notification_async(notifly_handle handle, int notification_id, void* data); + +/* Utility functions */ +const char* notifly_result_to_string(int result); + +#ifdef __cplusplus +} +#endif + +#endif /* NOTIFLY_C_H */ \ No newline at end of file diff --git a/src/notifly_c.cpp b/src/notifly_c.cpp new file mode 100644 index 0000000..e074d53 --- /dev/null +++ b/src/notifly_c.cpp @@ -0,0 +1,230 @@ +/* + * notifly_c.cpp + * notifly C interface implementation + * + * Copyright (c) 2024 Salvatore Rivieccio. All rights reserved. + */ + +#include "notifly_c.h" +#include "notifly.h" +#include +#include +#include + +// Internal structure to wrap notifly instances +struct notifly_instance { + std::unique_ptr instance; + // Maps observer ID to callback info for proper cleanup + std::unordered_map> callbacks; + std::mutex callback_mutex; + + explicit notifly_instance(std::unique_ptr inst) + : instance(std::move(inst)) {} +}; + +// Wrapper callback that adapts C++ callback to C callback +class CallbackWrapper { +public: + CallbackWrapper(notifly_callback cb, void* user_data) + : callback(cb), user_data(user_data) {} + + void operator()(void* data) { + callback(0, data, user_data); // notification_id will be set by the caller + } + +private: + notifly_callback callback; + void* user_data; +}; + +// Global default instance pointer +static notifly_handle g_default_handle = nullptr; +static std::mutex g_default_mutex; + +extern "C" { + +notifly_handle notifly_create(void) { + try { + auto cpp_instance = std::make_unique(); + return new notifly_instance(std::move(cpp_instance)); + } catch (...) { + return nullptr; + } +} + +void notifly_destroy(notifly_handle handle) { + if (handle) { + delete handle; + } +} + +notifly_handle notifly_default(void) { + std::lock_guard lock(g_default_mutex); + if (g_default_handle == nullptr) { + // Create a wrapper around the default C++ instance + // Note: we don't own the C++ default instance, so we create a special wrapper + g_default_handle = new notifly_instance(nullptr); // nullptr indicates default instance + } + return g_default_handle; +} + +int notifly_add_observer(notifly_handle handle, int notification_id, notifly_callback callback, void* user_data) { + if (!handle || !callback) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } + + try { + // Get the actual notifly instance + notifly* instance = nullptr; + if (handle->instance) { + instance = handle->instance.get(); + } else { + // This is the default instance + instance = ¬ifly::default_notifly(); + } + + // Create a lambda that captures the notification_id and calls our C callback + auto cpp_callback = [handle, notification_id, callback, user_data](void* data) { + callback(notification_id, data, user_data); + }; + + // Add observer to the C++ instance + int observer_id = instance->add_observer(notification_id, cpp_callback); + + if (observer_id > 0) { + // Store callback info for cleanup + std::lock_guard lock(handle->callback_mutex); + handle->callbacks[observer_id] = std::make_pair(callback, user_data); + } + + return observer_id; + } catch (...) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } +} + +int notifly_remove_observer(notifly_handle handle, int observer_id) { + if (!handle) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } + + try { + // Get the actual notifly instance + notifly* instance = nullptr; + if (handle->instance) { + instance = handle->instance.get(); + } else { + // This is the default instance + instance = ¬ifly::default_notifly(); + } + + // Remove from C++ instance + int result = instance->remove_observer(observer_id); + + // Clean up our callback info + if (result == static_cast(notifly_result::success)) { + std::lock_guard lock(handle->callback_mutex); + handle->callbacks.erase(observer_id); + } + + return result; + } catch (...) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } +} + +int notifly_remove_all_observers(notifly_handle handle, int notification_id) { + if (!handle) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } + + try { + // Get the actual notifly instance + notifly* instance = nullptr; + if (handle->instance) { + instance = handle->instance.get(); + } else { + // This is the default instance + instance = ¬ifly::default_notifly(); + } + + int result = instance->remove_all_observers(notification_id); + + // Clean up our callback info - remove all callbacks for this notification + // Note: This is a simplified cleanup. In practice, we'd need to track which + // observers belong to which notification, but for now this clears all. + if (result > 0) { + std::lock_guard lock(handle->callback_mutex); + handle->callbacks.clear(); + } + + return result; + } catch (...) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } +} + +int notifly_post_notification(notifly_handle handle, int notification_id, void* data) { + if (!handle) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } + + try { + // Get the actual notifly instance + notifly* instance = nullptr; + if (handle->instance) { + instance = handle->instance.get(); + } else { + // This is the default instance + instance = ¬ifly::default_notifly(); + } + + // Post notification with void* data + return instance->post_notification(notification_id, data); + } catch (...) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } +} + +int notifly_post_notification_async(notifly_handle handle, int notification_id, void* data) { + if (!handle) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } + + try { + // Get the actual notifly instance + notifly* instance = nullptr; + if (handle->instance) { + instance = handle->instance.get(); + } else { + // This is the default instance + instance = ¬ifly::default_notifly(); + } + + // Post notification asynchronously with void* data + return instance->post_notification_async(notification_id, data); + } catch (...) { + return static_cast(NOTIFLY_INVALID_HANDLE); + } +} + +const char* notifly_result_to_string(int result) { + switch (result) { + case NOTIFLY_SUCCESS: + return "Success"; + case NOTIFLY_OBSERVER_NOT_FOUND: + return "Observer not found"; + case NOTIFLY_NOTIFICATION_NOT_FOUND: + return "Notification not found"; + case NOTIFLY_PAYLOAD_TYPE_NOT_MATCH: + return "Payload type mismatch"; + case NOTIFLY_NO_MORE_OBSERVER_IDS: + return "No more observer IDs available"; + case NOTIFLY_INVALID_HANDLE: + return "Invalid handle"; + default: + return "Unknown error"; + } +} + +} // extern "C" \ No newline at end of file diff --git a/test/test_c_interface.c b/test/test_c_interface.c new file mode 100644 index 0000000..54e2a7e --- /dev/null +++ b/test/test_c_interface.c @@ -0,0 +1,291 @@ +/* + * test_c_interface.c + * Simple C test for notifly C interface + */ + +#include "notifly_c.h" +#include +#include +#include +#include // for sleep + +// Test data structure +typedef struct { + int value; + char message[100]; +} test_data_t; + +// Global variables to track callback invocations +static int callback_count = 0; +static int last_notification_id = -1; +static test_data_t last_received_data = {0, ""}; + +// Test callback function +void test_callback(int notification_id, void* data, void* user_data) { + callback_count++; + last_notification_id = notification_id; + + printf("C Callback called: notification_id=%d, callback_count=%d\n", + notification_id, callback_count); + + if (data) { + test_data_t* test_data = (test_data_t*)data; + last_received_data = *test_data; + printf(" Received data: value=%d, message='%s'\n", + test_data->value, test_data->message); + } + + if (user_data) { + const char* user_msg = (const char*)user_data; + printf(" User data: '%s'\n", user_msg); + } +} + +void simple_callback(int notification_id, void* data, void* user_data) { + printf("Simple callback: notification_id=%d\n", notification_id); + callback_count++; +} + +int test_basic_functionality() { + printf("\n=== Test Basic Functionality ===\n"); + + // Reset globals + callback_count = 0; + last_notification_id = -1; + + // Get default handle + notifly_handle handle = notifly_default(); + if (!handle) { + printf("FAIL: Could not get default handle\n"); + return 1; + } + + // Add observer + const char* user_data = "test user data"; + int observer_id = notifly_add_observer(handle, 1001, test_callback, (void*)user_data); + if (observer_id <= 0) { + printf("FAIL: Could not add observer, result=%d (%s)\n", + observer_id, notifly_result_to_string(observer_id)); + return 1; + } + printf("Added observer with ID: %d\n", observer_id); + + // Post notification with data + test_data_t test_data = {42, "Hello from C!"}; + int result = notifly_post_notification(handle, 1001, &test_data); + if (result <= 0) { + printf("FAIL: Could not post notification, result=%d (%s)\n", + result, notifly_result_to_string(result)); + return 1; + } + printf("Posted notification, %d observers notified\n", result); + + // Verify callback was called + if (callback_count != 1) { + printf("FAIL: Expected callback_count=1, got %d\n", callback_count); + return 1; + } + if (last_notification_id != 1001) { + printf("FAIL: Expected notification_id=1001, got %d\n", last_notification_id); + return 1; + } + if (last_received_data.value != 42 || strcmp(last_received_data.message, "Hello from C!") != 0) { + printf("FAIL: Data not received correctly\n"); + return 1; + } + + // Remove observer + result = notifly_remove_observer(handle, observer_id); + if (result != NOTIFLY_SUCCESS) { + printf("FAIL: Could not remove observer, result=%d (%s)\n", + result, notifly_result_to_string(result)); + return 1; + } + printf("Removed observer successfully\n"); + + printf("PASS: Basic functionality test\n"); + return 0; +} + +int test_multiple_observers() { + printf("\n=== Test Multiple Observers ===\n"); + + // Reset globals + callback_count = 0; + + notifly_handle handle = notifly_default(); + + // Add multiple observers + int observer1 = notifly_add_observer(handle, 1002, simple_callback, NULL); + int observer2 = notifly_add_observer(handle, 1002, simple_callback, NULL); + int observer3 = notifly_add_observer(handle, 1002, simple_callback, NULL); + + if (observer1 <= 0 || observer2 <= 0 || observer3 <= 0) { + printf("FAIL: Could not add observers\n"); + return 1; + } + printf("Added 3 observers: %d, %d, %d\n", observer1, observer2, observer3); + + // Post notification + int result = notifly_post_notification(handle, 1002, NULL); + if (result != 3) { + printf("FAIL: Expected 3 observers notified, got %d\n", result); + return 1; + } + printf("Posted notification, %d observers notified\n", result); + + if (callback_count != 3) { + printf("FAIL: Expected 3 callbacks, got %d\n", callback_count); + return 1; + } + + // Remove all observers + result = notifly_remove_all_observers(handle, 1002); + if (result != 3) { + printf("FAIL: Expected 3 observers removed, got %d\n", result); + return 1; + } + printf("Removed all observers: %d\n", result); + + printf("PASS: Multiple observers test\n"); + return 0; +} + +int test_async_notification() { + printf("\n=== Test Async Notification ===\n"); + + // Reset globals + callback_count = 0; + + notifly_handle handle = notifly_default(); + + int observer_id = notifly_add_observer(handle, 1003, simple_callback, NULL); + if (observer_id <= 0) { + printf("FAIL: Could not add observer\n"); + return 1; + } + + // Post async notification + int result = notifly_post_notification_async(handle, 1003, NULL); + if (result != 1) { + printf("FAIL: Expected 1 observer notified async, got %d\n", result); + return 1; + } + printf("Posted async notification\n"); + + // Wait a bit for async callback + usleep(100000); // 100ms + + if (callback_count != 1) { + printf("FAIL: Expected 1 async callback, got %d\n", callback_count); + return 1; + } + + // Cleanup + notifly_remove_observer(handle, observer_id); + + printf("PASS: Async notification test\n"); + return 0; +} + +int test_instance_creation() { + printf("\n=== Test Instance Creation ===\n"); + + // Create our own instance + notifly_handle handle = notifly_create(); + if (!handle) { + printf("FAIL: Could not create instance\n"); + return 1; + } + printf("Created notifly instance\n"); + + // Reset globals + callback_count = 0; + + int observer_id = notifly_add_observer(handle, 1004, simple_callback, NULL); + if (observer_id <= 0) { + printf("FAIL: Could not add observer to custom instance\n"); + notifly_destroy(handle); + return 1; + } + + int result = notifly_post_notification(handle, 1004, NULL); + if (result != 1) { + printf("FAIL: Expected 1 observer notified, got %d\n", result); + notifly_destroy(handle); + return 1; + } + + if (callback_count != 1) { + printf("FAIL: Expected 1 callback, got %d\n", callback_count); + notifly_destroy(handle); + return 1; + } + + // Destroy instance + notifly_destroy(handle); + printf("Destroyed notifly instance\n"); + + printf("PASS: Instance creation test\n"); + return 0; +} + +int test_error_handling() { + printf("\n=== Test Error Handling ===\n"); + + // Test invalid handle + int result = notifly_add_observer(NULL, 1005, simple_callback, NULL); + if (result != NOTIFLY_INVALID_HANDLE) { + printf("FAIL: Expected NOTIFLY_INVALID_HANDLE for NULL handle, got %d\n", result); + return 1; + } + printf("NULL handle correctly rejected\n"); + + // Test NULL callback + notifly_handle handle = notifly_default(); + result = notifly_add_observer(handle, 1005, NULL, NULL); + if (result != NOTIFLY_INVALID_HANDLE) { + printf("FAIL: Expected NOTIFLY_INVALID_HANDLE for NULL callback, got %d\n", result); + return 1; + } + printf("NULL callback correctly rejected\n"); + + // Test removing non-existent observer + result = notifly_remove_observer(handle, 99999); + if (result != NOTIFLY_OBSERVER_NOT_FOUND) { + printf("FAIL: Expected NOTIFLY_OBSERVER_NOT_FOUND, got %d\n", result); + return 1; + } + printf("Non-existent observer correctly rejected\n"); + + // Test posting to non-existent notification + result = notifly_post_notification(handle, 99999, NULL); + if (result != NOTIFLY_NOTIFICATION_NOT_FOUND) { + printf("FAIL: Expected NOTIFLY_NOTIFICATION_NOT_FOUND, got %d\n", result); + return 1; + } + printf("Non-existent notification correctly rejected\n"); + + printf("PASS: Error handling test\n"); + return 0; +} + +int main() { + printf("Starting notifly C interface tests...\n"); + + int failed = 0; + + failed += test_basic_functionality(); + failed += test_multiple_observers(); + failed += test_async_notification(); + failed += test_instance_creation(); + failed += test_error_handling(); + + if (failed == 0) { + printf("\n✓ All tests passed!\n"); + return 0; + } else { + printf("\n✗ %d test(s) failed!\n", failed); + return 1; + } +} \ No newline at end of file