diff --git a/Android/MMKV/mmkv/proguard-rules.pro b/Android/MMKV/mmkv/proguard-rules.pro index 6fb0ef7b..0cb364d3 100644 --- a/Android/MMKV/mmkv/proguard-rules.pro +++ b/Android/MMKV/mmkv/proguard-rules.pro @@ -6,4 +6,5 @@ private static *** onMMKVFileLengthError(***); private static *** mmkvLogImp(...); private static *** onContentChangedByOuterProcess(***); + private static *** onMMKVContentLoadSuccessfully(***); } diff --git a/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp b/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp index a6e7dc84..8165f1ff 100644 --- a/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp +++ b/Android/MMKV/mmkv/src/main/cpp/flutter-bridge.cpp @@ -41,13 +41,50 @@ extern string g_android_tmpDir; # define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, const char *funcname, const char *message); -LogCallback_t g_logCallback = nullptr; +using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); +using ContenctChangeCallback_t = void (*)(const char *mmapID); -static void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (g_logCallback) { - g_logCallback(level, file, line, function, message.c_str()); +class FlutterMMKVHandler : public mmkv::MMKVHandler { +public: + LogCallback_t logCallback = nullptr; + ErrorCallback_t errorCallback = nullptr; + ContenctChangeCallback_t contentChangeCallback = nullptr; + ContenctChangeCallback_t contentLoadedCallback = nullptr; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (logCallback) { + logCallback(level, file, line, function, message.c_str()); + } } -} + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVCRCCheckFail); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVFileLength); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (contentChangeCallback) { + contentChangeCallback(mmapID.c_str()); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (contentLoadedCallback) { + contentLoadedCallback(mmapID.c_str()); + } + } +}; + +static FlutterMMKVHandler g_flutterHandler; MMKV_EXPORT void *mmkvInitialize_v2(const char *rootDir, const char *cacheDir, int32_t sdkInt, int32_t logLevel, LogCallback_t callback) { if (!rootDir) { @@ -65,8 +102,8 @@ MMKV_EXPORT void *mmkvInitialize_v2(const char *rootDir, const char *cacheDir, i #endif if (callback) { - g_logCallback = callback; - MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, myLogHandler); + g_flutterHandler.logCallback = callback; + MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel); } @@ -663,40 +700,24 @@ MMKV_EXPORT bool isReadOnly(void *handle) { return false; } -using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); -static ErrorCallback_t g_errorCallback = nullptr; - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (g_errorCallback) { - return (MMKVRecoverStrategic) g_errorCallback(mmapID.c_str(), errorType); - } - return OnErrorDiscard; -} - MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { - g_errorCallback = callback; - if (callback) { - MMKV::registerErrorHandler(myErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -using ContenctChangeCallback_t = void (*)(const char *mmapID); -static ContenctChangeCallback_t g_contentChanceCallback = nullptr; - -static void myContentChangeHandler(const std::string &mmapID) { - if (g_contentChanceCallback) { - g_contentChanceCallback(mmapID.c_str()); + g_flutterHandler.errorCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.contentChangeCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); } } MMKV_EXPORT void registerContentChangeNotify(ContenctChangeCallback_t callback) { - g_contentChanceCallback = callback; - if (callback) { - MMKV::registerContentChangeHandler(myContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_flutterHandler.contentChangeCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); + } +} + +MMKV_EXPORT void registerContentLoadedNotify(ContenctChangeCallback_t callback) { + g_flutterHandler.contentLoadedCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentChangeCallback) { + MMKV::registerHandler(&g_flutterHandler); } } diff --git a/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp b/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp index dedfba6e..d2626525 100644 --- a/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp +++ b/Android/MMKV/mmkv/src/main/cpp/native-bridge.cpp @@ -40,6 +40,7 @@ static jmethodID g_callbackOnCRCFailID = nullptr; static jmethodID g_callbackOnFileLengthErrorID = nullptr; static jmethodID g_mmkvLogID = nullptr; static jmethodID g_callbackOnContentChange = nullptr; +static jmethodID g_callbackOnContentLoaded = nullptr; static JavaVM *g_currentJVM = nullptr; static int registerNativeMethods(JNIEnv *env, jclass cls); @@ -52,6 +53,79 @@ namespace mmkv { typedef void (*AndroidLogHandler)(MMKVLogLevel level, const char *file, int line, const char *function, const char *message); static AndroidLogHandler g_androidLogHandler = nullptr; static void androidLogWrapper(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message); + + static JNIEnv *getCurrentEnv(); + static jstring string2jstring(JNIEnv *env, const string &str); + +// C++ adapter class that bridges mmkv::MMKVHandler to JNI +class JNIMMKVHandler : public mmkv::MMKVHandler { + bool m_logRedirecting = false; + bool m_hasCallback = false; + bool m_wantsContentChange = false; +public: + void setLogRedirecting(bool logRedirecting) { m_logRedirecting = logRedirecting; } + void setHasCallback(bool hasCallback) { m_hasCallback = hasCallback; } + void setWantsContentChange(bool wantsContentChange) { m_wantsContentChange = wantsContentChange; } + bool isLogRedirecting() const { return m_logRedirecting; } + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) override { + if (m_logRedirecting) { + if (g_androidLogHandler) { + g_androidLogHandler(level, file, line, function, message.c_str()); + } else { + mmkv::mmkvLog(level, file, line, function, message); + } + } + } + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (!m_hasCallback) { + return OnErrorDiscard; + } + auto currentEnv = getCurrentEnv(); + if (currentEnv && g_callbackOnCRCFailID) { + jstring str = string2jstring(currentEnv, mmapID); + auto strategic = currentEnv->CallStaticIntMethod(g_cls, g_callbackOnCRCFailID, str); + currentEnv->DeleteLocalRef(str); + return static_cast(strategic); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (!m_hasCallback) { + return OnErrorDiscard; + } + auto currentEnv = getCurrentEnv(); + if (currentEnv && g_callbackOnFileLengthErrorID) { + jstring str = string2jstring(currentEnv, mmapID); + auto strategic = currentEnv->CallStaticIntMethod(g_cls, g_callbackOnFileLengthErrorID, str); + currentEnv->DeleteLocalRef(str); + return static_cast(strategic); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + auto currentEnv = getCurrentEnv(); + if (currentEnv && g_callbackOnContentChange) { + jstring str = string2jstring(currentEnv, mmapID); + currentEnv->CallStaticVoidMethod(g_cls, g_callbackOnContentChange, str); + currentEnv->DeleteLocalRef(str); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + auto currentEnv = getCurrentEnv(); + if (currentEnv && g_callbackOnContentLoaded) { + jstring str = string2jstring(currentEnv, mmapID); + currentEnv->CallStaticVoidMethod(g_cls, g_callbackOnContentLoaded, str); + currentEnv->DeleteLocalRef(str); + } + } +}; + +static JNIMMKVHandler g_jniHandler; } #define InternalLogError(format, ...) \ @@ -112,6 +186,11 @@ extern "C" JNIEXPORT JNICALL jint JNI_OnLoad(JavaVM *vm, void *reserved) { if (!g_callbackOnContentChange) { InternalLogError("fail to get method id for onContentChangedByOuterProcess()"); } + g_callbackOnContentLoaded = + env->GetStaticMethodID(g_cls, "onMMKVContentLoadSuccessfully", "(Ljava/lang/String;)V"); + if (!g_callbackOnContentLoaded) { + InternalLogError("fail to get method id for onMMKVContentLoadSuccessfully()"); + } // Note: If you use NDK r23 or older, you can get API level by accessing android.os.Build.VERSION.SDK_INT g_android_api = android_get_device_api_level(); @@ -131,32 +210,29 @@ namespace mmkv { static string jstring2string(JNIEnv *env, jstring str); -MMKVRecoverStrategic onMMKVError(const std::string &mmapID, MMKVErrorType errorType); - MMKV_JNI void jniInitialize(JNIEnv *env, jobject obj, jstring rootDir, jstring cacheDir, jint logLevel, jboolean logReDirecting, jboolean hasCallback, jlong nativeLogHandler) { if (!rootDir) { return; } const char *kstr = env->GetStringUTFChars(rootDir, nullptr); if (kstr) { - mmkv::LogHandler logHandler = nullptr; - if (logReDirecting) { - if (nativeLogHandler != 0) { - g_androidLogHandler = (AndroidLogHandler) nativeLogHandler; - logHandler = androidLogWrapper; - } else { - logHandler = mmkvLog; - } + g_jniHandler.setLogRedirecting(logReDirecting == JNI_TRUE); + g_jniHandler.setHasCallback(hasCallback == JNI_TRUE); + if (logReDirecting && nativeLogHandler != 0) { + g_androidLogHandler = (AndroidLogHandler) nativeLogHandler; + } else { + g_androidLogHandler = nullptr; } - MMKV::initializeMMKV(kstr, (MMKVLogLevel) logLevel, logHandler); + mmkv::MMKVHandler *handler = (logReDirecting || hasCallback) ? &g_jniHandler : nullptr; + MMKV::initializeMMKV(kstr, (MMKVLogLevel) logLevel, handler); env->ReleaseStringUTFChars(rootDir, kstr); g_android_tmpDir = jstring2string(env, cacheDir); - if (hasCallback == JNI_TRUE) { - MMKV::registerErrorHandler(onMMKVError); + if (hasCallback == JNI_TRUE || logReDirecting == JNI_TRUE) { + MMKV::registerHandler(&g_jniHandler); } else { - MMKV::unRegisterErrorHandler(); + MMKV::unRegisterHandler(); } } } @@ -227,24 +303,6 @@ static JNIEnv *getCurrentEnv() { return nullptr; } -MMKVRecoverStrategic onMMKVError(const std::string &mmapID, MMKVErrorType errorType) { - jmethodID methodID = nullptr; - if (errorType == MMKVCRCCheckFail) { - methodID = g_callbackOnCRCFailID; - } else if (errorType == MMKVFileLength) { - methodID = g_callbackOnFileLengthErrorID; - } - - auto currentEnv = getCurrentEnv(); - if (currentEnv && methodID) { - jstring str = string2jstring(currentEnv, mmapID); - auto strategic = currentEnv->CallStaticIntMethod(g_cls, methodID, str); - currentEnv->DeleteLocalRef(str); - return static_cast(strategic); - } - return OnErrorDiscard; -} - extern "C" void internalLogWithLevel(MMKVLogLevel level, const char *filename, const char *func, int line, const char *format, ...) { if (level >= g_currentLogLevel) { std::string message; @@ -294,15 +352,6 @@ static void androidLogWrapper(MMKVLogLevel level, const char *file, int line, co g_androidLogHandler(level, file, line, function, message.c_str()); } -static void onContentChangedByOuterProcess(const std::string &mmapID) { - auto currentEnv = getCurrentEnv(); - if (currentEnv && g_callbackOnContentChange) { - jstring str = string2jstring(currentEnv, mmapID); - currentEnv->CallStaticVoidMethod(g_cls, g_callbackOnContentChange, str); - currentEnv->DeleteLocalRef(str); - } -} - MMKV_JNI jlong getMMKVWithID(JNIEnv *env, jobject, jstring mmapID, jint mode, jstring cryptKey, jstring rootPath, jlong expectedCapacity, jboolean aes256, jint enableKeyExpire, jint expiredInSeconds, jboolean enableCompareBeforeSet, jint recover, jint itemSizeLimit) { @@ -944,22 +993,18 @@ MMKV_JNI void setLogLevel(JNIEnv *env, jclass type, jint level) { } MMKV_JNI void setCallbackHandler(JNIEnv *env, jclass type, jboolean logReDirecting, jboolean hasCallback, jlong nativeHandler) { - if (logReDirecting == JNI_TRUE) { - if (nativeHandler != 0) { - g_androidLogHandler = (AndroidLogHandler) nativeHandler; - MMKV::registerLogHandler(androidLogWrapper); - } else { - MMKV::registerLogHandler(mmkvLog); - } + g_jniHandler.setLogRedirecting(logReDirecting == JNI_TRUE); + g_jniHandler.setHasCallback(hasCallback == JNI_TRUE); + if (logReDirecting && nativeHandler != 0) { + g_androidLogHandler = (AndroidLogHandler) nativeHandler; } else { g_androidLogHandler = nullptr; - MMKV::unRegisterLogHandler(); } - if (hasCallback == JNI_TRUE) { - MMKV::registerErrorHandler(onMMKVError); + if (hasCallback == JNI_TRUE || logReDirecting == JNI_TRUE) { + MMKV::registerHandler(&g_jniHandler); } else { - MMKV::unRegisterErrorHandler(); + MMKV::unRegisterHandler(); } } @@ -986,10 +1031,10 @@ MMKV_JNI jint writeValueToNB(JNIEnv *env, jobject instance, jlong handle, jstrin } MMKV_JNI void setWantsContentChangeNotify(JNIEnv *env, jclass type, jboolean notify) { + g_jniHandler.setWantsContentChange(notify == JNI_TRUE); + // ensure handler is registered when content change is wanted if (notify == JNI_TRUE) { - MMKV::registerContentChangeHandler(onContentChangedByOuterProcess); - } else { - MMKV::unRegisterContentChangeHandler(); + MMKV::registerHandler(&g_jniHandler); } } diff --git a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java index 03e1906d..2e6ce14f 100644 --- a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java +++ b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKV.java @@ -209,6 +209,10 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor { String ret = doInitialize(rootDir, cacheDir, loader, logLevel, gWantLogReDirecting, hasCallback, nativeLogHandler); + if (handler != null && handler.wantContentChangeNotification()) { + setWantsContentChangeNotify(true); + } + // disable process mode in release build // FIXME: Find a better way to getApplicationInfo() without using context. // If any one knows how, you're welcome to make a contribution. @@ -1836,6 +1840,9 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor { gWantLogReDirecting = gCallbackHandler.wantLogRedirecting(); long nativeLogHandler = gCallbackHandler.getNativeLogHandler(); setCallbackHandler(gWantLogReDirecting, true, nativeLogHandler); + if (gCallbackHandler.wantContentChangeNotification()) { + setWantsContentChangeNotify(true); + } } /** @@ -1846,6 +1853,7 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor { setCallbackHandler(false, false, 0); gWantLogReDirecting = false; + setWantsContentChangeNotify(gContentChangeNotify != null); } private static int onMMKVCRCCheckFail(String mmapID) { @@ -1912,26 +1920,40 @@ public class MMKV implements SharedPreferences, SharedPreferences.Editor { * For example {@link #checkContentChangedByOuterProcess()}. * * @param notify The notification handler. + * @deprecated Use {@link MMKVHandler#onContentChangedByOuterProcess(String)} instead. */ + @Deprecated public static void registerContentChangeNotify(MMKVContentChangeNotification notify) { gContentChangeNotify = notify; - setWantsContentChangeNotify(gContentChangeNotify != null); + setWantsContentChangeNotify(gContentChangeNotify != null || (gCallbackHandler != null && gCallbackHandler.wantContentChangeNotification())); } /** * Unregister for MMKV inter-process content change notification. + * @deprecated Use {@link MMKVHandler#onContentChangedByOuterProcess(String)} instead. */ + @Deprecated public static void unregisterContentChangeNotify() { gContentChangeNotify = null; - setWantsContentChangeNotify(false); + setWantsContentChangeNotify(gCallbackHandler != null && gCallbackHandler.wantContentChangeNotification()); } private static void onContentChangedByOuterProcess(String mmapID) { - if (gContentChangeNotify != null) { + MMKVHandler handler = gCallbackHandler; + if (handler != null && handler.wantContentChangeNotification()) { + handler.onContentChangedByOuterProcess(mmapID); + } else if (gContentChangeNotify != null) { gContentChangeNotify.onContentChangedByOuterProcess(mmapID); } } + private static void onMMKVContentLoadSuccessfully(String mmapID) { + MMKVHandler handler = gCallbackHandler; + if (handler != null) { + handler.onMMKVContentLoadSuccessfully(mmapID); + } + } + private static native void setWantsContentChangeNotify(boolean needsNotify); /** diff --git a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java index f9d62555..af896be7 100644 --- a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java +++ b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVContentChangeNotification.java @@ -23,7 +23,9 @@ package com.tencent.mmkv; /** * Inter-process content change notification. * Triggered by any method call, such as getXXX() or setXXX() or {@link MMKV#checkContentChangedByOuterProcess()}. + * @deprecated Use {@link MMKVHandler#onContentChangedByOuterProcess(String)} instead. */ +@Deprecated public interface MMKVContentChangeNotification { /** * Inter-process content change notification. diff --git a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java index 4bc98f2a..13bc5d41 100644 --- a/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java +++ b/Android/MMKV/mmkv/src/main/java/com/tencent/mmkv/MMKVHandler.java @@ -21,7 +21,7 @@ package com.tencent.mmkv; /** - * Callback handler for MMKV. + * Unified callback handler for MMKV. * Callback is called on the operating thread of the MMKV instance. */ public interface MMKVHandler { @@ -59,5 +59,23 @@ public interface MMKVHandler { * @return a native log handler with signature of void log(int level, const char *file, int line, const char *function, const char *message) * return 0 to indicate no native handler */ - long getNativeLogHandler(); + default long getNativeLogHandler() { return 0; } + + /** + * @return Return true if you want inter-process content change notification. + */ + default boolean wantContentChangeNotification() { return false; } + + /** + * Inter-process content change notification. + * Triggered by any method call, such as getXXX() or setXXX() or {@link MMKV#checkContentChangedByOuterProcess()}. + * @param mmapID The unique ID of the changed MMKV instance. + */ + default void onContentChangedByOuterProcess(String mmapID) {} + + /** + * Called when an MMKV file is loaded successfully. + * @param mmapID The unique ID of the loaded MMKV instance. + */ + default void onMMKVContentLoadSuccessfully(String mmapID) {} } diff --git a/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java b/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java index 470bac45..a23a37d9 100644 --- a/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java +++ b/Android/MMKV/mmkvdemo/src/main/java/com/tencent/mmkvdemo/MyApplication.java @@ -24,13 +24,12 @@ import android.app.Application; import android.util.Log; import com.getkeepsafe.relinker.ReLinker; import com.tencent.mmkv.MMKV; -import com.tencent.mmkv.MMKVContentChangeNotification; import com.tencent.mmkv.MMKVHandler; import com.tencent.mmkv.MMKVLogLevel; import com.tencent.mmkv.MMKVRecoverStrategic; import com.tencent.mmkv.NameSpace; -public class MyApplication extends Application implements MMKVHandler, MMKVContentChangeNotification { +public class MyApplication extends Application implements MMKVHandler { @Override public void onCreate() { super.onCreate(); @@ -46,7 +45,7 @@ public class MyApplication extends Application implements MMKVHandler, MMKVConte public void loadLibrary(String libName) { ReLinker.loadLibrary(MyApplication.this, libName); } - }, MMKVLogLevel.LevelInfo,this); + }, MMKVLogLevel.LevelInfo, this); Log.i("MMKV", "mmkv root: " + rootDir); // set log level @@ -58,8 +57,8 @@ public class MyApplication extends Application implements MMKVHandler, MMKVConte // register log redirecting & recover handler is moved into MMKV.initialize() // MMKV.registerHandler(this); - // content change notification - MMKV.registerContentChangeNotify(this); + // deprecated: content change notification + // MMKV.registerContentChangeNotify(this); } @Override @@ -112,6 +111,16 @@ public class MyApplication extends Application implements MMKVHandler, MMKVConte Log.i("MMKV", "other process has changed content of : " + mmapID); } + @Override + public boolean wantContentChangeNotification() { + return true; + } + + @Override + public void onMMKVContentLoadSuccessfully(String mmapID) { + Log.i("MMKV", "content load successfully : " + mmapID); + } + private void testNameSpace() { final String nsRoot = getFilesDir().getAbsolutePath() + "/namespace"; final NameSpace ns = MMKV.nameSpace(nsRoot); diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 096ffacd..9d7b9f18 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -193,6 +193,7 @@ copy_files( MMKVPredef.h MMBuffer.h MiniPBCoder.h + MMKVHandler.h ${CMAKE_CURRENT_SOURCE_DIR}/include/MMKV) #message(STATUS "copying headers to ${CMAKE_CURRENT_SOURCE_DIR}/include/MMKV") diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 68489ee3..3728ee89 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + CB0798F02F6A4B3C00B74BB8 /* MMKVHandler.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB0798EF2F6A4A6D00B74BB8 /* MMKVHandler.h */; }; + CB0798F12F6A4B4200B74BB8 /* MMKVHandler.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB0798EF2F6A4A6D00B74BB8 /* MMKVHandler.h */; }; CB0DCC45242B384E009AFE59 /* openssl_md5.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB95640223AB2E9100ACCD39 /* openssl_md5.h */; }; CB0DCC46242B3856009AFE59 /* openssl_opensslconf.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CB95640023AB2E9100ACCD39 /* openssl_opensslconf.h */; }; CB0DCC6D242B5786009AFE59 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = CBF3450323B4BABA00168AC7 /* libz.tbd */; }; @@ -88,6 +90,7 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + CB0798F02F6A4B3C00B74BB8 /* MMKVHandler.h in CopyFiles */, CBE668A32D5C8746005C184E /* MiniPBCoder.h in CopyFiles */, CB0DCC46242B3856009AFE59 /* openssl_opensslconf.h in CopyFiles */, CB0DCC45242B384E009AFE59 /* openssl_md5.h in CopyFiles */, @@ -107,6 +110,7 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + CB0798F12F6A4B4200B74BB8 /* MMKVHandler.h in CopyFiles */, CBE668A42D5C9467005C184E /* MiniPBCoder.h in CopyFiles */, CBF1906A243D70BA001C82ED /* openssl_opensslconf.h in CopyFiles */, CBF1906B243D70BA001C82ED /* openssl_md5.h in CopyFiles */, @@ -123,6 +127,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + CB0798EF2F6A4A6D00B74BB8 /* MMKVHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMKVHandler.h; sourceTree = ""; }; CB0DCC70242B57E5009AFE59 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/libc++.tbd"; sourceTree = DEVELOPER_DIR; }; CB30A3EC24987C32007171B1 /* CodedInputDataCrypt.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = CodedInputDataCrypt.cpp; sourceTree = ""; }; CB30A3ED24987C32007171B1 /* CodedInputDataCrypt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CodedInputDataCrypt.h; sourceTree = ""; }; @@ -244,6 +249,7 @@ CB7C029924A0F65B008D77E6 /* MMKV_IO.cpp */, CB467F862431D3ED00FD7421 /* MMKV_OSX.h */, CBD723BE23B5C22800D3CDAF /* MMKV_OSX.cpp */, + CB0798EF2F6A4A6D00B74BB8 /* MMKVHandler.h */, CB95641123AB2E9100ACCD39 /* MMKVLog.cpp */, CB9563F423AB2E9100ACCD39 /* MMKVLog.h */, CB9563EC23AB2E9100ACCD39 /* MMKVMetaInfo.hpp */, diff --git a/Core/MMKV.cpp b/Core/MMKV.cpp index 11567f92..fb20d9e9 100755 --- a/Core/MMKV.cpp +++ b/Core/MMKV.cpp @@ -63,7 +63,6 @@ MMKVPath_t g_rootDir; MMKVPath_t g_realRootDir; static ThreadLock *g_namespaceLock; static unordered_map g_realRootMap; -static mmkv::ErrorHandler g_errorHandler; size_t mmkv::DEFAULT_MMAP_SIZE; MMKV_NAMESPACE_BEGIN @@ -210,9 +209,9 @@ static void ensureMinimalInitialize() { ThreadLock::ThreadOnce(&once_control, initialize); } -void MMKV::initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel, mmkv::LogHandler handler) { +void MMKV::initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel, mmkv::MMKVHandler *handler) { g_currentLogLevel = logLevel; - g_logHandler = handler; + g_handler = handler; ensureMinimalInitialize(); @@ -316,18 +315,15 @@ const string &MMKV::mmapID() const { return m_mmapID; } -mmkv::ContentChangeHandler g_contentChangeHandler = nullptr; -mmkv::ContentLoadedHandler g_contentLoadedHandler = nullptr; - void MMKV::notifyContentChanged() { - if (g_contentChangeHandler) { - g_contentChangeHandler(m_mmapID); + if (g_handler) { + g_handler->onContentChangedByOuterProcess(m_mmapID); } } void MMKV::notifyContentLoaded() { - if (g_contentLoadedHandler) { - g_contentLoadedHandler(m_mmapID); + if (g_handler) { + g_handler->onMMKVContentLoadSuccessfully(m_mmapID); } } @@ -336,22 +332,6 @@ void MMKV::checkContentChanged() { checkLoadData(); } -void MMKV::registerContentChangeHandler(mmkv::ContentChangeHandler handler) { - g_contentChangeHandler = handler; -} - -void MMKV::unRegisterContentChangeHandler() { - g_contentChangeHandler = nullptr; -} - -void MMKV::registerContentLoadedHandler(mmkv::ContentChangeHandler handler) { - g_contentLoadedHandler = handler; -} - -void MMKV::unRegisterContentLoadedHandler() { - g_contentLoadedHandler = nullptr; -} - void MMKV::clearMemoryCache(bool keepSpace) { SCOPED_LOCK(m_lock); if (m_needLoadFromFile) { @@ -1603,36 +1583,20 @@ size_t MMKV::restoreAllFromDirectory(const MMKVPath_t &srcDir, const MMKVPath_t // callbacks -void MMKV::registerErrorHandler(ErrorHandler handler) { +void MMKV::registerHandler(mmkv::MMKVHandler *handler) { if (!g_instanceLock) { return; } SCOPED_LOCK(g_instanceLock); - g_errorHandler = handler; + g_handler = handler; } -void MMKV::unRegisterErrorHandler() { +void MMKV::unRegisterHandler() { if (!g_instanceLock) { return; } SCOPED_LOCK(g_instanceLock); - g_errorHandler = nullptr; -} - -void MMKV::registerLogHandler(LogHandler handler) { - if (!g_instanceLock) { - return; - } - SCOPED_LOCK(g_instanceLock); - g_logHandler = handler; -} - -void MMKV::unRegisterLogHandler() { - if (!g_instanceLock) { - return; - } - SCOPED_LOCK(g_instanceLock); - g_logHandler = nullptr; + g_handler = nullptr; } void MMKV::setLogLevel(MMKVLogLevel level) { @@ -1759,15 +1723,15 @@ MMKVPath_t crcPathWithPath(const MMKVPath_t &kvPath) { } MMKVRecoverStrategic onMMKVCRCCheckFail(const string &mmapID) { - if (g_errorHandler) { - return g_errorHandler(mmapID, MMKVErrorType::MMKVCRCCheckFail); + if (g_handler) { + return g_handler->onMMKVCRCCheckFail(mmapID); } return OnErrorDiscard; } MMKVRecoverStrategic onMMKVFileLengthError(const string &mmapID) { - if (g_errorHandler) { - return g_errorHandler(mmapID, MMKVErrorType::MMKVFileLength); + if (g_handler) { + return g_handler->onMMKVFileLengthError(mmapID); } return OnErrorDiscard; } diff --git a/Core/MMKV.h b/Core/MMKV.h index dff82f69..0b2d8d2a 100755 --- a/Core/MMKV.h +++ b/Core/MMKV.h @@ -33,6 +33,7 @@ #endif #include "MiniPBCoder.h" +#include "MMKVHandler.h" #include #include @@ -298,7 +299,7 @@ class MMKV_EXPORT MMKV { public: // call this before getting any MMKV instance - static void initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel = MMKVLogInfo, mmkv::LogHandler handler = nullptr); + static void initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel = MMKVLogInfo, mmkv::MMKVHandler *handler = nullptr); // a generic purpose instance static MMKV *defaultMMKV(MMKVMode mode = MMKV_SINGLE_PROCESS, const std::string *cryptKey = nullptr, bool aes256 = false); @@ -654,29 +655,14 @@ public: // check if content been changed by other process void checkContentChanged(); - // called when content is changed by other process - // doesn't guarantee real-time notification - static void registerContentChangeHandler(mmkv::ContentChangeHandler handler); - static void unRegisterContentChangeHandler(); - - // called when content is loaded successfully - static void registerContentLoadedHandler(mmkv::ContentLoadedHandler handler); - static void unRegisterContentLoadedHandler(); - - // by default MMKV will discard all datas on failure - // return `OnErrorRecover` to recover any data from file - static void registerErrorHandler(mmkv::ErrorHandler handler); - static void unRegisterErrorHandler(); + // register a unified callback handler for MMKV + static void registerHandler(mmkv::MMKVHandler *handler); + static void unRegisterHandler(); // MMKVLogInfo by default // pass MMKVLogNone to disable all logging static void setLogLevel(MMKVLogLevel level); - // by default MMKV will print log to the console - // implement this method to redirect MMKV's log - static void registerLogHandler(mmkv::LogHandler handler); - static void unRegisterLogHandler(); - // detect if the MMKV file is valid or not // Note: Don't use this to check the existence of the instance, the return value is undefined if the file was never created. static bool isFileValid(const std::string &mmapID, const MMKVPath_t *relatePath = nullptr); diff --git a/Core/MMKVHandler.h b/Core/MMKVHandler.h new file mode 100644 index 00000000..2fad03ff --- /dev/null +++ b/Core/MMKVHandler.h @@ -0,0 +1,55 @@ +/* + * Tencent is pleased to support the open source community by making + * MMKV available. + * + * Copyright (C) 2026 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of + * the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MMKVCoreHandler_h +#define MMKVCoreHandler_h + +#include "MMKVPredef.h" + +namespace mmkv { + +// unified callback handler for MMKV +class MMKVHandler { +public: + virtual ~MMKVHandler() = default; + + // by default MMKV will print log using system log + // implement this method to redirect MMKV's log + virtual void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) {} + + // by default MMKV will discard all data on crc32-check failure + // return `OnErrorRecover` to recover any data on the file + virtual MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) { return OnErrorDiscard; } + + // by default MMKV will discard all data on file length mismatch + // return `OnErrorRecover` to recover any data on the file + virtual MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) { return OnErrorDiscard; } + + // called when content is changed by other process + // doesn't guarantee real-time notification + virtual void onContentChangedByOuterProcess(const std::string &mmapID) {} + + // called when an MMKV file is loaded successfully + virtual void onMMKVContentLoadSuccessfully(const std::string &mmapID) {} +}; + +} // namespace mmkv + +#endif /* MMKVHandler_h */ diff --git a/Core/MMKVLog.cpp b/Core/MMKVLog.cpp index 35d2bd95..f2400b87 100755 --- a/Core/MMKVLog.cpp +++ b/Core/MMKVLog.cpp @@ -32,8 +32,6 @@ MMKVLogLevel g_currentLogLevel = MMKVLogDebug; MMKVLogLevel g_currentLogLevel = MMKVLogInfo; #endif -mmkv::LogHandler g_logHandler = nullptr; - #ifndef __FILE_NAME__ const char *_getFileName(const char *path) { const char *ptr = strrchr(path, '/'); @@ -48,6 +46,8 @@ const char *_getFileName(const char *path) { } #endif +mmkv::MMKVHandler *g_handler = nullptr; + MMKV_NAMESPACE_END @@ -84,8 +84,8 @@ void _MMKVLogWithLevel(MMKVLogLevel level, const char *filename, const char *fun NSString *message = [[NSString alloc] initWithFormat:nsFormat arguments:argList]; va_end(argList); - if (g_logHandler) { - g_logHandler(level, filename, line, func, message); + if (g_handler) { + g_handler->mmkvLog(level, filename, line, func, message); } else { NSLog(@"[%s] <%s:%d::%s> %@", MMKVLogLevelDesc(level), filename, line, func, message); } @@ -169,8 +169,8 @@ void _MMKVLogWithLevel(MMKVLogLevel level, const char *filename, const char *fun va_end(args); } - if (g_logHandler) { - g_logHandler(level, filename, line, func, message); + if (g_handler) { + g_handler->mmkvLog(level, filename, line, func, message); } else { #if defined(MMKV_WIN32) PrintUTF8("[%s] <%s:%d::%s> %s\n", MMKVLogLevelDesc(level), filename, line, func, message.c_str()); diff --git a/Core/MMKVLog.h b/Core/MMKVLog.h index 12274622..6f398340 100755 --- a/Core/MMKVLog.h +++ b/Core/MMKVLog.h @@ -23,6 +23,7 @@ #ifdef __cplusplus #include "MMKVPredef.h" +#include "MMKVHandler.h" #include #include @@ -39,7 +40,7 @@ void _MMKVLogWithLevel( MMKV_NAMESPACE_BEGIN extern MMKVLogLevel g_currentLogLevel; -extern mmkv::LogHandler g_logHandler; +extern mmkv::MMKVHandler *g_handler; // enable logging #define ENABLE_MMKV_LOG diff --git a/Core/MMKVLog_Android.cpp b/Core/MMKVLog_Android.cpp index 5b0c7af9..24c474f0 100644 --- a/Core/MMKVLog_Android.cpp +++ b/Core/MMKVLog_Android.cpp @@ -66,8 +66,8 @@ void _MMKVLogWithLevel(MMKVLogLevel level, const char *filename, const char *fun va_end(args); } - if (g_logHandler) { - g_logHandler(level, filename, line, func, message); + if (g_handler) { + g_handler->mmkvLog(level, filename, line, func, message); } else { auto desc = MMKVLogLevelDesc(level); OH_LOG_Print(LOG_APP, desc, 0, APP_NAME, "<%{public}s:%{public}d::%{public}s> %{public}s", @@ -121,8 +121,8 @@ void _MMKVLogWithLevel(MMKVLogLevel level, const char *filename, const char *fun va_end(args); } - if (g_logHandler) { - g_logHandler(level, filename, line, func, message); + if (g_handler) { + g_handler->mmkvLog(level, filename, line, func, message); } else { auto desc = MMKVLogLevelDesc(level); __android_log_print(desc, APP_NAME, "<%s:%d::%s> %s", filename, line, func, message.c_str()); diff --git a/Core/MMKVPredef.h b/Core/MMKVPredef.h index 88bee249..1ba1f116 100755 --- a/Core/MMKVPredef.h +++ b/Core/MMKVPredef.h @@ -169,20 +169,6 @@ MMKV_NAMESPACE_END namespace mmkv { -typedef void (*LogHandler)(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message); - -// by default MMKV will discard all datas on failure -// return `OnErrorRecover` to recover any data from file -typedef MMKVRecoverStrategic (*ErrorHandler)(const std::string &mmapID, MMKVErrorType errorType); - -// called when content is changed by other process -// doesn't guarantee real-time notification -typedef void (*ContentChangeHandler)(const std::string &mmapID); - -// called when content is loaded successfully -typedef void (*ContentLoadedHandler)(const std::string &mmapID); - - extern MMKV_EXPORT size_t DEFAULT_MMAP_SIZE; #define DEFAULT_MMAP_ID "mmkv.default" diff --git a/OpenHarmony/MMKV/src/main/cpp/flutter-bridge.cpp b/OpenHarmony/MMKV/src/main/cpp/flutter-bridge.cpp index bc2cc99d..b78b0ba6 100644 --- a/OpenHarmony/MMKV/src/main/cpp/flutter-bridge.cpp +++ b/OpenHarmony/MMKV/src/main/cpp/flutter-bridge.cpp @@ -38,13 +38,50 @@ extern string g_android_tmpDir; # define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, const char *funcname, const char *message); -LogCallback_t g_logCallback = nullptr; +using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); +using ContenctChangeCallback_t = void (*)(const char *mmapID); -static void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (g_logCallback) { - g_logCallback(level, file, line, function, message.c_str()); +class FlutterMMKVHandler : public mmkv::MMKVHandler { +public: + LogCallback_t logCallback = nullptr; + ErrorCallback_t errorCallback = nullptr; + ContenctChangeCallback_t contentChangeCallback = nullptr; + ContenctChangeCallback_t contentLoadedCallback = nullptr; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (logCallback) { + logCallback(level, file, line, function, message.c_str()); + } } -} + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVCRCCheckFail); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVFileLength); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (contentChangeCallback) { + contentChangeCallback(mmapID.c_str()); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (contentLoadedCallback) { + contentLoadedCallback(mmapID.c_str()); + } + } +}; + +static FlutterMMKVHandler g_flutterHandler; MMKV_EXPORT void *mmkvInitialize_v2(const char *rootDir, const char *cacheDir, int32_t sdkInt, int32_t logLevel, LogCallback_t callback) { if (!rootDir) { @@ -62,8 +99,8 @@ MMKV_EXPORT void *mmkvInitialize_v2(const char *rootDir, const char *cacheDir, i #endif if (callback) { - g_logCallback = callback; - MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, myLogHandler); + g_flutterHandler.logCallback = callback; + MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel); } @@ -660,40 +697,24 @@ MMKV_EXPORT bool isReadOnly(void *handle) { return false; } -using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); -static ErrorCallback_t g_errorCallback = nullptr; - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (g_errorCallback) { - return (MMKVRecoverStrategic) g_errorCallback(mmapID.c_str(), errorType); - } - return OnErrorDiscard; -} - MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { - g_errorCallback = callback; - if (callback) { - MMKV::registerErrorHandler(myErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -using ContenctChangeCallback_t = void (*)(const char *mmapID); -static ContenctChangeCallback_t g_contentChanceCallback = nullptr; - -static void myContentChangeHandler(const std::string &mmapID) { - if (g_contentChanceCallback) { - g_contentChanceCallback(mmapID.c_str()); + g_flutterHandler.errorCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.contentChangeCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); } } MMKV_EXPORT void registerContentChangeNotify(ContenctChangeCallback_t callback) { - g_contentChanceCallback = callback; - if (callback) { - MMKV::registerContentChangeHandler(myContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_flutterHandler.contentChangeCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); + } +} + +MMKV_EXPORT void registerContentLoadedNotify(ContenctChangeCallback_t callback) { + g_flutterHandler.contentLoadedCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentChangeCallback) { + MMKV::registerHandler(&g_flutterHandler); } } diff --git a/OpenHarmony/MMKV/src/main/cpp/native_bridge.cpp b/OpenHarmony/MMKV/src/main/cpp/native_bridge.cpp index c2cfaac2..67432ca3 100644 --- a/OpenHarmony/MMKV/src/main/cpp/native_bridge.cpp +++ b/OpenHarmony/MMKV/src/main/cpp/native_bridge.cpp @@ -354,13 +354,14 @@ static uint64_t NValueToUInt64(napi_env env, napi_value value, bool maybeUndefin struct CallbackInfo { napi_ref handlerRef = nullptr; - napi_ref callbacks[6] = {}; + napi_ref callbacks[7] = {}; napi_ref &wantLogRedirect = callbacks[0]; napi_ref &mmkvLog = callbacks[1]; napi_ref &onMMKVCRCCheckFail = callbacks[2]; napi_ref &onMMKVFileLengthError = callbacks[3]; napi_ref &wantContentChangeNotification = callbacks[4]; napi_ref &onContentChangedByOuterProcess = callbacks[5]; + napi_ref &onMMKVContentLoadSuccessfully = callbacks[6]; }; static const char *g_arrCallbackNames[] = { @@ -369,7 +370,8 @@ static const char *g_arrCallbackNames[] = { "onMMKVCRCCheckFail", "onMMKVFileLengthError", "wantContentChangeNotification", - "onContentChangedByOuterProcess" + "onContentChangedByOuterProcess", + "onMMKVContentLoadSuccessfully" }; static CallbackInfo g_callbackInfo; @@ -431,70 +433,102 @@ static std::tuple initCallbacks(napi_env env, napi_value callb static napi_env g_env = nullptr; -void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (!g_env) { - return; - } - - napi_value handler; - napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); - napi_value callback; - napi_get_reference_value(g_env, g_callbackInfo.mmkvLog, &callback); - - napi_value args[] = { - Int32ToNValue(g_env, level), - StringToNValue(g_env, file), - Int32ToNValue(g_env, line), - StringToNValue(g_env, function), - StringToNValue(g_env, message) - }; - - napi_value result; - napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); -} - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (!g_env) { - return OnErrorDiscard; - } - - napi_value handler; - napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); - napi_value callback; - if (errorType == MMKVCRCCheckFail) { - napi_get_reference_value(g_env, g_callbackInfo.onMMKVCRCCheckFail, &callback); - } else if (errorType == MMKVFileLength) { - napi_get_reference_value(g_env, g_callbackInfo.onMMKVFileLengthError, &callback); - } else { - return OnErrorDiscard; - } - - napi_value args[] = { StringToNValue(g_env, mmapID) }; - - napi_value result; - if (napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result) == napi_ok) { - if (!IsNValueUndefined(g_env, result)) { - return ( MMKVRecoverStrategic ) NValueToInt32(g_env, result); +// C++ adapter class that bridges mmkv::MMKVHandler to NAPI +class NAPIMMKVHandler : public mmkv::MMKVHandler { +public: + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) override { + if (!g_env || !g_callbackInfo.mmkvLog) { + return; } - } - return OnErrorDiscard; -} + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.mmkvLog, &callback); -static void myContentNotificationHandler(const std::string &mmapID) { - if (!g_env) { - return; + napi_value args[] = { + Int32ToNValue(g_env, level), + StringToNValue(g_env, file), + Int32ToNValue(g_env, line), + StringToNValue(g_env, function), + StringToNValue(g_env, message) + }; + + napi_value result; + napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); } - napi_value handler; - napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); - napi_value callback; - napi_get_reference_value(g_env, g_callbackInfo.onContentChangedByOuterProcess, &callback); + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (!g_env || !g_callbackInfo.onMMKVCRCCheckFail) { + return OnErrorDiscard; + } + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.onMMKVCRCCheckFail, &callback); - napi_value args[] = { StringToNValue(g_env, mmapID) }; + napi_value args[] = { StringToNValue(g_env, mmapID) }; - napi_value result; - napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); -} + napi_value result; + if (napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result) == napi_ok) { + if (!IsNValueUndefined(g_env, result)) { + return ( MMKVRecoverStrategic ) NValueToInt32(g_env, result); + } + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (!g_env || !g_callbackInfo.onMMKVFileLengthError) { + return OnErrorDiscard; + } + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.onMMKVFileLengthError, &callback); + + napi_value args[] = { StringToNValue(g_env, mmapID) }; + + napi_value result; + if (napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result) == napi_ok) { + if (!IsNValueUndefined(g_env, result)) { + return ( MMKVRecoverStrategic ) NValueToInt32(g_env, result); + } + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (!g_env || !g_callbackInfo.onContentChangedByOuterProcess) { + return; + } + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.onContentChangedByOuterProcess, &callback); + + napi_value args[] = { StringToNValue(g_env, mmapID) }; + + napi_value result; + napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (!g_env || !g_callbackInfo.onMMKVContentLoadSuccessfully) { + return; + } + napi_value handler; + napi_get_reference_value(g_env, g_callbackInfo.handlerRef, &handler); + napi_value callback; + napi_get_reference_value(g_env, g_callbackInfo.onMMKVContentLoadSuccessfully, &callback); + + napi_value args[] = { StringToNValue(g_env, mmapID) }; + + napi_value result; + napi_call_function(g_env, handler, callback, sizeof(args) / sizeof(args[0]), args, &result); + } +}; + +static NAPIMMKVHandler g_napiHandler; static napi_value initialize(napi_env env, napi_callback_info info) { g_env = env; @@ -510,19 +544,16 @@ static napi_value initialize(napi_env env, napi_callback_info info) { NAPI_CALL(napi_get_value_int32(env, args[2], &logLevel)); auto [hasCallback, wantLogRedirect, wantContentChangeNotification] = initCallbacks(env, args[3]); - auto logHandler = wantLogRedirect ? myLogHandler : nullptr; + mmkv::MMKVHandler *handler = (hasCallback || wantLogRedirect) ? &g_napiHandler : nullptr; MMKVInfo("rootDir: %s, cacheDir: %s, log level:%d, has callback: %d, want log redirect: %d", rootDir.c_str(), cacheDir.c_str(), logLevel, hasCallback, wantLogRedirect); - MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, logHandler); + MMKV::initializeMMKV(rootDir, (MMKVLogLevel) logLevel, handler); g_android_tmpDir = cacheDir; - if (hasCallback) { - MMKV::registerErrorHandler(myErrorHandler); - } - if (wantContentChangeNotification) { - MMKV::registerContentChangeHandler(myContentNotificationHandler); + if (hasCallback || wantLogRedirect || wantContentChangeNotification) { + MMKV::registerHandler(&g_napiHandler); } return StringToNValue(env, MMKV::getRootDir()); diff --git a/OpenHarmony/MMKV/src/main/ets/utils/MMKVHandler.ets b/OpenHarmony/MMKV/src/main/ets/utils/MMKVHandler.ets index adabf771..004332af 100644 --- a/OpenHarmony/MMKV/src/main/ets/utils/MMKVHandler.ets +++ b/OpenHarmony/MMKV/src/main/ets/utils/MMKVHandler.ets @@ -108,4 +108,12 @@ export abstract class MMKVHandler { public onContentChangedByOuterProcess(mmapID: string): void { return; } + + /** + * Called when an MMKV file is loaded successfully. + * @param mmapID The unique ID of the loaded MMKV instance. + */ + public onMMKVContentLoadSuccessfully(mmapID: string): void { + return; + } } diff --git a/POSIX/golang/callback.go b/POSIX/golang/callback.go index 1c63c447..6cc47eea 100644 --- a/POSIX/golang/callback.go +++ b/POSIX/golang/callback.go @@ -27,34 +27,6 @@ package mmkv */ import "C" -// the callback type of Logger -type LogHandler func(level int, file string, line int, function string, message string) - -var gLogHandler LogHandler - -// register a log callback -// -// Deprecated: use mmkv.InitializeMMKVWithLogLevelAndHandler(rootDir, logLevel, logHandler) instead. -func RegisterLogHandler(logHandler LogHandler) { - gLogHandler = logHandler - - C.setWantsLogRedirect(C.bool(true)) -} - -// unregister a log callback -func UnRegisterLogHandler() { - gLogHandler = nil - - C.setWantsLogRedirect(C.bool(false)) -} - -//export myLogHandler -func myLogHandler(level int, file string, line int, function string, message string) { - if gLogHandler != nil { - gLogHandler(level, file, line, function, message) - } -} - const ( OnErrorDiscard = iota // When there's an error, MMKV will discard everything by default. OnErrorRecover // When there's an error, MMKV will try to recover as much data as possible. @@ -65,57 +37,91 @@ const ( MMKVFileLength ) -// the callback type of error handler -// error is either MMKVCRCCheckFail or MMKVFileLength -// return OnErrorDiscard (default) or OnErrorRecover -type ErrorHandler func(mmapID string, error int) int +// Handler is the unified callback interface for MMKV. +type Handler interface { + // WantLogRedirect returns true to enable log redirecting. + WantLogRedirect() bool -var gErrorHandler ErrorHandler + // MMKVLog is called for each log message when log redirecting is enabled. + MMKVLog(level int, file string, line int, function string, message string) -// register a error callback -func RegisterErrorHandler(errorHandler ErrorHandler) { - gErrorHandler = errorHandler + // OnMMKVCRCCheckFail is called when a CRC check fails. + // Return OnErrorDiscard (default) or OnErrorRecover. + OnMMKVCRCCheckFail(mmapID string) int - C.setWantsErrorHandle(C.bool(true)) + // OnMMKVFileLengthError is called when a file length mismatch is detected. + // Return OnErrorDiscard (default) or OnErrorRecover. + OnMMKVFileLengthError(mmapID string) int + + // WantContentChangeNotification returns true to enable inter-process content change notifications. + WantContentChangeNotification() bool + + // OnContentChangedByOuterProcess is called when content is changed by another process. + OnContentChangedByOuterProcess(mmapID string) + + // OnMMKVContentLoadSuccessfully is called when an MMKV file is loaded successfully. + OnMMKVContentLoadSuccessfully(mmapID string) } -// unregister a error callback -func UnRegisterErrorHandler() { - gErrorHandler = nil +// DefaultHandler provides default implementations for all Handler methods. +// Embed this in your handler struct to only override the methods you need. +type DefaultHandler struct{} - C.setWantsErrorHandle(C.bool(false)) +func (d DefaultHandler) WantLogRedirect() bool { return false } +func (d DefaultHandler) MMKVLog(level int, file string, line int, function string, message string) {} +func (d DefaultHandler) OnMMKVCRCCheckFail(mmapID string) int { return OnErrorDiscard } +func (d DefaultHandler) OnMMKVFileLengthError(mmapID string) int { return OnErrorDiscard } +func (d DefaultHandler) WantContentChangeNotification() bool { return false } +func (d DefaultHandler) OnContentChangedByOuterProcess(mmapID string) {} +func (d DefaultHandler) OnMMKVContentLoadSuccessfully(mmapID string) {} + +var gHandler Handler + +// RegisterHandler registers a unified callback handler for MMKV. +func RegisterHandler(handler Handler) { + gHandler = handler + + wantLog := handler.WantLogRedirect() + wantContent := handler.WantContentChangeNotification() + C.setWantsHandler(C.bool(true), C.bool(wantLog), C.bool(wantContent)) +} + +// UnRegisterHandler unregisters the callback handler for MMKV. +func UnRegisterHandler() { + gHandler = nil + + C.setWantsHandler(C.bool(false), C.bool(false), C.bool(false)) +} + +//export myLogHandler +func myLogHandler(level int, file string, line int, function string, message string) { + if gHandler != nil { + gHandler.MMKVLog(level, file, line, function, message) + } } //export myErrorHandler func myErrorHandler(mmapID string, error int) int { - if gErrorHandler != nil { - return gErrorHandler(mmapID, error) + if gHandler != nil { + if error == MMKVCRCCheckFail { + return gHandler.OnMMKVCRCCheckFail(mmapID) + } else if error == MMKVFileLength { + return gHandler.OnMMKVFileLengthError(mmapID) + } } return OnErrorDiscard } -// the type of content change handler -type ContentChangeHandler func(mmapID string) - -var gContentChangeHandler ContentChangeHandler - -// register a content change callback -func RegisterContentChangeHandler(contentChangeHandler ContentChangeHandler) { - gContentChangeHandler = contentChangeHandler - - C.setWantsContentChangeHandle(C.bool(true)) -} - -// unregister a content change callback -func UnRegisterContentChangeHandler() { - gContentChangeHandler = nil - - C.setWantsContentChangeHandle(C.bool(false)) -} - //export myContentChangeHandler func myContentChangeHandler(mmapID string) { - if gContentChangeHandler != nil { - gContentChangeHandler(mmapID) + if gHandler != nil { + gHandler.OnContentChangedByOuterProcess(mmapID) + } +} + +//export myContentLoadedHandler +func myContentLoadedHandler(mmapID string) { + if gHandler != nil { + gHandler.OnMMKVContentLoadSuccessfully(mmapID) } } diff --git a/POSIX/golang/golang-bridge.cpp b/POSIX/golang/golang-bridge.cpp index 310c4af4..fe4a7880 100644 --- a/POSIX/golang/golang-bridge.cpp +++ b/POSIX/golang/golang-bridge.cpp @@ -34,13 +34,58 @@ using namespace std; # endif # define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) -void cLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message); +extern "C" void myLogHandler(int64_t level, GoStringWrap file, int64_t line, GoStringWrap function, GoStringWrap message); +extern "C" int64_t myErrorHandler(GoStringWrap mmapID, int64_t error); +extern "C" void myContentChangeHandler(GoStringWrap mmapID); +extern "C" void myContentLoadedHandler(GoStringWrap mmapID); + +class GoMMKVHandler : public mmkv::MMKVHandler { +public: + bool m_wantLog = false; + bool m_wantContentChange = false; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (!m_wantLog) { + return; + } + GoStringWrap oFile { file, static_cast(strlen(file)) }; + GoStringWrap oFunction { function, static_cast(strlen(function)) }; + GoStringWrap oMessage { message.data(), static_cast(message.length()) }; + myLogHandler(level, oFile, line, oFunction, oMessage); + } + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; + return static_cast(myErrorHandler(oID, static_cast(MMKVCRCCheckFail))); + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; + return static_cast(myErrorHandler(oID, static_cast(MMKVFileLength))); + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (!m_wantContentChange) { + return; + } + GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; + myContentChangeHandler(oID); + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; + myContentLoadedHandler(oID); + } +}; + +static GoMMKVHandler g_goHandler; MMKV_EXPORT void mmkvInitialize(GoStringWrap rootDir, int32_t logLevel, bool redirect) { if (!rootDir.ptr) { return; } - mmkv::LogHandler handler = redirect ? cLogHandler : nullptr; + g_goHandler.m_wantLog = redirect; + mmkv::MMKVHandler *handler = redirect ? &g_goHandler : nullptr; MMKV::initializeMMKV(string(rootDir.ptr, rootDir.length), (MMKVLogLevel) logLevel, handler); } @@ -654,53 +699,15 @@ MMKV_EXPORT bool disableCompareBeforeSet(void *handle) { return false; } -extern "C" void myLogHandler(int64_t level, GoStringWrap file, int64_t line, GoStringWrap function, GoStringWrap message); - -void cLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) { - GoStringWrap oFile { file, static_cast(strlen(file)) }; - GoStringWrap oFunction { function, static_cast(strlen(function)) }; - GoStringWrap oMessage { message.data(), static_cast(message.length()) }; - - myLogHandler(level, oFile, line, oFunction, oMessage); -} - -void setWantsLogRedirect(bool redirect) { - if (redirect) { - MMKV::registerLogHandler(&cLogHandler); +void setWantsHandler(bool hasHandler, bool wantLog, bool wantContent) { + if (hasHandler) { + g_goHandler.m_wantLog = wantLog; + g_goHandler.m_wantContentChange = wantContent; + MMKV::registerHandler(&g_goHandler); } else { - MMKV::unRegisterLogHandler(); - } -} - -extern "C" int64_t myErrorHandler(GoStringWrap mmapID, int64_t error); - -static MMKVRecoverStrategic cErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; - - return static_cast(myErrorHandler(oID, static_cast(errorType))); -} - -void setWantsErrorHandle(bool errorHandle) { - if (errorHandle) { - MMKV::registerErrorHandler(&cErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -extern "C" void myContentChangeHandler(GoStringWrap mmapID); - -static void cContentChangeHandler(const std::string &mmapID) { - GoStringWrap oID { mmapID.data(), static_cast(mmapID.length()) }; - - myContentChangeHandler(oID); -} - -void setWantsContentChangeHandle(bool errorHandle) { - if (errorHandle) { - MMKV::registerContentChangeHandler(&cContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_goHandler.m_wantLog = false; + g_goHandler.m_wantContentChange = false; + MMKV::unRegisterHandler(); } } diff --git a/POSIX/golang/golang-bridge.h b/POSIX/golang/golang-bridge.h index d3fe1d7f..d08dd910 100644 --- a/POSIX/golang/golang-bridge.h +++ b/POSIX/golang/golang-bridge.h @@ -132,9 +132,7 @@ int32_t pageSize(); const char *version(); const char *getRootDir(); -void setWantsLogRedirect(bool redirect); -void setWantsErrorHandle(bool errorHandle); -void setWantsContentChangeHandle(bool contentChange); +void setWantsHandler(bool hasHandler, bool wantLog, bool wantContent); bool removeStorage(GoStringWrap_t mmapID, GoStringWrap_t rootPath); diff --git a/POSIX/golang/mmkv.go b/POSIX/golang/mmkv.go index 8204daab..45d6dab1 100644 --- a/POSIX/golang/mmkv.go +++ b/POSIX/golang/mmkv.go @@ -338,10 +338,23 @@ func InitializeMMKVWithLogLevel(rootDir string, logLevel int) { C.mmkvInitialize(C.wrapGoString(rootDir), C.int32_t(logLevel), C.bool(false)) } -// InitializeMMKVWithLogLevelAndHandler Same as the function InitializeMMKVWithLogLevel() above, except that you can provide a logHandler at the very beginning. -func InitializeMMKVWithLogLevelAndHandler(rootDir string, logLevel int, logHandler LogHandler) { - gLogHandler = logHandler - C.mmkvInitialize(C.wrapGoString(rootDir), C.int32_t(logLevel), C.bool(true)) +// Deprecated: Use InitializeMMKVWithLogLevel() + RegisterHandler() instead. +func InitializeMMKVWithLogLevelAndHandler(rootDir string, logLevel int, logHandler func(level int, file string, line int, function string, message string)) { + C.mmkvInitialize(C.wrapGoString(rootDir), C.int32_t(logLevel), C.bool(false)) + if logHandler != nil { + RegisterHandler(&logHandlerAdapter{logHandler: logHandler}) + } +} + +// logHandlerAdapter wraps a legacy log handler function into the Handler interface. +type logHandlerAdapter struct { + DefaultHandler + logHandler func(level int, file string, line int, function string, message string) +} + +func (a *logHandlerAdapter) WantLogRedirect() bool { return true } +func (a *logHandlerAdapter) MMKVLog(level int, file string, line int, function string, message string) { + a.logHandler(level, file, line, function, message) } // OnExit Call before App exists, it's just fine not calling it on most case (except when the device shutdown suddenly). diff --git a/POSIX/golang/test/main.go b/POSIX/golang/test/main.go index c5336328..c954a559 100644 --- a/POSIX/golang/test/main.go +++ b/POSIX/golang/test/main.go @@ -14,8 +14,8 @@ func main() { // test NameSpace before mmkv.Initialize() testNameSpace() - // init MMKV with root dir and log redirecting - mmkv.InitializeMMKVWithLogLevelAndHandler("/tmp/mmkv", mmkv.MMKVLogInfo, logHandler) + // init MMKV with root dir + mmkv.InitializeMMKVWithLogLevel("/tmp/mmkv", mmkv.MMKVLogInfo) { fmt.Println("rootDir:", mmkv.GetRootDir()) @@ -23,13 +23,8 @@ func main() { fmt.Println("DefaultNameSpace:", ns.GetRootDir()) } - // you can set log redirecting - // mmkv.RegisterLogHandler(logHandler) - - // you can set error handler - mmkv.RegisterErrorHandler(errorHandler) - // you can get notify content change by other process (not in realtime) - mmkv.RegisterContentChangeHandler(contentChangeNotify) + // register unified callback handler (log redirect, error handler, content change) + mmkv.RegisterHandler(&myHandler{}) testExpectedCapacity() functionalTest() @@ -425,7 +420,14 @@ func testImport() { } } -func logHandler(level int, file string, line int, function string, message string) { +// myHandler implements mmkv.Handler with DefaultHandler for defaults +type myHandler struct { + mmkv.DefaultHandler +} + +func (h *myHandler) WantLogRedirect() bool { return true } + +func (h *myHandler) MMKVLog(level int, file string, line int, function string, message string) { var levelStr string switch level { case mmkv.MMKVLogDebug: @@ -442,18 +444,22 @@ func logHandler(level int, file string, line int, function string, message strin fmt.Printf("Redirect %v <%v:%v::%v> %v\n", levelStr, file, line, function, message) } -func errorHandler(mmapID string, error int) int { - var errorDesc string - if error == mmkv.MMKVCRCCheckFail { - errorDesc = "CRC-Error" - } else { - errorDesc = "File-Length-Error" - } - fmt.Println(mmapID, "has error type:", errorDesc) - +func (h *myHandler) OnMMKVCRCCheckFail(mmapID string) int { + fmt.Println(mmapID, "has error type: CRC-Error") return mmkv.OnErrorRecover } -func contentChangeNotify(mmapID string) { +func (h *myHandler) OnMMKVFileLengthError(mmapID string) int { + fmt.Println(mmapID, "has error type: File-Length-Error") + return mmkv.OnErrorRecover +} + +func (h *myHandler) WantContentChangeNotification() bool { return true } + +func (h *myHandler) OnContentChangedByOuterProcess(mmapID string) { fmt.Println(mmapID, "content changed by other process") } + +func (h *myHandler) OnMMKVContentLoadSuccessfully(mmapID string) { + fmt.Println(mmapID, "content loaded successfully") +} diff --git a/Python/libmmkv_python.cpp b/Python/libmmkv_python.cpp index b74aab01..9e4e56c7 100644 --- a/Python/libmmkv_python.cpp +++ b/Python/libmmkv_python.cpp @@ -61,26 +61,47 @@ static MMBuffer pyBytes2MMBuffer(const py::bytes &bytes) { static function g_logHandler = nullptr; -static void MyLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, const string &message) { - if (g_logHandler) { - g_logHandler(level, file, line, function, message); - } -} - static function g_errorHandler = nullptr; -static MMKVRecoverStrategic MyErrorHandler(const string &mmapID, MMKVErrorType errorType) { - if (g_errorHandler) { - return g_errorHandler(mmapID, errorType); - } - return OnErrorDiscard; -} - static function g_contentHandler = nullptr; -static void MyContentChangeHandler(const std::string &mmapID) { - if (g_contentHandler) { - g_contentHandler(mmapID); +static function g_contentLoadedHandler = nullptr; + +// C++ handler that bridges to Python callbacks +class PyMMKVHandler : public mmkv::MMKVHandler { +public: + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) override { + if (g_logHandler) { + g_logHandler(level, file, line, function, message); + } } -} + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (g_errorHandler) { + return g_errorHandler(mmapID, MMKVErrorType::MMKVCRCCheckFail); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (g_errorHandler) { + return g_errorHandler(mmapID, MMKVErrorType::MMKVFileLength); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (g_contentHandler) { + g_contentHandler(mmapID); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (g_contentLoadedHandler) { + g_contentLoadedHandler(mmapID); + } + } +}; + +static PyMMKVHandler g_pyHandler; PYBIND11_MODULE(mmkv, m) { m.doc() = "An efficient, small key-value storage framework developed by WeChat Team."; @@ -207,7 +228,7 @@ PYBIND11_MODULE(mmkv, m) { [](const MMKVPath_t &rootDir, MMKVLogLevel logLevel, decltype(g_logHandler) logHandler) { if (logHandler) { g_logHandler = std::move(logHandler); - MMKV::initializeMMKV(rootDir, logLevel, MyLogHandler); + MMKV::initializeMMKV(rootDir, logLevel, &g_pyHandler); } else { MMKV::initializeMMKV(rootDir, logLevel, nullptr); } @@ -380,69 +401,41 @@ PYBIND11_MODULE(mmkv, m) { clsMMKV.def_static("rootDir", &MMKV::getRootDir, "get the root directory of MMKV"); - // log callback handler - clsMMKV.def_static( - "registerLogHandler", - [](decltype(g_logHandler) callback) { - g_logHandler = std::move(callback); - MMKV::registerLogHandler(MyLogHandler); - }, - "call this method to redirect MMKV's log,\n" - "must call MMKV.unRegisterLogHandler() or MMKV.onExit() before exit\n" - "Parameters:\n" - " log_handler: (logLevel: mmkv.MMKVLogLevel, file: str, line: int, function: str, message: str) -> None", - py::arg("log_handler")); - clsMMKV.def_static( - "unRegisterLogHandler", - [] { - g_logHandler = nullptr; - MMKV::unRegisterLogHandler(); - }, - "If you have registered a log handler, you must call this method or MMKV.onExit() before exit. " - "Otherwise your app/script won't exit properly."); - - // error callback handler - clsMMKV.def_static( - "registerErrorHandler", - [](decltype(g_errorHandler) callback) { - g_errorHandler = std::move(callback); - MMKV::registerErrorHandler(MyErrorHandler); - }, - "call this method to handle MMKV failure,\n" - "must call MMKV.unRegisterErrorHandler() or MMKV.onExit() before exit\n" - "Parameters:\n" - " error_handler: (mmapID: str, errorType: mmkv.MMKVErrorType) -> mmkv.MMKVRecoverStrategic", - py::arg("error_handler")); - clsMMKV.def_static( - "unRegisterErrorHandler", - [] { - g_errorHandler = nullptr; - MMKV::unRegisterErrorHandler(); - }, - "If you have registered an error handler, you must call this method or MMKV.onExit() before exit. " - "Otherwise your app/script won't exit properly."); - - // content change callback handler + // unified callback handler clsMMKV.def("checkContentChanged", &MMKV::checkContentChanged, "check if content been changed by other process"); clsMMKV.def_static( - "registerContentChangeHandler", - [](decltype(g_contentHandler) callback) { - g_contentHandler = std::move(callback); - MMKV::registerContentChangeHandler(MyContentChangeHandler); + "registerHandler", + [](decltype(g_logHandler) logHandler, + decltype(g_errorHandler) errorHandler, + decltype(g_contentHandler) contentChangeHandler, + decltype(g_contentLoadedHandler) contentLoadedHandler) { + g_logHandler = logHandler ? std::move(logHandler) : nullptr; + g_errorHandler = errorHandler ? std::move(errorHandler) : nullptr; + g_contentHandler = contentChangeHandler ? std::move(contentChangeHandler) : nullptr; + g_contentLoadedHandler = contentLoadedHandler ? std::move(contentLoadedHandler) : nullptr; + MMKV::registerHandler(&g_pyHandler); }, - "register a content change handler,\n" - "get notified when an MMKV instance has been changed by other process (not guarantee real-time notification),\n" - "must call MMKV.unRegisterContentChangeHandler() or MMKV.onExit() before exit\n" + "register a unified callback handler for MMKV,\n" + "must call MMKV.unRegisterHandler() or MMKV.onExit() before exit\n" "Parameters:\n" - " content_change_handler: (mmapID: str) -> None", - py::arg("content_change_handler")); + " log_handler: (logLevel: mmkv.MMKVLogLevel, file: str, line: int, function: str, message: str) -> None\n" + " error_handler: (mmapID: str, errorType: mmkv.MMKVErrorType) -> mmkv.MMKVRecoverStrategic\n" + " content_change_handler: (mmapID: str) -> None\n" + " content_loaded_handler: (mmapID: str) -> None", + py::arg("log_handler") = nullptr, + py::arg("error_handler") = nullptr, + py::arg("content_change_handler") = nullptr, + py::arg("content_loaded_handler") = nullptr); clsMMKV.def_static( - "unRegisterContentChangeHandler", + "unRegisterHandler", [] { + g_logHandler = nullptr; + g_errorHandler = nullptr; g_contentHandler = nullptr; - MMKV::unRegisterContentChangeHandler(); + g_contentLoadedHandler = nullptr; + MMKV::unRegisterHandler(); }, - "If you have registered a content change handler, you must call this method or MMKV.onExit() before exit. " + "If you have registered a handler, you must call this method or MMKV.onExit() before exit. " "Otherwise your app/script won't exit properly."); clsMMKV.def_static( @@ -452,6 +445,7 @@ PYBIND11_MODULE(mmkv, m) { g_logHandler = nullptr; g_errorHandler = nullptr; g_contentHandler = nullptr; + g_contentLoadedHandler = nullptr; }, "call this method before exit, especially if you have registered any callback handlers"); diff --git a/flutter/mmkv/lib/mmkv.dart b/flutter/mmkv/lib/mmkv.dart index 8a171240..8f62a0d1 100644 --- a/flutter/mmkv/lib/mmkv.dart +++ b/flutter/mmkv/lib/mmkv.dart @@ -235,6 +235,9 @@ class MMKV { final contentHandler = Pointer.fromFunction(_contentChangeHandler); _registerContentHandler(contentHandler); } + + final contentLoadedHandler = Pointer.fromFunction(_contentLoadedHandler); + _registerContentLoadedHandler(contentLoadedHandler); } return result; } @@ -919,6 +922,13 @@ void _contentChangeHandler(Pointer mmapIDPtr) { } } +void _contentLoadedHandler(Pointer mmapIDPtr) { + if (_mmkvPlatform.theHandler != null && mmapIDPtr != nullptr) { + final mmapID = _pointer2String(mmapIDPtr); + _mmkvPlatform.theHandler!.onMMKVContentLoadSuccessfully(mmapID!); + } +} + int _bool2Int(bool value) { return value ? 1 : 0; } @@ -1053,6 +1063,8 @@ final ErrorCallbackRegister _registerErrorHandler = _mmkvPlatform.registerErrorH final ContentCallbackRegister _registerContentHandler = _mmkvPlatform.registerContentHandlerFunc(); +final ContentCallbackRegister _registerContentLoadedHandler = _mmkvPlatform.registerContentLoadedHandlerFunc(); + final void Function(Pointer) _checkContentChanged = _mmkvPlatform.checkContentChangedFunc(); final bool Function(Pointer) _getNameSpace = _mmkvPlatform.getNameSpaceFunc(); diff --git a/flutter/mmkv_ios/darwin/Classes/flutter-bridge.h b/flutter/mmkv_ios/darwin/Classes/flutter-bridge.h index 0fa79133..3cda2464 100644 --- a/flutter/mmkv_ios/darwin/Classes/flutter-bridge.h +++ b/flutter/mmkv_ios/darwin/Classes/flutter-bridge.h @@ -24,6 +24,7 @@ using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, c @property (atomic, assign) ErrorCallback_t errorCallback; @property (atomic, assign) LogCallback_t logCallback; @property (atomic, assign) ContenctChangeCallback_t contenctChangeCallback; +@property (atomic, assign) ContenctChangeCallback_t contentLoadedCallback; +(MyMMKVHandler *) getHandler; diff --git a/flutter/mmkv_ios/darwin/Classes/flutter-bridge.mm b/flutter/mmkv_ios/darwin/Classes/flutter-bridge.mm index 69b6b36e..87de2928 100644 --- a/flutter/mmkv_ios/darwin/Classes/flutter-bridge.mm +++ b/flutter/mmkv_ios/darwin/Classes/flutter-bridge.mm @@ -670,6 +670,12 @@ MMKV_EXPORT bool MMKV_FUNC(isReadOnly)(const void *handle) { } } +- (void)onMMKVContentLoadSuccessfully:(NSString *)mmapID { + if (self.contentLoadedCallback) { + self.contentLoadedCallback(mmapID.UTF8String); + } +} + @end MMKV_EXPORT void MMKV_FUNC(registerErrorHandler)(ErrorCallback_t callback) { @@ -680,6 +686,10 @@ MMKV_EXPORT void MMKV_FUNC(registerContentChangeNotify)(ContenctChangeCallback_t [MyMMKVHandler getHandler].contenctChangeCallback = callback; } +MMKV_EXPORT void MMKV_FUNC(registerContentLoadedNotify)(ContenctChangeCallback_t callback) { + [MyMMKVHandler getHandler].contentLoadedCallback = callback; +} + MMKV_EXPORT void MMKV_FUNC(checkContentChanged)(const void *handle) { MMKV *kv = (__bridge MMKV*)handle; if (kv) { diff --git a/flutter/mmkv_linux/linux/flutter-bridge.cpp b/flutter/mmkv_linux/linux/flutter-bridge.cpp index cd0c1d67..7ed2b46d 100644 --- a/flutter/mmkv_linux/linux/flutter-bridge.cpp +++ b/flutter/mmkv_linux/linux/flutter-bridge.cpp @@ -36,13 +36,50 @@ using namespace std; # define MMKV_EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, const char *funcname, const char *message); -LogCallback_t g_logCallback = nullptr; +using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); +using ContenctChangeCallback_t = void (*)(const char *mmapID); -static void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (g_logCallback) { - g_logCallback(level, file, line, function, message.c_str()); +class FlutterMMKVHandler : public mmkv::MMKVHandler { +public: + LogCallback_t logCallback = nullptr; + ErrorCallback_t errorCallback = nullptr; + ContenctChangeCallback_t contentChangeCallback = nullptr; + ContenctChangeCallback_t contentLoadedCallback = nullptr; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (logCallback) { + logCallback(level, file, line, function, message.c_str()); + } } -} + + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVCRCCheckFail); + } + return OnErrorDiscard; + } + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVFileLength); + } + return OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (contentChangeCallback) { + contentChangeCallback(mmapID.c_str()); + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (contentLoadedCallback) { + contentLoadedCallback(mmapID.c_str()); + } + } +}; + +static FlutterMMKVHandler g_flutterHandler; MMKV_EXPORT void *mmkvInitialize(const char *rootDir, int32_t logLevel, LogCallback_t callback) { if (!rootDir) { @@ -51,8 +88,8 @@ MMKV_EXPORT void *mmkvInitialize(const char *rootDir, int32_t logLevel, LogCallb auto root = string(rootDir); if (callback) { - g_logCallback = callback; - MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel, myLogHandler); + g_flutterHandler.logCallback = callback; + MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel); } @@ -645,40 +682,24 @@ MMKV_EXPORT bool isReadOnly(void *handle) { return false; } -using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); -static ErrorCallback_t g_errorCallback = nullptr; - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (g_errorCallback) { - return (MMKVRecoverStrategic) g_errorCallback(mmapID.c_str(), errorType); - } - return OnErrorDiscard; -} - MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { - g_errorCallback = callback; - if (callback) { - MMKV::registerErrorHandler(myErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -using ContenctChangeCallback_t = void (*)(const char *mmapID); -static ContenctChangeCallback_t g_contentChanceCallback = nullptr; - -static void myContentChangeHandler(const std::string &mmapID) { - if (g_contentChanceCallback) { - g_contentChanceCallback(mmapID.c_str()); + g_flutterHandler.errorCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.contentChangeCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); } } MMKV_EXPORT void registerContentChangeNotify(ContenctChangeCallback_t callback) { - g_contentChanceCallback = callback; - if (callback) { - MMKV::registerContentChangeHandler(myContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_flutterHandler.contentChangeCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); + } +} + +MMKV_EXPORT void registerContentLoadedNotify(ContenctChangeCallback_t callback) { + g_flutterHandler.contentLoadedCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentChangeCallback) { + MMKV::registerHandler(&g_flutterHandler); } } diff --git a/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart b/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart index fd52ba37..9c500c0b 100644 --- a/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart +++ b/flutter/mmkv_platform_interface/lib/mmkv_platform_ffi.dart @@ -318,6 +318,11 @@ class MMKVPluginPlatformFFI extends MMKVPluginPlatform { return nativeLib().lookup>(nativeFuncName("registerContentChangeNotify")).asFunction(); } + @override + ContentCallbackRegister registerContentLoadedHandlerFunc() { + return nativeLib().lookup>(nativeFuncName("registerContentLoadedNotify")).asFunction(); + } + @override void Function(Pointer p1) checkContentChangedFunc() { return nativeLib().lookup)>>(nativeFuncName("checkContentChanged")).asFunction(); diff --git a/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart b/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart index 7616f0e4..62a29a57 100644 --- a/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart +++ b/flutter/mmkv_platform_interface/lib/mmkv_platform_interface.dart @@ -81,6 +81,12 @@ class MMKVHandler { void onContentChangedByOuterProcess(String mmapID) { return; } + + /// Called when an MMKV file is loaded successfully. + /// [mmapID] The unique ID of the loaded MMKV instance. + void onMMKVContentLoadSuccessfully(String mmapID) { + return; + } } /// The interface class that all implementation of MMKV platform plugin must extend @@ -307,6 +313,10 @@ class MMKVPluginPlatform { throw UnimplementedError(); } + ContentCallbackRegister registerContentLoadedHandlerFunc() { + throw UnimplementedError(); + } + void Function(Pointer) checkContentChangedFunc() { throw UnimplementedError(); } diff --git a/flutter/mmkv_win32/windows/flutter-bridge.cpp b/flutter/mmkv_win32/windows/flutter-bridge.cpp index 5819427a..f0401771 100644 --- a/flutter/mmkv_win32/windows/flutter-bridge.cpp +++ b/flutter/mmkv_win32/windows/flutter-bridge.cpp @@ -36,39 +36,50 @@ using namespace std; # define MMKV_EXPORT extern "C" __declspec(dllexport) using LogCallback_t = void (*)(uint32_t level, const char *file, int32_t line, const char *funcname, const char *message); -LogCallback_t g_logCallback = nullptr; +using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); +using ContenctChangeCallback_t = void (*)(const char *mmapID); -static void myLogHandler(MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) { - if (g_logCallback) { - g_logCallback(level, file, line, function, message.c_str()); +class FlutterMMKVHandler : public mmkv::MMKVHandler { +public: + LogCallback_t logCallback = nullptr; + ErrorCallback_t errorCallback = nullptr; + ContenctChangeCallback_t contentChangeCallback = nullptr; + ContenctChangeCallback_t contentLoadedCallback = nullptr; + + void mmkvLog(MMKVLogLevel level, const char *file, int line, const char *function, const std::string &message) override { + if (logCallback) { + logCallback(level, file, line, function, message.c_str()); + } } -} -static void myOutputDebugString(MMKVLogLevel level, const char *file, int line, const char *function, const char *format, ...) { - if (!g_logCallback) { - return; + MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVCRCCheckFail); + } + return OnErrorDiscard; } - va_list args; - va_start(args, format); - char buffer[1024]; - auto requiredLen = vsnprintf(buffer, sizeof(buffer), format, args); - if (requiredLen < 0) { - // Handle error in formatting - va_end(args); - g_logCallback(level, file, line, function, "Error formatting debug string\n"); - return; - } else if (requiredLen >= sizeof(buffer)) { - // If the buffer was not large enough, truncate the string - buffer[sizeof(buffer) - 1] = '\0'; + + MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if (errorCallback) { + return (MMKVRecoverStrategic) errorCallback(mmapID.c_str(), MMKVFileLength); + } + return OnErrorDiscard; } - - va_end(args); - g_logCallback(level, file, line, function, buffer); -} -extern const char *_getFileName(const char *path); + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if (contentChangeCallback) { + contentChangeCallback(mmapID.c_str()); + } + } -#define InternalLog(fmt, ...) myOutputDebugString(MMKVLogLevel::MMKVLogInfo, _getFileName(__FILE__), __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__) + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if (contentLoadedCallback) { + contentLoadedCallback(mmapID.c_str()); + } + } +}; + +static FlutterMMKVHandler g_flutterHandler; MMKV_EXPORT void *mmkvInitialize(const char *rootDir, int32_t logLevel, LogCallback_t callback) { if (!rootDir) { @@ -77,8 +88,8 @@ MMKV_EXPORT void *mmkvInitialize(const char *rootDir, int32_t logLevel, LogCallb auto root = string2MMKVPath_t(rootDir); if (callback) { - g_logCallback = callback; - MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel, myLogHandler); + g_flutterHandler.logCallback = callback; + MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel, &g_flutterHandler); } else { MMKV::initializeMMKV(root, (MMKVLogLevel) logLevel); } @@ -672,40 +683,24 @@ MMKV_EXPORT bool isReadOnly(void *handle) { return false; } -using ErrorCallback_t = int (*)(const char *mmapID, int32_t errorType); -static ErrorCallback_t g_errorCallback = nullptr; - -static MMKVRecoverStrategic myErrorHandler(const std::string &mmapID, MMKVErrorType errorType) { - if (g_errorCallback) { - return (MMKVRecoverStrategic) g_errorCallback(mmapID.c_str(), errorType); - } - return OnErrorDiscard; -} - MMKV_EXPORT void registerErrorHandler(ErrorCallback_t callback) { - g_errorCallback = callback; - if (callback) { - MMKV::registerErrorHandler(myErrorHandler); - } else { - MMKV::unRegisterErrorHandler(); - } -} - -using ContenctChangeCallback_t = void (*)(const char *mmapID); -static ContenctChangeCallback_t g_contentChanceCallback = nullptr; - -static void myContentChangeHandler(const std::string &mmapID) { - if (g_contentChanceCallback) { - g_contentChanceCallback(mmapID.c_str()); + g_flutterHandler.errorCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.contentChangeCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); } } MMKV_EXPORT void registerContentChangeNotify(ContenctChangeCallback_t callback) { - g_contentChanceCallback = callback; - if (callback) { - MMKV::registerContentChangeHandler(myContentChangeHandler); - } else { - MMKV::unRegisterContentChangeHandler(); + g_flutterHandler.contentChangeCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentLoadedCallback) { + MMKV::registerHandler(&g_flutterHandler); + } +} + +MMKV_EXPORT void registerContentLoadedNotify(ContenctChangeCallback_t callback) { + g_flutterHandler.contentLoadedCallback = callback; + if (callback || g_flutterHandler.logCallback || g_flutterHandler.errorCallback || g_flutterHandler.contentChangeCallback) { + MMKV::registerHandler(&g_flutterHandler); } } diff --git a/iOS/MMKV/MMKV/libMMKV.mm b/iOS/MMKV/MMKV/libMMKV.mm index 790965ac..4589879b 100644 --- a/iOS/MMKV/MMKV/libMMKV.mm +++ b/iOS/MMKV/MMKV/libMMKV.mm @@ -48,10 +48,47 @@ static NSString *g_groupPath = nil; static BOOL g_isRunningInAppExtension = NO; #endif -static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message); -static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType); -static void ContentChangeHandler(const string &mmapID); -static void ContentLoadedHandler(const string &mmapID); +#pragma makr - callbacks + +// C++ adapter class that bridges mmkv::MMKVHandler to Objective-C MMKVHandler protocol +class ObjCMMKVHandler : public mmkv::MMKVHandler { +public: + void mmkvLog(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, MMKVLog_t message) override { + if (g_callbackHandler && [g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { + [g_callbackHandler mmkvLogWithLevel:(MMKVLogLevel) level file:file line:line func:function message:message]; + } + } + + mmkv::MMKVRecoverStrategic onMMKVCRCCheckFail(const std::string &mmapID) override { + if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)]) { + auto ret = [g_callbackHandler onMMKVCRCCheckFail:[NSString stringWithUTF8String:mmapID.c_str()]]; + return (mmkv::MMKVRecoverStrategic) ret; + } + return mmkv::OnErrorDiscard; + } + + mmkv::MMKVRecoverStrategic onMMKVFileLengthError(const std::string &mmapID) override { + if ([g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) { + auto ret = [g_callbackHandler onMMKVFileLengthError:[NSString stringWithUTF8String:mmapID.c_str()]]; + return (mmkv::MMKVRecoverStrategic) ret; + } + return mmkv::OnErrorDiscard; + } + + void onContentChangedByOuterProcess(const std::string &mmapID) override { + if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) { + [g_callbackHandler onMMKVContentChange:[NSString stringWithUTF8String:mmapID.c_str()]]; + } + } + + void onMMKVContentLoadSuccessfully(const std::string &mmapID) override { + if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentLoadSuccessfully:)]) { + [g_callbackHandler onMMKVContentLoadSuccessfully:[NSString stringWithUTF8String:mmapID.c_str()]]; + } + } +}; + +static ObjCMMKVHandler g_cppHandler; @interface MMKVNameSpace () @@ -93,10 +130,12 @@ static BOOL g_hasCalledInitializeMMKV = NO; } [g_callbackHandler release]; g_callbackHandler = [handler retain]; - mmkv::LogHandler logHandler = nullptr; - if (g_callbackHandler && [g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { - g_isLogRedirecting = true; - logHandler = LogHandler; + mmkv::MMKVHandler *cppHandler = nullptr; + if (g_callbackHandler) { + if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { + g_isLogRedirecting = true; + } + cppHandler = &g_cppHandler; } if (rootDir != nil) { @@ -106,17 +145,10 @@ static BOOL g_hasCalledInitializeMMKV = NO; [self mmkvBasePath]; } NSAssert(g_basePath.length > 0, @"MMKV not initialized properly, must not call +initializeMMKV: before -application:didFinishLaunchingWithOptions:"); - mmkv::MMKV::initializeMMKV(g_basePath.UTF8String, (mmkv::MMKVLogLevel) logLevel, logHandler); + mmkv::MMKV::initializeMMKV(g_basePath.UTF8String, (mmkv::MMKVLogLevel) logLevel, cppHandler); - if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)] || - [g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) { - mmkv::MMKV::registerErrorHandler(ErrorHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) { - mmkv::MMKV::registerContentChangeHandler(ContentChangeHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentLoadSuccessfully:)]) { - mmkv::MMKV::registerContentLoadedHandler(ContentLoadedHandler); + if (g_callbackHandler) { + mmkv::MMKV::registerHandler(&g_cppHandler); } #if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION) && !(TARGET_OS_MACCATALYST) @@ -1128,19 +1160,14 @@ static NSString *md5(NSString *value) { [g_callbackHandler release]; g_callbackHandler = [handler retain]; - if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { - g_isLogRedirecting = true; - mmkv::MMKV::registerLogHandler(LogHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)] || - [g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) { - mmkv::MMKV::registerErrorHandler(ErrorHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) { - mmkv::MMKV::registerContentChangeHandler(ContentChangeHandler); - } - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentLoadSuccessfully:)]) { - mmkv::MMKV::registerContentLoadedHandler(ContentLoadedHandler); + if (g_callbackHandler) { + if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) { + g_isLogRedirecting = true; + } + mmkv::MMKV::registerHandler(&g_cppHandler); + } else { + g_isLogRedirecting = false; + mmkv::MMKV::unRegisterHandler(); } } @@ -1151,10 +1178,7 @@ static NSString *md5(NSString *value) { [g_callbackHandler release]; g_callbackHandler = nil; - mmkv::MMKV::unRegisterLogHandler(); - mmkv::MMKV::unRegisterErrorHandler(); - mmkv::MMKV::unRegisterContentChangeHandler(); - mmkv::MMKV::unRegisterContentLoadedHandler(); + mmkv::MMKV::unRegisterHandler(); } + (void)setLogLevel:(MMKVLogLevel)logLevel { @@ -1284,39 +1308,6 @@ static NSString *md5(NSString *value) { @end -#pragma makr - callbacks - -static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message) { - [g_callbackHandler mmkvLogWithLevel:(MMKVLogLevel) level file:file line:line func:function message:message]; -} - -static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType) { - if (errorType == mmkv::MMKVCRCCheckFail) { - if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)]) { - auto ret = [g_callbackHandler onMMKVCRCCheckFail:[NSString stringWithUTF8String:mmapID.c_str()]]; - return (mmkv::MMKVRecoverStrategic) ret; - } - } else { - if ([g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) { - auto ret = [g_callbackHandler onMMKVFileLengthError:[NSString stringWithUTF8String:mmapID.c_str()]]; - return (mmkv::MMKVRecoverStrategic) ret; - } - } - return mmkv::OnErrorDiscard; -} - -static void ContentChangeHandler(const string &mmapID) { - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) { - [g_callbackHandler onMMKVContentChange:[NSString stringWithUTF8String:mmapID.c_str()]]; - } -} - -static void ContentLoadedHandler(const string &mmapID) { - if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentLoadSuccessfully:)]) { - [g_callbackHandler onMMKVContentLoadSuccessfully:[NSString stringWithUTF8String:mmapID.c_str()]]; - } -} - #pragma mark - MMKVNameSpace @implementation MMKVNameSpace {