diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp index 3e4692cc3..b37056eaa 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp +++ b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp @@ -1,6 +1,6 @@ #include "tasks_peer.h" -#include "tasks_log.h" #include "join_string_values.h" +#include "tasks_log.h" #include "transform_container.h" #include "Ditto.h" @@ -33,42 +33,70 @@ vector tasks_json_from(const ditto::QueryResult &result) { } /// Initialize a Ditto instance. -unique_ptr init_ditto(JNIEnv *env, - jobject android_context, - string app_id, - string online_playground_token, - bool enable_cloud_sync, - string persistence_dir, - bool is_running_on_emulator, - string custom_url, - const string& websocket_url) { +shared_ptr +init_ditto(JNIEnv * /* env */, jobject android_context, string app_id, + string online_playground_token, bool enable_cloud_sync, + string persistence_dir, bool is_running_on_emulator, + string custom_url, const string &websocket_url) { try { - // TODO UPDATE TO USE CUSTOM URL AND WEBSOCKET - // Docs: https://docs.ditto.live/sdk/latest/install-guides/cpp#importing-and-initializing-ditto - + // Use the failable initializer with Ditto C++ SDK v4.12 or newer +#if (DITTO_VERSION_MAJOR >= 5) || \ + (DITTO_VERSION_MAJOR == 4 && DITTO_VERSION_MINOR >= 12) + auto config = ditto::DittoConfig::default_config() + .set_database_id(std::move(app_id)) + .set_persistence_directory(std::move(persistence_dir)) + .set_server_connect(std::move(custom_url)) + .set_android_context(android_context); + auto ditto = ditto::Ditto::open(config); + + ditto->get_auth()->set_expiration_handler( + [login_token = std::move(online_playground_token)]( + ditto::Ditto &ditto, uint32_t time_remaining) { + try { + log_debug("expiration handler called; time remaining = " + + std::to_string(time_remaining)); + ditto.get_auth()->login( + login_token, ditto::Authenticator::get_development_provider(), + [&](unique_ptr client_info, + unique_ptr err) { + if (err != nullptr) { + log_error("expiration handler: login error:" + + string(err->what())); + } else { + log_debug("expiration handler: login succeeded"); + } + }); + } catch (const exception &e) { + log_error("expiration handler: " + string(e.what())); + } + }); +#else + // For older Ditto SDK versions, use the constructor. const auto identity = ditto::Identity::OnlinePlayground( - std::move(app_id), - std::move(online_playground_token), - enable_cloud_sync, // This is required to be set to false to use the correct URLs - std::move(custom_url) - ); + std::move(app_id), std::move(online_playground_token), + enable_cloud_sync, // This is required to be set to false to use the + // correct URLs + std::move(custom_url)); - auto ditto = - make_unique(android_context, identity, std::move(persistence_dir)); + auto ditto = make_shared(android_context, identity, + std::move(persistence_dir)); +#endif if (is_running_on_emulator) { // Some transports don't work correctly on emulator, so disable them. - ditto->update_transport_config([websocket_url](ditto::TransportConfig &config) { - config.peer_to_peer.bluetooth_le.enabled = false; - config.peer_to_peer.wifi_aware.enabled = false; - config.connect.websocket_urls.insert(websocket_url); - }); + ditto->update_transport_config( + [websocket_url](ditto::TransportConfig &config) { + config.peer_to_peer.bluetooth_le.enabled = false; + config.peer_to_peer.wifi_aware.enabled = false; + config.connect.websocket_urls.insert(websocket_url); + }); } else { - ditto->update_transport_config([websocket_url](ditto::TransportConfig &config) { - config.enable_all_peer_to_peer(); - config.connect.websocket_urls.insert(websocket_url); - }); + ditto->update_transport_config( + [websocket_url](ditto::TransportConfig &config) { + config.enable_all_peer_to_peer(); + config.connect.websocket_urls.insert(websocket_url); + }); } // Required for compatibility with DQL. @@ -86,38 +114,25 @@ unique_ptr init_ditto(JNIEnv *env, class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) private: unique_ptr mtx; - unique_ptr ditto; + shared_ptr ditto; shared_ptr tasks_subscription; public: - Impl(JNIEnv *env, - jobject context, - string app_id, - string online_playground_token, - bool enable_cloud_sync, - string persistence_dir, - bool is_running_on_emulator, - string custom_auth_url, - const string& websocket_url) + Impl(JNIEnv *env, jobject context, string app_id, + string online_playground_token, bool enable_cloud_sync, + string persistence_dir, bool is_running_on_emulator, + string custom_auth_url, const string &websocket_url) : mtx(new mutex()), - ditto(init_ditto( - env, - context, - std::move(app_id), - std::move(online_playground_token), - enable_cloud_sync, - std::move(persistence_dir), - is_running_on_emulator, - std::move(custom_auth_url), - websocket_url - )) {} + ditto(init_ditto(env, context, std::move(app_id), + std::move(online_playground_token), enable_cloud_sync, + std::move(persistence_dir), is_running_on_emulator, + std::move(custom_auth_url), websocket_url)) {} ~Impl() noexcept { try { stop_sync(); } catch (const exception &err) { - cerr << "Failed to destroy tasks peer instance: " + - string(err.what()) + cerr << "Failed to destroy tasks peer instance: " + string(err.what()) << endl; } } @@ -129,7 +144,7 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) ditto->start_sync(); tasks_subscription = - ditto->sync().register_subscription("SELECT * FROM tasks"); + ditto->get_sync().register_subscription("SELECT * FROM tasks"); } void stop_sync() { @@ -147,9 +162,7 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) string add_task(const string &title, bool done) { try { const json task_args = { - {"title", title}, - {"done", done}, - {"deleted", false}}; + {"title", title}, {"done", done}, {"deleted", false}}; const auto command = "INSERT INTO tasks DOCUMENTS (:newTask)"; const auto result = ditto->get_store().execute(command, {{"newTask", task_args}}); @@ -200,10 +213,10 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) " deleted = :deleted" " WHERE _id = :id"; const auto result = - ditto->get_store().execute(stmt, {{"title", task.title}, - {"done", task.done}, + ditto->get_store().execute(stmt, {{"title", task.title}, + {"done", task.done}, {"deleted", task.deleted}, - {"id", task._id}}); + {"id", task._id}}); if (result.mutated_document_ids().empty()) { throw runtime_error("task not found with ID: " + task._id); } @@ -224,8 +237,7 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) const auto stmt = "UPDATE tasks SET done = :done WHERE _id = :id"; const auto result = - ditto->get_store().execute(stmt, {{"done", done}, - {"id", task_id}}); + ditto->get_store().execute(stmt, {{"done", done}, {"id", task_id}}); log_debug("Marked task " + task_id + (done ? " complete" : " incomplete")); } catch (const exception &err) { @@ -255,8 +267,8 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) } } - shared_ptr register_tasks_observer( - function &)> callback) { + shared_ptr + register_tasks_observer(function &)> callback) { try { const auto observer = ditto->get_store().register_observer( "SELECT * FROM tasks WHERE NOT deleted ORDER BY _id", @@ -290,13 +302,13 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) {"50191411-4C46-4940-8B72-5F8017A04FA7", "Buy groceries"}, {"6DA283DA-8CFE-4526-A6FA-D385089364E5", "Clean the kitchen"}, {"5303DDF8-0E72-4FEB-9E82-4B007E5797F0", - "Schedule dentist appointment"}, + "Schedule dentist appointment"}, {"38411F1B-6B49-4346-90C3-0B16CE97E174", "Pay bills"}}; - for (const auto &task: initial_tasks) { - const json task_args = {{"_id", task._id}, - {"title", task.title}, - {"done", task.done}, + for (const auto &task : initial_tasks) { + const json task_args = {{"_id", task._id}, + {"title", task.title}, + {"done", task.done}, {"deleted", task.deleted}}; const auto command = "INSERT INTO tasks INITIAL DOCUMENTS (:newTask)"; ditto->get_store().execute(command, {{"newTask", task_args}}); @@ -319,12 +331,14 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) } }; // class TasksPeer::Impl -TasksPeer::TasksPeer(JNIEnv *env, jobject context, string app_id, string online_playground_token, - bool enable_cloud_sync, string persistence_dir, bool is_running_on_emulator, - string custom_auth_url, const string& websocket_url) - : impl(make_unique(env, context, std::move(app_id), std::move(online_playground_token), - enable_cloud_sync, std::move(persistence_dir), - is_running_on_emulator, std::move(custom_auth_url), std::move(websocket_url))) {} +TasksPeer::TasksPeer(JNIEnv *env, jobject context, string app_id, + string online_playground_token, bool enable_cloud_sync, + string persistence_dir, bool is_running_on_emulator, + string custom_auth_url, const string &websocket_url) + : impl(make_unique( + env, context, std::move(app_id), std::move(online_playground_token), + enable_cloud_sync, std::move(persistence_dir), is_running_on_emulator, + std::move(custom_auth_url), std::move(websocket_url))) {} TasksPeer::~TasksPeer() noexcept { try { @@ -368,3 +382,7 @@ string TasksPeer::get_ditto_sdk_version() { } void TasksPeer::insert_initial_tasks() { impl->insert_initial_tasks(); } + +jobjectArray TasksPeer::missing_permissions_jni_array() const { + return impl->missing_permissions_jni_array(); +} diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h index 9fd66ba6c..5e55b237c 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h +++ b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h @@ -83,6 +83,9 @@ class TasksPeer { /// Add a set of initial documents to the tasks collection. void insert_initial_tasks(); + /// Get array of missing permissions + jobjectArray missing_permissions_jni_array() const; + private: class Impl; // private implementation class ("pimpl pattern") std::unique_ptr impl; diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp index 364d60398..b32097cb1 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp +++ b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp @@ -376,3 +376,23 @@ Java_live_ditto_quickstart_tasks_TasksLib_removeTasksObserver(JNIEnv *env, jobje throw_java_exception(env, err.what()); } } + +extern "C" +JNIEXPORT jobjectArray JNICALL +Java_live_ditto_quickstart_tasks_TasksLib_getMissingPermissions(JNIEnv *env, jobject thiz) { + __android_log_print(ANDROID_LOG_DEBUG, TAG, + "Java_live_ditto_quickstart_tasks_TasksLib_getMissingPermissions"); + try { + std::lock_guard lock(mtx); + if (!peer) { + // Return empty array if not initialized yet + jclass stringClass = env->FindClass("java/lang/String"); + return env->NewObjectArray(0, stringClass, nullptr); + } + return peer->missing_permissions_jni_array(); + } catch (const std::exception &err) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "getMissingPermissions failed: %s", err.what()); + throw_java_exception(env, err.what()); + return nullptr; + } +} diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/MainActivity.kt b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/MainActivity.kt index 3e4d213e5..c78932598 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/MainActivity.kt +++ b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/MainActivity.kt @@ -3,7 +3,6 @@ package live.ditto.quickstart.tasks import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import live.ditto.transports.DittoSyncPermissions class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -17,9 +16,13 @@ class MainActivity : ComponentActivity() { } private fun requestMissingPermissions() { - val missingPermissions = DittoSyncPermissions(this).missingPermissions() - if (missingPermissions.isNotEmpty()) { - this.requestPermissions(missingPermissions, 0) + // Check if TasksApplication has been initialized first + val app = application as? TasksApplication + if (app?.isInitialized() == true) { + val missingPermissions = TasksLib.getMissingPermissions() + if (missingPermissions.isNotEmpty()) { + this.requestPermissions(missingPermissions, 0) + } } } } diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/TasksApplication.kt b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/TasksApplication.kt index 09d1c9c75..38864ec7d 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/TasksApplication.kt +++ b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/TasksApplication.kt @@ -52,6 +52,7 @@ val isProbablyRunningOnEmulator: Boolean by lazy { } class TasksApplication : Application() { + private var dittoInitialized = false companion object { private const val TAG = "TasksApplication" @@ -66,6 +67,10 @@ class TasksApplication : Application() { init { instance = this } + + fun isInitialized(): Boolean { + return dittoInitialized + } override fun onCreate() { super.onCreate() @@ -94,6 +99,7 @@ class TasksApplication : Application() { webSocketURL ) TasksLib.startSync() + dittoInitialized = true } catch (e: Exception) { Log.e(TAG, "unable to initialize Ditto", e) } diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/TasksLib.kt b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/TasksLib.kt index 82e9941ac..e790d48ab 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/TasksLib.kt +++ b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/TasksLib.kt @@ -58,5 +58,8 @@ object TasksLib { // Remove the tasks observer. external fun removeTasksObserver() + + // Get array of missing permissions + external fun getMissingPermissions(): Array } diff --git a/cpp-tui/taskscpp/src/main.cpp b/cpp-tui/taskscpp/src/main.cpp index 290f6f8ff..ac2dc4dea 100644 --- a/cpp-tui/taskscpp/src/main.cpp +++ b/cpp-tui/taskscpp/src/main.cpp @@ -191,14 +191,12 @@ int main(int argc, const char *argv[]) { opt_parse.count("online-playground-token") > 0 ? opt_parse["online-playground-token"].as() : DITTO_PLAYGROUND_TOKEN; - const auto websocket_url = - opt_parse.count("websocket-url") > 0 - ? opt_parse["websocket-url"].as() - : DITTO_WEBSOCKET_URL; - const auto auth_url = - opt_parse.count("auth-url") > 0 - ? opt_parse["auth-url"].as() - : DITTO_AUTH_URL; + const auto websocket_url = opt_parse.count("websocket-url") > 0 + ? opt_parse["websocket-url"].as() + : DITTO_WEBSOCKET_URL; + const auto auth_url = opt_parse.count("auth-url") > 0 + ? opt_parse["auth-url"].as() + : DITTO_AUTH_URL; const auto enable_cloud_sync = opt_parse.count("enable-cloud-sync") > 0; @@ -209,13 +207,8 @@ int main(int argc, const char *argv[]) { // The peer is destroyed at the end of this scope { - TasksPeer peer( - app_id, - online_playground_token, - websocket_url, - auth_url, - enable_cloud_sync, - persistence_dir); + TasksPeer peer(app_id, online_playground_token, websocket_url, auth_url, + enable_cloud_sync, persistence_dir); peer.insert_initial_tasks(); peer.start_sync(); diff --git a/cpp-tui/taskscpp/src/tasks_peer.cpp b/cpp-tui/taskscpp/src/tasks_peer.cpp index 697c1a8a0..efe071433 100644 --- a/cpp-tui/taskscpp/src/tasks_peer.cpp +++ b/cpp-tui/taskscpp/src/tasks_peer.cpp @@ -45,26 +45,55 @@ static string to_json_string(const ditto::QueryResult &result) { } /// Initialize a Ditto instance. -static shared_ptr init_ditto(string app_id, - string online_playground_token, - string websocket_url, - string auth_url, - bool enable_cloud_sync, - string persistence_dir) { +static shared_ptr +init_ditto(string app_id, string online_playground_token, string websocket_url, + string auth_url, bool enable_cloud_sync, string persistence_dir) { try { + // Use the failable initializer with Ditto C++ SDK v4.12 or newer +#if (DITTO_VERSION_MAJOR >= 5) || \ + (DITTO_VERSION_MAJOR == 4 && DITTO_VERSION_MINOR >= 12) + auto config = ditto::DittoConfig::default_config() + .set_database_id(std::move(app_id)) + .set_persistence_directory(std::move(persistence_dir)) + .set_server_connect(std::move(auth_url)); + auto ditto = ditto::Ditto::open(config); + + ditto->get_auth()->set_expiration_handler( + [login_token = std::move(online_playground_token)]( + ditto::Ditto &ditto, uint32_t time_remaining) { + try { + log_debug("expiration handler called; time remaining = " + + std::to_string(time_remaining)); + ditto.get_auth()->login( + login_token, ditto::Authenticator::get_development_provider(), + [&](unique_ptr client_info, + unique_ptr err) { + if (err != nullptr) { + log_error("expiration handler: login error:" + + string(err->what())); + } else { + log_debug("expiration handler: login succeeded"); + } + }); + } catch (const exception &e) { + log_error("expiration handler: " + string(e.what())); + } + }); +#else + // For older Ditto SDK versions, use the constructor. const auto identity = ditto::Identity::OnlinePlayground( - std::move(app_id), - std::move(online_playground_token), - enable_cloud_sync, - std::move(auth_url)); + std::move(app_id), std::move(online_playground_token), + enable_cloud_sync, std::move(auth_url)); auto ditto = std::make_shared(identity, std::move(persistence_dir)); +#endif - ditto->update_transport_config([websocket_url](ditto::TransportConfig &config) { - config.enable_all_peer_to_peer(); - config.connect.websocket_urls.insert(websocket_url); - }); + ditto->update_transport_config( + [websocket_url](ditto::TransportConfig &config) { + config.enable_all_peer_to_peer(); + config.connect.websocket_urls.insert(websocket_url); + }); // Required for compatibility with DQL. ditto->disable_sync_with_v3(); @@ -91,22 +120,14 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) } public: - Impl( - string app_id, - string online_playground_token, - string websocket_url, - string auth_url, - bool enable_cloud_sync, - string persistence_dir) + Impl(string app_id, string online_playground_token, string websocket_url, + string auth_url, bool enable_cloud_sync, string persistence_dir) : mtx(new mutex()), - ditto( - init_ditto( - std::move(app_id), - std::move(online_playground_token), - std::move(websocket_url), - std::move(auth_url), - enable_cloud_sync, // This is required to be set to false to use the correct URLs - std::move(persistence_dir))) {} + ditto(init_ditto(std::move(app_id), std::move(online_playground_token), + std::move(websocket_url), std::move(auth_url), + enable_cloud_sync, // This is required to be set to + // false to use the correct URLs + std::move(persistence_dir))) {} ~Impl() noexcept { try { @@ -394,20 +415,12 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) } }; // class TasksPeer::Impl -TasksPeer::TasksPeer( - string app_id, - string online_playground_token, - string websocket_url, - string auth_url, - bool enable_cloud_sync, - string persistence_dir) - : impl(new Impl( - std::move(app_id), - std::move(online_playground_token), - std::move(websocket_url), - std::move(auth_url), - enable_cloud_sync, - std::move(persistence_dir))) {} +TasksPeer::TasksPeer(string app_id, string online_playground_token, + string websocket_url, string auth_url, + bool enable_cloud_sync, string persistence_dir) + : impl(new Impl(std::move(app_id), std::move(online_playground_token), + std::move(websocket_url), std::move(auth_url), + enable_cloud_sync, std::move(persistence_dir))) {} TasksPeer::~TasksPeer() noexcept { try { diff --git a/cpp-tui/taskscpp/src/tasks_peer.h b/cpp-tui/taskscpp/src/tasks_peer.h index 8edbcf90e..3a76307cf 100644 --- a/cpp-tui/taskscpp/src/tasks_peer.h +++ b/cpp-tui/taskscpp/src/tasks_peer.h @@ -16,13 +16,9 @@ class TasksPeer { static std::string get_ditto_sdk_version(); /// Construct a new TasksPeer object. - TasksPeer( - std::string ditto_app_id, - std::string ditto_online_playground_token, - std::string ditto_websocket_url, - std::string ditto_auth_url, - bool enable_cloud_sync, - std::string ditto_persistence_dir); + TasksPeer(std::string ditto_app_id, std::string ditto_online_playground_token, + std::string ditto_websocket_url, std::string ditto_auth_url, + bool enable_cloud_sync, std::string ditto_persistence_dir); virtual ~TasksPeer() noexcept;